@fitlab-ai/agent-infra 0.7.0 → 0.7.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.
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +3 -0
- package/dist/lib/init.js +62 -23
- package/dist/lib/prompt.js +49 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/list-running.js +58 -13
- package/dist/lib/sandbox/commands/rebuild.js +3 -11
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/update.js +59 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +3 -0
- package/lib/init.ts +87 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/list-running.ts +69 -16
- package/lib/sandbox/commands/rebuild.ts +3 -12
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/update.ts +71 -30
- package/package.json +1 -1
- package/templates/.agents/README.en.md +32 -0
- package/templates/.agents/README.zh-CN.md +32 -0
- package/templates/.agents/rules/task-short-id.en.md +141 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +124 -0
- package/templates/.agents/scripts/task-short-id.js +713 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/commit/SKILL.en.md +4 -0
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +14 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +14 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +14 -0
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +14 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +14 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +14 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +14 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -0
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +4 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +112 -21
- package/templates/.agents/templates/task.en.md +1 -0
- package/templates/.agents/templates/task.zh-CN.md +1 -0
package/lib/update.ts
CHANGED
|
@@ -3,6 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { info, ok, err } from './log.ts';
|
|
4
4
|
import { resolveTemplateDir } from './paths.ts';
|
|
5
5
|
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
|
|
6
|
+
import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from './builtin-tuis.ts';
|
|
7
|
+
import type { BuiltinTUIId } from './builtin-tuis.ts';
|
|
6
8
|
|
|
7
9
|
type FileRegistry = {
|
|
8
10
|
managed: string[];
|
|
@@ -17,14 +19,17 @@ type UpdateConfig = {
|
|
|
17
19
|
platform?: { type?: string };
|
|
18
20
|
requiresPullRequest?: boolean;
|
|
19
21
|
sandbox?: Record<string, unknown>;
|
|
22
|
+
task?: { shortIdLength: number };
|
|
20
23
|
labels?: Record<string, unknown>;
|
|
21
24
|
files?: Partial<FileRegistry>;
|
|
25
|
+
tuis?: unknown;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
type Defaults = {
|
|
25
29
|
platform: { type: string };
|
|
26
30
|
requiresPullRequest: boolean;
|
|
27
31
|
sandbox: Record<string, unknown>;
|
|
32
|
+
task: { shortIdLength: number };
|
|
28
33
|
labels: Record<string, unknown>;
|
|
29
34
|
files: FileRegistry;
|
|
30
35
|
};
|
|
@@ -45,7 +50,7 @@ function isPathOwnedByOtherPlatform(relativePath: string, platformType: string):
|
|
|
45
50
|
return candidate !== platformType;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
function syncFileRegistry(config: UpdateConfig, platformType: string) {
|
|
53
|
+
function syncFileRegistry(config: UpdateConfig, platformType: string, enabledTUIs: Set<BuiltinTUIId>) {
|
|
49
54
|
config.files ||= {};
|
|
50
55
|
const before = JSON.stringify({
|
|
51
56
|
files: {
|
|
@@ -67,6 +72,7 @@ function syncFileRegistry(config: UpdateConfig, platformType: string) {
|
|
|
67
72
|
|
|
68
73
|
for (const entry of defaults.files.managed) {
|
|
69
74
|
if (isPathOwnedByOtherPlatform(entry, platformType)) continue;
|
|
75
|
+
if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
|
|
70
76
|
if (!allExisting.includes(entry)) {
|
|
71
77
|
config.files.managed.push(entry);
|
|
72
78
|
added.managed.push(entry);
|
|
@@ -74,6 +80,7 @@ function syncFileRegistry(config: UpdateConfig, platformType: string) {
|
|
|
74
80
|
}
|
|
75
81
|
for (const entry of defaults.files.merged) {
|
|
76
82
|
if (isPathOwnedByOtherPlatform(entry, platformType)) continue;
|
|
83
|
+
if (isPathOwnedByDisabledTUI(entry, enabledTUIs)) continue;
|
|
77
84
|
if (!allExisting.includes(entry)) {
|
|
78
85
|
config.files.merged.push(entry);
|
|
79
86
|
added.merged.push(entry);
|
|
@@ -118,6 +125,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
118
125
|
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) as UpdateConfig;
|
|
119
126
|
const { project, org, language } = config;
|
|
120
127
|
const platformType = config.platform?.type || defaults.platform.type;
|
|
128
|
+
const enabledTUIs = resolveEnabledTUIs(config.tuis);
|
|
121
129
|
const replacements = { project, org };
|
|
122
130
|
|
|
123
131
|
info(`Updating seed files for: ${project}`);
|
|
@@ -150,35 +158,42 @@ async function cmdUpdate(): Promise<void> {
|
|
|
150
158
|
// Ignore missing legacy script from pre-ESM installs.
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
// update Claude command
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
// update Claude command (only if enabled)
|
|
162
|
+
if (enabledTUIs.has('claude-code')) {
|
|
163
|
+
renderFile(
|
|
164
|
+
path.join(templateDir, '.claude', 'commands', claudeSrc),
|
|
165
|
+
path.join('.claude', 'commands', 'update-agent-infra.md'),
|
|
166
|
+
replacements
|
|
167
|
+
);
|
|
168
|
+
ok('Updated .claude/commands/update-agent-infra.md');
|
|
169
|
+
}
|
|
160
170
|
|
|
161
|
-
// update Gemini command
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
// update Gemini command (only if enabled)
|
|
172
|
+
if (enabledTUIs.has('gemini-cli')) {
|
|
173
|
+
renderFile(
|
|
174
|
+
path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc),
|
|
175
|
+
path.join('.gemini', 'commands', project, 'update-agent-infra.toml'),
|
|
176
|
+
replacements
|
|
177
|
+
);
|
|
178
|
+
ok(`Updated .gemini/commands/${project}/update-agent-infra.toml`);
|
|
179
|
+
}
|
|
168
180
|
|
|
169
|
-
// update OpenCode command
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
// update OpenCode command (only if enabled)
|
|
182
|
+
if (enabledTUIs.has('opencode')) {
|
|
183
|
+
renderFile(
|
|
184
|
+
path.join(templateDir, '.opencode', 'commands', opencodeSrc),
|
|
185
|
+
path.join('.opencode', 'commands', 'update-agent-infra.md'),
|
|
186
|
+
replacements
|
|
187
|
+
);
|
|
188
|
+
ok('Updated .opencode/commands/update-agent-infra.md');
|
|
189
|
+
}
|
|
176
190
|
|
|
177
191
|
// sync file registry
|
|
178
|
-
const { added, changed } = syncFileRegistry(config, platformType);
|
|
192
|
+
const { added, changed } = syncFileRegistry(config, platformType, enabledTUIs);
|
|
179
193
|
const hasNewEntries = added.managed.length > 0 || added.merged.length > 0;
|
|
180
194
|
const platformAdded = !config.platform;
|
|
181
195
|
const sandboxAdded = !config.sandbox;
|
|
196
|
+
const taskAdded = !config.task;
|
|
182
197
|
const labelsAdded = !config.labels;
|
|
183
198
|
const requiresPullRequestAdded = config.requiresPullRequest === undefined;
|
|
184
199
|
let configChanged = changed;
|
|
@@ -193,6 +208,11 @@ async function cmdUpdate(): Promise<void> {
|
|
|
193
208
|
configChanged = true;
|
|
194
209
|
}
|
|
195
210
|
|
|
211
|
+
if (taskAdded) {
|
|
212
|
+
config.task = structuredClone(defaults.task);
|
|
213
|
+
configChanged = true;
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
if (labelsAdded) {
|
|
197
217
|
config.labels = structuredClone(defaults.labels);
|
|
198
218
|
configChanged = true;
|
|
@@ -213,13 +233,16 @@ async function cmdUpdate(): Promise<void> {
|
|
|
213
233
|
for (const entry of added.merged) {
|
|
214
234
|
ok(` merged: ${entry}`);
|
|
215
235
|
}
|
|
216
|
-
} else if (platformAdded || sandboxAdded || labelsAdded || requiresPullRequestAdded) {
|
|
236
|
+
} else if (platformAdded || sandboxAdded || taskAdded || labelsAdded || requiresPullRequestAdded) {
|
|
217
237
|
if (platformAdded) {
|
|
218
238
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
219
239
|
}
|
|
220
240
|
if (sandboxAdded) {
|
|
221
241
|
info(`Default sandbox config added to ${CONFIG_PATH}.`);
|
|
222
242
|
}
|
|
243
|
+
if (taskAdded) {
|
|
244
|
+
info(`Default task.shortIdLength=${defaults.task.shortIdLength} added to ${CONFIG_PATH}.`);
|
|
245
|
+
}
|
|
223
246
|
if (labelsAdded) {
|
|
224
247
|
info(`Default labels.in config added to ${CONFIG_PATH}.`);
|
|
225
248
|
}
|
|
@@ -232,6 +255,9 @@ async function cmdUpdate(): Promise<void> {
|
|
|
232
255
|
if (hasNewEntries && sandboxAdded) {
|
|
233
256
|
info(`Default sandbox config added to ${CONFIG_PATH}.`);
|
|
234
257
|
}
|
|
258
|
+
if (hasNewEntries && taskAdded) {
|
|
259
|
+
info(`Default task.shortIdLength=${defaults.task.shortIdLength} added to ${CONFIG_PATH}.`);
|
|
260
|
+
}
|
|
235
261
|
if (hasNewEntries && labelsAdded) {
|
|
236
262
|
info(`Default labels.in config added to ${CONFIG_PATH}.`);
|
|
237
263
|
}
|
|
@@ -249,12 +275,27 @@ async function cmdUpdate(): Promise<void> {
|
|
|
249
275
|
console.log('');
|
|
250
276
|
ok('Seed files updated successfully!');
|
|
251
277
|
console.log('');
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
278
|
+
if (enabledTUIs.size === 0) {
|
|
279
|
+
console.log(' No built-in TUI enabled (tuis: []).');
|
|
280
|
+
console.log(` Configure "customTUIs" in ${CONFIG_PATH} if needed.`);
|
|
281
|
+
console.log('');
|
|
282
|
+
} else {
|
|
283
|
+
console.log(' Next step: run the full update in your AI TUI:');
|
|
284
|
+
console.log('');
|
|
285
|
+
const claudeOrOpencode: string[] = [];
|
|
286
|
+
if (enabledTUIs.has('claude-code')) claudeOrOpencode.push('Claude Code');
|
|
287
|
+
if (enabledTUIs.has('opencode')) claudeOrOpencode.push('OpenCode');
|
|
288
|
+
if (claudeOrOpencode.length > 0) {
|
|
289
|
+
console.log(` ${claudeOrOpencode.join(' / ')}: /update-agent-infra`);
|
|
290
|
+
}
|
|
291
|
+
if (enabledTUIs.has('gemini-cli')) {
|
|
292
|
+
console.log(` Gemini CLI: /${project}:update-agent-infra`);
|
|
293
|
+
}
|
|
294
|
+
if (enabledTUIs.has('codex')) {
|
|
295
|
+
console.log(' Codex CLI: $update-agent-infra');
|
|
296
|
+
}
|
|
297
|
+
console.log('');
|
|
298
|
+
}
|
|
258
299
|
}
|
|
259
300
|
|
|
260
301
|
export { cmdUpdate };
|
package/package.json
CHANGED
|
@@ -210,6 +210,38 @@ The `files` field in `.agents/.airc.json` groups project files into three catego
|
|
|
210
210
|
|
|
211
211
|
`ejected` entries support literal paths or globs, using the same matching rules as `merged`.
|
|
212
212
|
|
|
213
|
+
## Built-in TUI Selection
|
|
214
|
+
|
|
215
|
+
Use the top-level `.agents/.airc.json` `tuis` array to pick which built-in TUIs (`claude-code`, `codex`, `gemini-cli`, `opencode`) agent-infra should install command files for and keep in sync.
|
|
216
|
+
|
|
217
|
+
| Value | Meaning |
|
|
218
|
+
|-------|---------|
|
|
219
|
+
| `tuis` missing or `null` | All four built-in TUIs are enabled (backward-compatible default for legacy `.airc.json` predating this field). |
|
|
220
|
+
| `tuis: []` | No built-in TUI is managed. Use this when the project only relies on `customTUIs` and does not need any built-in command files installed. |
|
|
221
|
+
| `tuis: [<subset>]` | Only the listed TUIs are managed. Unknown ids are ignored. |
|
|
222
|
+
|
|
223
|
+
`ai init` includes an interactive multi-select for this field:
|
|
224
|
+
|
|
225
|
+
- Press Enter to accept the default (all built-in TUIs enabled).
|
|
226
|
+
- Type comma-separated numbers or ids (e.g. `1,3` or `claude-code,opencode`) to keep a subset.
|
|
227
|
+
- Type `none` to explicitly disable every built-in TUI (typically combined with a `customTUIs` entry added later).
|
|
228
|
+
- Invalid input (duplicate, out-of-range, unknown id, whitespace-only) aborts init with a non-zero exit code.
|
|
229
|
+
|
|
230
|
+
### Side effects of disabling a TUI
|
|
231
|
+
|
|
232
|
+
When you disable a built-in TUI (either via `ai init` or by hand-editing `.airc.json`), the next `ai update` / `update-agent-infra` will:
|
|
233
|
+
|
|
234
|
+
- skip seed command writes for that TUI (e.g. `.gemini/commands/<project>/update-agent-infra.toml`);
|
|
235
|
+
- skip the TUI's owned default entries when backfilling `files.managed` / `files.merged`;
|
|
236
|
+
- **clean up existing files** under the TUI's owned path prefix (`.claude/`, `.codex/`, `.gemini/`, `.opencode/`) — these are listed in `report.managed.removed`, mirroring the cleanup behavior when switching `platform`.
|
|
237
|
+
|
|
238
|
+
To opt a specific file out of this cleanup, list it in `files.ejected`; ejected entries owned by disabled TUIs are preserved as-is and are not re-created by sync.
|
|
239
|
+
|
|
240
|
+
### Relation to other config fields
|
|
241
|
+
|
|
242
|
+
- `tuis` controls **which TUI command files agent-infra writes and maintains**. It is independent from `sandbox.tools`, which controls **which CLIs the sandbox image installs**. Toggling one does not affect the other; the README for `sandbox.tools` lives in the Sandbox section.
|
|
243
|
+
- `tuis` is independent from `customTUIs` (see below). CustomTUI command files are not removed when you disable a built-in TUI, even if the customTUI's `dir` falls under that TUI's owned prefix (e.g. a customTUI configured with `dir: ".codex/commands"` is preserved when `codex` is disabled).
|
|
244
|
+
|
|
213
245
|
## Custom TUI Configuration
|
|
214
246
|
|
|
215
247
|
Use the top-level `.agents/.airc.json` `customTUIs` array when your team uses an AI TUI that is not one of the built-in command targets. This config lets agent-infra show the correct next-step commands and generate command files for project custom skills by learning from an existing command in the custom TUI directory.
|
|
@@ -210,6 +210,38 @@ args: "<task-id>" # 可选
|
|
|
210
210
|
|
|
211
211
|
`ejected` 条目支持字面路径或 glob,匹配规则与 `merged` 相同。
|
|
212
212
|
|
|
213
|
+
## 内建 TUI 选择
|
|
214
|
+
|
|
215
|
+
`.agents/.airc.json` 顶层 `tuis` 数组用于决定 agent-infra 应当为哪些内建 TUI(`claude-code`、`codex`、`gemini-cli`、`opencode`)安装并维护命令文件。
|
|
216
|
+
|
|
217
|
+
| 取值 | 含义 |
|
|
218
|
+
|------|------|
|
|
219
|
+
| `tuis` 缺失或为 `null` | 启用全部四个内建 TUI(向后兼容默认,适用于本字段引入之前的 `.airc.json`) |
|
|
220
|
+
| `tuis: []` | 不维护任何内建 TUI。适用于只依赖 `customTUIs`、不需要安装任何内建命令文件的项目 |
|
|
221
|
+
| `tuis: [<子集>]` | 仅维护列出的 TUI;未知 id 会被忽略 |
|
|
222
|
+
|
|
223
|
+
`ai init` 会通过交互式多选询问该字段:
|
|
224
|
+
|
|
225
|
+
- 直接回车 = 接受默认值(全部内建 TUI 启用)。
|
|
226
|
+
- 输入逗号分隔的编号或 id(如 `1,3` 或 `claude-code,opencode`)= 只保留子集。
|
|
227
|
+
- 输入 `none` = 明确不启用任何内建 TUI(通常配合后续在 `customTUIs` 添加条目使用)。
|
|
228
|
+
- 非法输入(重复、超界、未知 id、纯空白)会让 init 以非零退出码终止。
|
|
229
|
+
|
|
230
|
+
### 取消某个 TUI 的副作用
|
|
231
|
+
|
|
232
|
+
通过 `ai init` 或手工编辑 `.airc.json` 取消某个内建 TUI 后,下一次 `ai update` / `update-agent-infra` 会:
|
|
233
|
+
|
|
234
|
+
- 跳过该 TUI 的 seed 命令文件写入(例如 `.gemini/commands/<project>/update-agent-infra.toml`);
|
|
235
|
+
- 在回填 `files.managed` / `files.merged` 时跳过该 TUI owned 的默认条目;
|
|
236
|
+
- **物理清理**该 TUI owned 路径前缀(`.claude/`、`.codex/`、`.gemini/`、`.opencode/`)下的已有文件——清理列表会出现在 `report.managed.removed`,与切换 `platform` 时的清理行为一致。
|
|
237
|
+
|
|
238
|
+
若希望保留某个具体文件,把它加入 `files.ejected`:被 ejected 的、属于已取消 TUI 的条目会保持原状,sync 不会重新创建也不会删除。
|
|
239
|
+
|
|
240
|
+
### 与其他配置字段的关系
|
|
241
|
+
|
|
242
|
+
- `tuis` 控制 **agent-infra 写入与维护哪些 TUI 的命令文件**,与 `sandbox.tools`(控制**沙箱镜像里安装哪些 CLI**)相互独立。两者互不影响;`sandbox.tools` 的说明见 Sandbox 一节。
|
|
243
|
+
- `tuis` 与 `customTUIs`(见下)相互独立。取消某个内建 TUI 时 customTUI 命令文件不会被清理,即便 customTUI 的 `dir` 落在该 TUI 的 owned 前缀下(例如 `dir: ".codex/commands"` 的 customTUI 在 `codex` 被取消时仍会保留)。
|
|
244
|
+
|
|
213
245
|
## 自定义 TUI 配置
|
|
214
246
|
|
|
215
247
|
当团队使用的 AI TUI 不属于内置命令目标时,可以在 `.agents/.airc.json` 顶层配置 `customTUIs` 数组。该配置用于让 agent-infra 输出正确的下一步命令,并通过学习自定义 TUI 目录中的既有命令文件,为项目自定义 skill 生成同格式命令。
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Task short id
|
|
2
|
+
|
|
3
|
+
Task short ids let mobile-style SKILL invocations replace the full 22-char
|
|
4
|
+
`TASK-YYYYMMDD-HHMMSS` with `#NN` while a task is active.
|
|
5
|
+
|
|
6
|
+
## Syntax
|
|
7
|
+
|
|
8
|
+
- Format: `^#\d{shortIdLength}$` (**zero-padded to a fixed width**; with the
|
|
9
|
+
default `shortIdLength=2`, e.g. `#01`, `#07`, `#42`).
|
|
10
|
+
- **Must** be zero-padded to `shortIdLength` digits (default 2: `#1` is a format
|
|
11
|
+
error, use `#01`). This keeps things aligned and touch-typeable.
|
|
12
|
+
- `#00` (or `#0` when `shortIdLength=1`) is reserved and never allocated; digits
|
|
13
|
+
only, no letters.
|
|
14
|
+
- The plain `TASK-…` form keeps working everywhere; `#NN` is an alias, not the
|
|
15
|
+
persisted task id.
|
|
16
|
+
|
|
17
|
+
## Lifecycle
|
|
18
|
+
|
|
19
|
+
| Action | When | Effect on registry & task.md |
|
|
20
|
+
|------------|-----------------------------------------------------------------------|---------------------------------------------------------------|
|
|
21
|
+
| alloc | `create-task`, `import-issue`, `import-codescan`, `import-dependabot` | Assigns lowest free `#NN`; writes `short_id` into task.md. |
|
|
22
|
+
| resolve | Lifecycle SKILLs (`analyze-task`, `plan-task`, `code-task`, …) | Looks up `#NN` → full task id. Does not allocate. |
|
|
23
|
+
| release | `complete-task`, `cancel-task`, `block-task`, `close-codescan`, `close-dependabot` | Removes the registry entry; leaves task.md `short_id` as a historical value. |
|
|
24
|
+
| re-alloc | `restore-task` | Re-allocates a (possibly new) `#NN` and writes it to task.md. |
|
|
25
|
+
|
|
26
|
+
Short ids are valid only while a task lives in `.agents/workspace/active/`.
|
|
27
|
+
Once it is moved to `completed/`, `blocked/`, or `archive/`, the `#NN` slot is
|
|
28
|
+
freed and may be reused by a new task.
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
```jsonc
|
|
33
|
+
// .agents/.airc.json
|
|
34
|
+
{
|
|
35
|
+
"task": {
|
|
36
|
+
"shortIdLength": 2 // default; capacity = 99 (#01–#99). Set to 3 for #001–#999.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
When all slots for the configured width are in use, `alloc` fails with a clear
|
|
42
|
+
error suggesting either archiving some tasks or raising `task.shortIdLength`.
|
|
43
|
+
There is no silent extension or truncation. Changing `shortIdLength` requires
|
|
44
|
+
archiving all active tasks first (the registry key width depends on it).
|
|
45
|
+
|
|
46
|
+
## `#NN` resolution scope (split by entrypoint)
|
|
47
|
+
|
|
48
|
+
| Entrypoint | Hit | Miss |
|
|
49
|
+
|-------------------------------------------------------------|----------------------|------------------------------------------------------|
|
|
50
|
+
| SKILL parameter resolver (lifecycle SKILLs) | resolve to full id | **strict error** — short id not found / invalid |
|
|
51
|
+
| `ai sandbox enter '#NN'` / `ai sandbox exec '#NN' …` | resolve to full id | fall back to running-sandbox ls index (`#414`) |
|
|
52
|
+
|
|
53
|
+
`list --verify` is strictly read-only: it reports discrepancies between active
|
|
54
|
+
dir, registry, and `short_id` declared in each `task.md`, but never writes.
|
|
55
|
+
|
|
56
|
+
## SKILL parameter resolver
|
|
57
|
+
|
|
58
|
+
Any SKILL (alloc / resolve / release / re-alloc lifecycle entry-points) that
|
|
59
|
+
receives a `{task-id}` argument must follow this contract:
|
|
60
|
+
|
|
61
|
+
1. If `{task-id}` starts with `#`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
if [[ "{task-id}" == "#"* ]]; then
|
|
65
|
+
# The script writes the full error message (including "expected #NN
|
|
66
|
+
# (N-digit zero-padded; e.g. '#01')") to stderr; callers only forward the exit.
|
|
67
|
+
task_id=$(node .agents/scripts/task-short-id.js resolve "{task-id}") || exit 1
|
|
68
|
+
else
|
|
69
|
+
task_id="{task-id}"
|
|
70
|
+
fi
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
2. Every downstream command treats `{task-id}` as `$task_id` (already the full
|
|
74
|
+
`TASK-YYYYMMDD-HHMMSS` form).
|
|
75
|
+
3. Error-code semantics for resolve are documented under "Error scenarios"; do
|
|
76
|
+
not reimplement error handling inside each SKILL.
|
|
77
|
+
|
|
78
|
+
## Storage
|
|
79
|
+
|
|
80
|
+
The short id system persists state in two places that stay in sync at rest:
|
|
81
|
+
|
|
82
|
+
| Location | Written by | Read by | Removed by |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `.agents/workspace/active/.short-ids.json` (registry) | `alloc` / cold-start migration | `resolve` (authoritative) / `list` / `list --verify` | `release` / cold-start stale cleanup |
|
|
85
|
+
| `short_id` frontmatter field in each task.md | `alloc` / cold-start migration | `list --verify` (consistency check) | **never** (kept as historical value after archive) |
|
|
86
|
+
|
|
87
|
+
**Registry**:
|
|
88
|
+
|
|
89
|
+
- Path: `<repo-root>/.agents/workspace/active/.short-ids.json`
|
|
90
|
+
- Schema: `{ "version": 1, "ids": { "01": "TASK-20260609-192644", "02": "TASK-…" } }`
|
|
91
|
+
- Keys are zero-padded decimal strings of `task.shortIdLength` digits; values are
|
|
92
|
+
full `TASK-…` task ids.
|
|
93
|
+
- Automatically git-ignored (the whole active workspace is ignored; no new
|
|
94
|
+
ignore entry needed).
|
|
95
|
+
- Created on demand by the first `alloc` / `resolve`; an absent file is treated
|
|
96
|
+
as an empty registry.
|
|
97
|
+
|
|
98
|
+
**`short_id` field in task.md**:
|
|
99
|
+
|
|
100
|
+
- Lives in frontmatter, immediately after `id`; formatted `short_id: #01`.
|
|
101
|
+
- Matches the registry key byte-for-byte (including the `#` prefix).
|
|
102
|
+
- After archive (complete-task / cancel-task / block-task / close-*) the
|
|
103
|
+
registry entry is deleted immediately (the short id can be reused), but the
|
|
104
|
+
`short_id` field in task.md is kept as a historical value. The resolver
|
|
105
|
+
trusts the registry only.
|
|
106
|
+
- Cold-start migration: the first `alloc` / `resolve` after an upgrade scans
|
|
107
|
+
the active directory and fills in the missing field for legacy tasks; the
|
|
108
|
+
field write is constrained (does NOT refresh `updated_at` /
|
|
109
|
+
`agent_infra_version` and does NOT append Activity Log).
|
|
110
|
+
|
|
111
|
+
`resolve('#NN')` workflow: ① validate arg matches `^#\d{shortIdLength}$` →
|
|
112
|
+
② look up `NN` directly as the registry `ids` key → ③ return full task id on
|
|
113
|
+
hit; on miss, exit 1 with the `list --verify` repair hint.
|
|
114
|
+
|
|
115
|
+
## Error scenarios
|
|
116
|
+
|
|
117
|
+
- **Short id not found**: the registry has no entry for `#NN`. Either the task
|
|
118
|
+
was archived (release freed the slot) or the input is wrong.
|
|
119
|
+
- **Registry corruption** (duplicate registry entries for the same task id, or
|
|
120
|
+
the JSON is unparsable): exit code 2; manual cleanup required.
|
|
121
|
+
- **Parameter format error** (e.g. `#00`, `#abc`, `#`, or `#1` when
|
|
122
|
+
`shortIdLength=2`): exit code 1.
|
|
123
|
+
|
|
124
|
+
## Cross-TUI quoting
|
|
125
|
+
|
|
126
|
+
Bash treats `#` as a comment marker. Always single-quote: `ai sandbox exec '#03' 'npm test'`.
|
|
127
|
+
Claude Code / Codex / Gemini CLI / OpenCode all forward `#NN` to SKILL
|
|
128
|
+
`ARGUMENTS` literally when quoted.
|
|
129
|
+
|
|
130
|
+
## Cold-start migration
|
|
131
|
+
|
|
132
|
+
When a project upgrades to a version with this feature, the first call to
|
|
133
|
+
`alloc` / `resolve` runs the cold-start path:
|
|
134
|
+
|
|
135
|
+
- Active tasks whose `task.md` lacks `short_id` get one allocated and written
|
|
136
|
+
back (the only frontmatter mutation; `updated_at` / `agent_infra_version`
|
|
137
|
+
are **not** refreshed and Activity Log is **not** appended).
|
|
138
|
+
- If active task count exceeds `shortIdLength` capacity, the migration aborts
|
|
139
|
+
**before any write** with a capacity error.
|
|
140
|
+
- If a partial write fails midway, `tx.commit()` rolls all task.md files back to
|
|
141
|
+
their original content (including `mtime` / `atime`).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# 任务短号
|
|
2
|
+
|
|
3
|
+
短号让所有 SKILL 在 active 任务生命周期内可以用 `#NN` 替代完整的 22 字符
|
|
4
|
+
`TASK-YYYYMMDD-HHMMSS`。
|
|
5
|
+
|
|
6
|
+
## 语法
|
|
7
|
+
|
|
8
|
+
- 格式:`^#\d{shortIdLength}$`(**零填充到固定宽度**;默认 `shortIdLength=2` 时
|
|
9
|
+
形如 `#01`、`#07`、`#42`)。
|
|
10
|
+
- **必须**零填充到 `shortIdLength` 位(默认 2 位:`#1` 视为格式错误,应输入
|
|
11
|
+
`#01`)。这是为了视觉对齐与盲打体验。
|
|
12
|
+
- `#00`(或 `shortIdLength=1` 时 `#0`)保留、永不分配;纯数字、不引入字母。
|
|
13
|
+
- 完整 `TASK-…` 入参在所有路径下行为与现状等价;`#NN` 只是别名,不是持久化任务 ID。
|
|
14
|
+
|
|
15
|
+
## 生命周期
|
|
16
|
+
|
|
17
|
+
| 动作 | 触发时机 | 注册表 / task.md 效应 |
|
|
18
|
+
|-----------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
|
19
|
+
| alloc | `create-task`、`import-issue`、`import-codescan`、`import-dependabot` | 分配最小可用 `#NN`,写入 task.md 的 `short_id` 字段。 |
|
|
20
|
+
| resolve | 生命周期 SKILL(`analyze-task` / `plan-task` / `code-task` / `review-*` / `commit` / …) | `#NN` → 完整 task id 查询,不分配。 |
|
|
21
|
+
| release | `complete-task`、`cancel-task`、`block-task`、`close-codescan`、`close-dependabot` | 从注册表移除;task.md 的 `short_id` 字段保留作为历史值。 |
|
|
22
|
+
| re-alloc | `restore-task` | 重新分配(可能与历史不同),写入注册表与 task.md。 |
|
|
23
|
+
|
|
24
|
+
短号仅在任务处于 `.agents/workspace/active/` 期间有效;任务移动到
|
|
25
|
+
`completed/` / `blocked/` / `archive/` 后短号立即释放,可被新任务复用。
|
|
26
|
+
|
|
27
|
+
## 配置
|
|
28
|
+
|
|
29
|
+
```jsonc
|
|
30
|
+
// .agents/.airc.json
|
|
31
|
+
{
|
|
32
|
+
"task": {
|
|
33
|
+
"shortIdLength": 2 // 默认;容量 = 99(#01–#99)。改为 3 时容量 = #001–#999。
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
当前位宽容量耗尽时,`alloc` 给出明确错误并建议「归档若干任务」或「调高
|
|
39
|
+
`task.shortIdLength`」两种修复路径;不静默扩位、不静默截断。
|
|
40
|
+
切换 `shortIdLength` 配置需要先归档所有 active 任务(注册表 key 宽度依赖配置)。
|
|
41
|
+
|
|
42
|
+
## `#NN` 解析作用域(按入口二分)
|
|
43
|
+
|
|
44
|
+
| 入口 | 注册表命中 | 注册表未命中 |
|
|
45
|
+
|-----------------------------------------------------------|----------------------|--------------------------------------------------------|
|
|
46
|
+
| SKILL 入参解析器(生命周期 SKILL) | 解析为完整 task id | **严格报错** —— 短号不存在 / 格式错误 |
|
|
47
|
+
| `ai sandbox enter '#NN'` / `ai sandbox exec '#NN' …` | 解析为完整 task id | 回退到 running sandbox 的 ls 行号语义(保留 #414 行为)|
|
|
48
|
+
|
|
49
|
+
`list --verify` 严格只读:报告 active 目录 / 注册表 / 各 task.md 的 `short_id`
|
|
50
|
+
三者差异,但不修改任何状态。
|
|
51
|
+
|
|
52
|
+
## SKILL 入参解析
|
|
53
|
+
|
|
54
|
+
任意 SKILL(含 alloc / resolve / release / re-alloc 四类生命周期入口)在收到
|
|
55
|
+
`{task-id}` 入参后,必须按以下契约处理:
|
|
56
|
+
|
|
57
|
+
1. 如果 `{task-id}` 字面以 `#` 开头:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
if [[ "{task-id}" == "#"* ]]; then
|
|
61
|
+
# 脚本本身已输出完整错误(含「expected #NN (N-digit zero-padded; e.g. '#01')」);
|
|
62
|
+
# 调用方只需透传退出码
|
|
63
|
+
task_id=$(node .agents/scripts/task-short-id.js resolve "{task-id}") || exit 1
|
|
64
|
+
else
|
|
65
|
+
task_id="{task-id}"
|
|
66
|
+
fi
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. 后续所有命令把 `{task-id}` 视为 `$task_id`(已是完整 `TASK-YYYYMMDD-HHMMSS` 形式)
|
|
70
|
+
3. 解析失败的退出码语义参见「错误场景」段;不要在 SKILL 中重写错误处理
|
|
71
|
+
|
|
72
|
+
## 存储位置
|
|
73
|
+
|
|
74
|
+
短号系统跨两处持久化状态,二者在稳态时一一对应:
|
|
75
|
+
|
|
76
|
+
| 位置 | 写入时机 | 读取时机 | 删除时机 |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `.agents/workspace/active/.short-ids.json`(注册表) | `alloc` / 冷启动迁移 | `resolve` 唯一权威源 / `list` / `list --verify` | `release` / 冷启动 stale 清理 |
|
|
79
|
+
| 各 task.md frontmatter 的 `short_id` 字段 | `alloc` / 冷启动迁移 | `list --verify`(比对一致性) | **永不删除**(归档后保留为历史值) |
|
|
80
|
+
|
|
81
|
+
**注册表**:
|
|
82
|
+
|
|
83
|
+
- 路径:`<repo-root>/.agents/workspace/active/.short-ids.json`
|
|
84
|
+
- Schema:`{ "version": 1, "ids": { "01": "TASK-20260609-192644", "02": "TASK-…" } }`
|
|
85
|
+
- key 是零填充到 `task.shortIdLength` 位的字符串,value 是完整 `TASK-…` task id
|
|
86
|
+
- 自动 git ignore(active 工作区整体 ignore;无需新增 ignore 条目)
|
|
87
|
+
- 首次 `alloc` / `resolve` 时按需自动创建;不存在时按空注册表处理
|
|
88
|
+
|
|
89
|
+
**task.md `short_id` 字段**:
|
|
90
|
+
|
|
91
|
+
- 在 frontmatter 中、紧跟 `id` 字段之后;格式 `short_id: #01`
|
|
92
|
+
- 与注册表 key 字面一致(含 `#` 前缀)
|
|
93
|
+
- 归档(complete-task / cancel-task / block-task / close-*)后:注册表 entry 立即
|
|
94
|
+
删除(短号可被新任务复用),但 task.md `short_id` 字段保留作为历史值。解析器
|
|
95
|
+
只信任注册表
|
|
96
|
+
- 冷启动迁移:升级 agent-infra 后首次 alloc / resolve 路径会扫描所有 active
|
|
97
|
+
目录并为缺字段的 task.md 补发短号;补发受字段保护约束(不刷新
|
|
98
|
+
`updated_at` / `agent_infra_version`、不追加 Activity Log)
|
|
99
|
+
|
|
100
|
+
`resolve('#NN')` 工作流:① 校验入参严格匹配 `^#\d{shortIdLength}$` → ② 直接以
|
|
101
|
+
`NN` 作为 key 查注册表 `ids` → ③ 命中返回完整 task id,未命中按 `list --verify`
|
|
102
|
+
给出修复指引退出 1。
|
|
103
|
+
|
|
104
|
+
## 错误场景
|
|
105
|
+
|
|
106
|
+
- **短号不存在**:注册表中无 `#NN`。可能是任务已归档(短号已释放)或输入错误。
|
|
107
|
+
- **注册表损坏**(同一 taskId 出现多次或 JSON 无法解析):退出码 2,需人工处理。
|
|
108
|
+
- **参数格式错误**(如 `#00`、`#abc`、`#`、`#1` 当 `shortIdLength=2` 时):退出码 1。
|
|
109
|
+
|
|
110
|
+
## 跨 TUI 引号要求
|
|
111
|
+
|
|
112
|
+
bash 中 `#` 是注释起始符,必须单引号:`ai sandbox exec '#03' 'npm test'`。
|
|
113
|
+
Claude Code / Codex / Gemini CLI / OpenCode 在加引号时都能把 `#NN` 字面传递到
|
|
114
|
+
SKILL 的 `ARGUMENTS`。
|
|
115
|
+
|
|
116
|
+
## 冷启动迁移
|
|
117
|
+
|
|
118
|
+
升级 agent-infra 后,首次 `alloc` / `resolve` 调用会触发冷启动迁移:
|
|
119
|
+
|
|
120
|
+
- 所有 active task.md 缺 `short_id` 字段时自动补发并回写(仅修改 `short_id`
|
|
121
|
+
一行,不刷新 `updated_at` / `agent_infra_version`,不追加 Activity Log)。
|
|
122
|
+
- 若 active 任务总数超过 `shortIdLength` 容量,**在任何写入之前**报错退出 2。
|
|
123
|
+
- 若 task.md 写入中途失败,`tx.commit()` 按缓存的原内容回滚所有已写文件(含
|
|
124
|
+
`mtime` / `atime` 恢复)。
|