@fitlab-ai/agent-infra 0.7.1 → 0.7.2

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 (137) hide show
  1. package/bin/cli.ts +11 -0
  2. package/dist/bin/cli.js +12 -0
  3. package/dist/lib/sandbox/commands/create.js +10 -2
  4. package/dist/lib/sandbox/commands/enter.js +8 -7
  5. package/dist/lib/sandbox/commands/list-running.js +21 -32
  6. package/dist/lib/sandbox/commands/ls.js +20 -22
  7. package/dist/lib/sandbox/index.js +7 -3
  8. package/dist/lib/sandbox/task-resolver.js +1 -1
  9. package/dist/lib/sandbox/tools.js +1 -1
  10. package/dist/lib/table.js +29 -0
  11. package/dist/lib/task/commands/ls.js +122 -0
  12. package/dist/lib/task/commands/show.js +135 -0
  13. package/dist/lib/task/frontmatter.js +32 -0
  14. package/dist/lib/task/index.js +41 -0
  15. package/dist/lib/task/short-id.js +80 -0
  16. package/lib/sandbox/commands/create.ts +11 -2
  17. package/lib/sandbox/commands/enter.ts +8 -7
  18. package/lib/sandbox/commands/list-running.ts +23 -37
  19. package/lib/sandbox/commands/ls.ts +25 -25
  20. package/lib/sandbox/index.ts +7 -3
  21. package/lib/sandbox/task-resolver.ts +1 -1
  22. package/lib/sandbox/tools.ts +1 -1
  23. package/lib/table.ts +32 -0
  24. package/lib/task/commands/ls.ts +138 -0
  25. package/lib/task/commands/show.ts +139 -0
  26. package/lib/task/frontmatter.ts +30 -0
  27. package/lib/task/index.ts +44 -0
  28. package/lib/task/short-id.ts +97 -0
  29. package/package.json +1 -1
  30. package/templates/.agents/hooks/auto-resume.sh +87 -0
  31. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  32. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  33. package/templates/.agents/rules/milestone-inference.github.en.md +4 -1
  34. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -1
  35. package/templates/.agents/rules/next-step-output.en.md +59 -0
  36. package/templates/.agents/rules/next-step-output.zh-CN.md +59 -0
  37. package/templates/.agents/rules/task-short-id.en.md +54 -62
  38. package/templates/.agents/rules/task-short-id.zh-CN.md +35 -54
  39. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
  40. package/templates/.agents/scripts/task-short-id.js +32 -189
  41. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -12
  42. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -12
  43. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  44. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  45. package/templates/.agents/skills/block-task/SKILL.en.md +6 -6
  46. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +6 -6
  47. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  48. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
  49. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
  50. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  51. package/templates/.agents/skills/check-task/SKILL.en.md +12 -10
  52. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +12 -10
  53. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -6
  54. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -6
  55. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -6
  56. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -6
  57. package/templates/.agents/skills/code-task/SKILL.en.md +10 -6
  58. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -6
  59. package/templates/.agents/skills/code-task/config/verify.en.json +2 -1
  60. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +2 -1
  61. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +10 -5
  62. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +10 -5
  63. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  64. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  65. package/templates/.agents/skills/code-task/reference/report-template.en.md +8 -0
  66. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +8 -0
  67. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  68. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  69. package/templates/.agents/skills/commit/reference/task-status-update.en.md +9 -9
  70. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +9 -9
  71. package/templates/.agents/skills/complete-task/SKILL.en.md +6 -2
  72. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +6 -2
  73. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -1
  74. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -1
  75. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -6
  76. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -6
  77. package/templates/.agents/skills/create-pr/config/verify.json +2 -1
  78. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  79. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  80. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  81. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  82. package/templates/.agents/skills/create-task/SKILL.en.md +17 -17
  83. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +17 -17
  84. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  85. package/templates/.agents/skills/import-codescan/SKILL.en.md +8 -8
  86. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +8 -8
  87. package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
  88. package/templates/.agents/skills/import-dependabot/SKILL.en.md +8 -8
  89. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +8 -8
  90. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
  91. package/templates/.agents/skills/import-issue/SKILL.en.md +7 -7
  92. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +7 -7
  93. package/templates/.agents/skills/plan-task/SKILL.en.md +10 -12
  94. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -12
  95. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  96. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  97. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  98. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  99. package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -2
  100. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -2
  101. package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -2
  102. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -2
  103. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +15 -15
  104. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +15 -15
  105. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -1
  106. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +7 -1
  107. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +2 -0
  108. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +2 -0
  109. package/templates/.agents/skills/review-code/SKILL.en.md +5 -2
  110. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -2
  111. package/templates/.agents/skills/review-code/config/verify.en.json +3 -2
  112. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -2
  113. package/templates/.agents/skills/review-code/reference/output-templates.en.md +9 -9
  114. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +9 -9
  115. package/templates/.agents/skills/review-code/reference/report-template.en.md +7 -1
  116. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +7 -1
  117. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -0
  118. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -0
  119. package/templates/.agents/skills/review-plan/SKILL.en.md +4 -2
  120. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -2
  121. package/templates/.agents/skills/review-plan/config/verify.en.json +3 -2
  122. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -2
  123. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +15 -15
  124. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +15 -15
  125. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -1
  126. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -1
  127. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +2 -0
  128. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +2 -0
  129. package/templates/.agents/templates/task.en.md +0 -1
  130. package/templates/.agents/templates/task.zh-CN.md +0 -1
  131. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  132. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +1 -1
  133. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  134. package/templates/.agents/workflows/feature-development.zh-CN.yaml +1 -1
  135. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  136. package/templates/.agents/workflows/refactoring.zh-CN.yaml +1 -1
  137. package/templates/.claude/settings.json +11 -0
@@ -0,0 +1,30 @@
1
+ type Frontmatter = Record<string, string>;
2
+
3
+ function parseTaskFrontmatter(content: string): Frontmatter {
4
+ const result: Frontmatter = {};
5
+ if (!content.startsWith('---')) return result;
6
+ const end = content.indexOf('\n---', 3);
7
+ if (end === -1) return result;
8
+ const body = content.slice(3, end);
9
+ for (const rawLine of body.split('\n')) {
10
+ const line = rawLine.replace(/\r$/, '');
11
+ if (!line.trim()) continue;
12
+ const colon = line.indexOf(':');
13
+ if (colon === -1) continue;
14
+ const key = line.slice(0, colon).trim();
15
+ const value = line.slice(colon + 1).trim();
16
+ if (key) result[key] = value;
17
+ }
18
+ return result;
19
+ }
20
+
21
+ function extractTitle(content: string): string {
22
+ for (const line of content.split('\n')) {
23
+ const m = /^#\s+(?:任务[::]?\s*)?(.+)$/.exec(line.trim());
24
+ if (m && m[1]) return m[1].trim();
25
+ }
26
+ return '';
27
+ }
28
+
29
+ export { parseTaskFrontmatter, extractTitle };
30
+ export type { Frontmatter };
@@ -0,0 +1,44 @@
1
+ const USAGE = `Usage: ai task <command> [options]
2
+
3
+ Commands:
4
+ ls [--all | --blocked | --completed] List tasks (default: active)
5
+ show <N | #N | TASK-id> Print a task.md
6
+
7
+ Examples:
8
+ ai task ls
9
+ ai task show 11
10
+ ai task show TASK-20260612-162737
11
+
12
+ Run 'ai task <command> --help' for details.`;
13
+
14
+ export async function runTask(args: string[]): Promise<void> {
15
+ const [subcommand, ...rest] = args;
16
+
17
+ if (!subcommand) {
18
+ process.stdout.write(`${USAGE}\n`);
19
+ process.exitCode = 1;
20
+ return;
21
+ }
22
+
23
+ if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
24
+ process.stdout.write(`${USAGE}\n`);
25
+ return;
26
+ }
27
+
28
+ switch (subcommand) {
29
+ case 'ls': {
30
+ const { ls } = await import('./commands/ls.ts');
31
+ ls(rest);
32
+ break;
33
+ }
34
+ case 'show': {
35
+ const { show } = await import('./commands/show.ts');
36
+ show(rest);
37
+ break;
38
+ }
39
+ default:
40
+ process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
41
+ process.stdout.write(`${USAGE}\n`);
42
+ process.exitCode = 1;
43
+ }
44
+ }
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const REGISTRY_NAME = '.short-ids.json';
5
+
6
+ type NormalizeResult =
7
+ | { kind: 'shortId'; value: string }
8
+ | { kind: 'pass'; value: string }
9
+ | { kind: 'error'; message: string };
10
+
11
+ type NormalizeOpts = { shortIdLength: number };
12
+
13
+ function normalizeShortIdInput(input: string, opts: NormalizeOpts): NormalizeResult {
14
+ const L = opts.shortIdLength;
15
+ const m = /^#?(\d+)$/.exec(input);
16
+ if (!m) {
17
+ return { kind: 'pass', value: input };
18
+ }
19
+ const n = Number(m[1]);
20
+ if (n === 0) {
21
+ return {
22
+ kind: 'error',
23
+ message: `short id '${input}' is invalid (#${'0'.repeat(L)} is reserved)`
24
+ };
25
+ }
26
+ const max = Math.pow(10, L) - 1;
27
+ if (n > max) {
28
+ return {
29
+ kind: 'error',
30
+ message: `short id ${n} exceeds shortIdLength=${L} capacity (max=${max}); archive tasks or raise task.shortIdLength in .agents/.airc.json`
31
+ };
32
+ }
33
+ return { kind: 'shortId', value: `#${String(n).padStart(L, '0')}` };
34
+ }
35
+
36
+ type RegistrySchema = {
37
+ version: number;
38
+ ids: Record<string, string>;
39
+ };
40
+
41
+ function readRegistry(repoRoot: string): RegistrySchema | null {
42
+ const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', REGISTRY_NAME);
43
+ if (!fs.existsSync(registryPath)) return null;
44
+ try {
45
+ const raw = fs.readFileSync(registryPath, 'utf8');
46
+ const data = JSON.parse(raw) as RegistrySchema;
47
+ if (!data || typeof data !== 'object' || !data.ids) return null;
48
+ return data;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function readBranchFromTaskMd(repoRoot: string, taskId: string): string | null {
55
+ const taskMdPath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
56
+ if (!fs.existsSync(taskMdPath)) return null;
57
+ const content = fs.readFileSync(taskMdPath, 'utf8');
58
+ const m = content.match(/^branch:\s*(.+)$/m);
59
+ if (!m || !m[1]) return null;
60
+ return m[1].trim().replace(/^(["'])(.*)\1$/, '$2');
61
+ }
62
+
63
+ function loadShortIdByTaskId(repoRoot: string): Map<string, string> {
64
+ const registry = readRegistry(repoRoot);
65
+ const map = new Map<string, string>();
66
+ if (!registry) return map;
67
+ for (const [key, taskId] of Object.entries(registry.ids)) {
68
+ map.set(taskId, `#${key}`);
69
+ }
70
+ return map;
71
+ }
72
+
73
+ function lookupShortIdByBranch(
74
+ branch: string,
75
+ repoRoot: string,
76
+ _opts?: { shortIdLength?: number }
77
+ ): string | null {
78
+ const registry = readRegistry(repoRoot);
79
+ if (!registry) return null;
80
+ const matches: string[] = [];
81
+ for (const [key, taskId] of Object.entries(registry.ids)) {
82
+ const taskBranch = readBranchFromTaskMd(repoRoot, taskId);
83
+ if (taskBranch && taskBranch === branch) {
84
+ matches.push(`#${key}`);
85
+ }
86
+ }
87
+ if (matches.length === 0) return null;
88
+ if (matches.length > 1) {
89
+ process.stderr.write(
90
+ `Warning: branch '${branch}' is bound to multiple active tasks: ${matches.join(', ')}; using ${matches[0]}\n`
91
+ );
92
+ }
93
+ return matches[0]!;
94
+ }
95
+
96
+ export { normalizeShortIdInput, lookupShortIdByBranch, loadShortIdByTaskId };
97
+ export type { NormalizeResult, NormalizeOpts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
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",
@@ -0,0 +1,87 @@
1
+ #!/bin/sh
2
+ # StopFailure hook: auto-resume Claude Code after a recoverable API error.
3
+ #
4
+ # Fires when a turn ends due to an API error. Runs four gates and, if all pass,
5
+ # injects a "please continue" message into the current tmux pane via send-keys.
6
+ # StopFailure output and exit code are ignored by Claude Code, so recovery is
7
+ # delivered out-of-band through tmux; every exit path here returns 0 and the
8
+ # only observable trace is the log file.
9
+ #
10
+ # Intentionally NOT using `set -e`: the network probe, tmux and state-file
11
+ # writes may fail locally without warranting an abort of the whole script.
12
+
13
+ LOG="$HOME/.claude/auto-resume.log"
14
+ STATE_DIR="$HOME/.claude/auto-resume.state"
15
+ WHITELIST="unknown server_error overloaded"
16
+ WINDOW=1800
17
+ MAX=10
18
+ PROBE_URL="https://api.anthropic.com/"
19
+ PROBE_DEADLINE=60
20
+ RESUME_TEXT="Unexpected interruption. Please continue the unfinished operation."
21
+
22
+ log() {
23
+ mkdir -p "$HOME/.claude" 2>/dev/null
24
+ printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S%z')" "$1" >> "$LOG"
25
+ lines=$(wc -l < "$LOG" 2>/dev/null | tr -cd '0-9')
26
+ [ -z "$lines" ] && lines=0
27
+ if [ "$lines" -gt 5000 ]; then
28
+ tail -n 2500 "$LOG" > "$LOG.tmp" 2>/dev/null && mv "$LOG.tmp" "$LOG"
29
+ fi
30
+ }
31
+
32
+ # Read the StopFailure payload once, then extract session_id and error. The
33
+ # `error` field identifies the API error type and drives the whitelist gate.
34
+ payload=$(cat)
35
+ session_id=$(printf '%s' "$payload" | node -e 'let c=[];process.stdin.on("data",d=>c.push(d));process.stdin.on("end",()=>{try{const p=JSON.parse(Buffer.concat(c).toString());process.stdout.write(String(p.session_id||""))}catch{process.stdout.write("")}})' 2>/dev/null)
36
+ error=$(printf '%s' "$payload" | node -e 'let c=[];process.stdin.on("data",d=>c.push(d));process.stdin.on("end",()=>{try{const p=JSON.parse(Buffer.concat(c).toString());process.stdout.write(String(p.error||""))}catch{process.stdout.write("")}})' 2>/dev/null)
37
+
38
+ # Gate 1: only act inside a tmux pane; stay silent everywhere else.
39
+ if [ -z "$TMUX_PANE" ]; then
40
+ log "not in tmux, skip (error=$error)"
41
+ exit 0
42
+ fi
43
+
44
+ # Gate 2: only recover from the whitelisted, retriable error types.
45
+ case " $WHITELIST " in
46
+ *" $error "*) : ;;
47
+ *) log "blocked: non-recoverable error=$error"; exit 0 ;;
48
+ esac
49
+
50
+ # Gate 3: back off after MAX fires within a WINDOW-second sliding window per session.
51
+ mkdir -p "$STATE_DIR" 2>/dev/null
52
+ # Treat the payload session_id as untrusted: sanitize to a safe filename so a
53
+ # value like "../outside" cannot write the state file outside STATE_DIR.
54
+ safe_session=$(printf '%s' "${session_id:-nosession}" | tr -c 'A-Za-z0-9._-' '_')
55
+ f="$STATE_DIR/$safe_session.count"
56
+ now=$(date +%s)
57
+ if [ -f "$f" ]; then
58
+ awk -v n="$now" -v w="$WINDOW" '$1 > n - w' "$f" > "$f.tmp" 2>/dev/null && mv "$f.tmp" "$f"
59
+ fi
60
+ # BSD `wc -l` (macOS) pads the count with leading spaces; strip to bare digits
61
+ # so the integer compare and the log line stay portable across GNU/BSD.
62
+ count=$( [ -f "$f" ] && wc -l < "$f" 2>/dev/null | tr -cd '0-9' || echo 0 )
63
+ [ -z "$count" ] && count=0
64
+ if [ "$count" -ge "$MAX" ]; then
65
+ log "backoff: $count fires in 30m, skip (error=$error)"
66
+ exit 0
67
+ fi
68
+ echo "$now" >> "$f"
69
+
70
+ # Gate 4: wait until the API is reachable again, up to PROBE_DEADLINE seconds.
71
+ # No --fail: any HTTP response (incl. 401/404) proves TLS/network connectivity.
72
+ waited=0
73
+ until curl -s -o /dev/null --max-time 3 "$PROBE_URL"; do
74
+ waited=$((waited + 3))
75
+ if [ "$waited" -ge "$PROBE_DEADLINE" ]; then
76
+ log "probe timeout after ${waited}s, skip (error=$error)"
77
+ exit 0
78
+ fi
79
+ sleep 3
80
+ done
81
+ log "probe ok after ${waited}s (error=$error)"
82
+
83
+ # Inject: Escape first to leave any non-input TUI state, then the resume text.
84
+ tmux send-keys -t "$TMUX_PANE" Escape 2>/dev/null
85
+ tmux send-keys -t "$TMUX_PANE" "$RESUME_TEXT" Enter 2>/dev/null
86
+ log "send-keys done (error=$error)"
87
+ exit 0
@@ -180,7 +180,7 @@ Update task.md:
180
180
  - Write `issue_number: {n}` into the frontmatter (replace if it exists; append at the end of the frontmatter otherwise)
181
181
  - Update `updated_at` to the current time (command: `date "+%Y-%m-%d %H:%M:%S%:z"`)
182
182
 
183
- > Do NOT append an Activity Log entry here. The Issue creation event is already captured by the GitHub Issue itself and by the frontmatter `issue_number` field; the Activity Log only records the single `create-task` skill execution anchor (`Task Created`), written by the caller SKILL step 3.
183
+ > Do NOT append an Activity Log entry here. The Issue creation event is already captured by the GitHub Issue itself and by the frontmatter `issue_number` field; the Activity Log only records the single `create-task` skill execution anchor (`Create Task`), written by the caller SKILL step 3.
184
184
 
185
185
  ### 9. Return the Result
186
186
 
@@ -180,7 +180,7 @@ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
180
180
  - 把 `issue_number: {n}` 写入 frontmatter(已存在则替换;不存在则在 frontmatter 末尾追加)
181
181
  - 更新 `updated_at` 为当前时间(命令:`date "+%Y-%m-%d %H:%M:%S%:z"`)
182
182
 
183
- > 不要在此追加 Activity Log 条目。Issue 创建事件已由 GitHub Issue 自身和 frontmatter `issue_number` 承载;Activity Log 仅记录 `create-task` skill 一次执行的整体锚点(`Task Created`),由调用方 SKILL 步骤 3 写入。
183
+ > 不要在此追加 Activity Log 条目。Issue 创建事件已由 GitHub Issue 自身和 frontmatter `issue_number` 承载;Activity Log 仅记录 `create-task` skill 一次执行的整体锚点(`Create Task`),由调用方 SKILL 步骤 3 写入。
184
184
 
185
185
  ### 9. 返回结果
186
186
 
@@ -81,7 +81,10 @@ if [ "$has_triage" = "true" ]; then
81
81
  fi
82
82
  ```
83
83
 
84
- 6. If `has_triage=false`, the target milestone does not exist, or the branch ancestry cannot be determined reliably, keep the original milestone unchanged
84
+ 6. Keep the original milestone unchanged only in the following cases (otherwise narrow per step 5):
85
+ - Trunk mode with no open concrete version under the release line — the `code-task` / `create-pr` `verify_milestone_specific` gate will fail and prompt maintainers to create the missing concrete version
86
+ - Multi-release-line mode when both `git merge-base --is-ancestor` checks are unreliable or the remote refs are missing
87
+ - In any mode, `has_triage=false` (the bot will reconcile later)
85
88
 
86
89
  Suggested concrete-version query:
87
90
 
@@ -81,7 +81,10 @@ if [ "$has_triage" = "true" ]; then
81
81
  fi
82
82
  ```
83
83
 
84
- 6. 如果 `has_triage=false`、目标 milestone 不存在,或无法可靠判断 -> 保持原 milestone 不变
84
+ 6. 仅在以下情况保持原 milestone 不变(其余情形必须按步骤 5 收窄):
85
+ - 主干模式下版本线下没有 open 具体版本 —— `code-task` / `create-pr` 的 `verify_milestone_specific` gate 会 FAIL,提醒维护者补建具体版本
86
+ - 多版本分支模式下 `git merge-base --is-ancestor` 两条判断都不可靠或远程引用缺失
87
+ - 任意模式下 `has_triage=false`(由 bot 后补)
85
88
 
86
89
  具体版本查询建议:
87
90
 
@@ -0,0 +1,59 @@
1
+ # Next-Step Output Rule
2
+
3
+ When a skill renders "Next steps" commands and the "Task info" block in its notify-user step, present the task ID consistently per this rule. Read this file before rendering next steps.
4
+
5
+ ## Placeholder semantics
6
+
7
+ | Placeholder | Meaning | Rendered form |
8
+ |-------------|---------|---------------|
9
+ | `{task-ref}` | Current task **short id** | `#`-prefixed, e.g. `#15`; falls back to the full `TASK-id` when unavailable |
10
+ | `{task-id}` | Current task **full id** | `TASK-YYYYMMDD-HHMMSS` |
11
+
12
+ ## Scope
13
+
14
+ - **Next-step TUI commands** (`/analyze-task`, `/{{project}}:review-code`, `$create-pr`, etc., including commands inside Markdown table cells) → always use `{task-ref}` (short id).
15
+ - **"Task info" / "Task status" structured field lines** → show full id and short id together: `- Task ID: {task-id} (short id {task-ref})`.
16
+ - **Report titles** (`Task {task-id} ... completed`) and **artifact paths** (`.agents/workspace/active/{task-id}/...`) → keep the full `{task-id}` (physical path and archive key, must not change).
17
+
18
+ ## Obtaining the short id (`{task-ref}`)
19
+
20
+ The single source of truth for short ids is the registry `.agents/workspace/active/.short-ids.json` (via `task-short-id.js`). **Never** read the `short_id` field from task.md frontmatter (that field is not authoritative).
21
+
22
+ Once the full `$task_id` is resolved, use the snippet below to look up the short id; it returns `#NN` on hit and falls back to the full `TASK-id` on miss:
23
+
24
+ ```bash
25
+ task_ref=$(node -e '
26
+ const cp=require("child_process");
27
+ const out=cp.execSync("node .agents/scripts/task-short-id.js list",{encoding:"utf8"});
28
+ const ids=(JSON.parse(out).ids)||{};
29
+ const full=process.argv[1];
30
+ const hit=Object.entries(ids).find(([,v])=>v===full);
31
+ process.stdout.write(hit?("#"+hit[0]):full);
32
+ ' "$task_id")
33
+ # Example: $task_id=TASK-20260613-225809 -> task_ref=#15
34
+ ```
35
+
36
+ ## Fallback conditions
37
+
38
+ `{task-ref}` falls back to the full `TASK-id` in these cases (i.e. the registry has no matching short id):
39
+
40
+ - **Unallocated**: very early paths before `create-task` / `import-*` / `restore-task` has allocated a short id.
41
+ - **Released**: after a task is archived by `complete-task` / `cancel-task` / `block-task` / `close-codescan` / `close-dependabot`, its short id is immediately removed from the registry. The terminal/summary lines of these archival skills therefore fall back to the full `TASK-id` naturally, with no special-casing.
42
+
43
+ `restore-task` re-allocates a short id when restoring a task (possibly different from before); the snippet picks up the new short id.
44
+
45
+ ## `#` prefix and shell quoting
46
+
47
+ Short ids are always rendered with a `#` prefix as `#NN`, matching how task.md frontmatter renders `short_id`. `#` starts a comment in bash, so pasting example commands depends on the TUI (both the bare numeric `NN` and `#NN` are accepted by `task-short-id.js resolve`).
48
+
49
+ ## Completion timestamp line (Completed at)
50
+
51
+ Every skill that reads this rule and renders "Next steps / Inform user" output appends a single completion-time line as the **very last line** of its user-facing output, so users scanning across tmux windows can tell at a glance which agent finished most recently:
52
+
53
+ ```text
54
+ Completed at: YYYY-MM-DD HH:mm:ss
55
+ ```
56
+
57
+ - Value command (local timezone, no offset): `date "+%Y-%m-%d %H:%M:%S"`
58
+ - Position: it must be the last line of the entire user-facing output, after all "Next steps" commands. If a scenario has a conditional reminder line after the commands (e.g. the env-blocked reminder), the completion line goes after that reminder.
59
+ - This line is for terminal scanning only; it is never written to any artifact file or Issue/PR comment. The single source of truth for completion time remains the Activity Log in task.md.
@@ -0,0 +1,59 @@
1
+ # 下一步输出规则
2
+
3
+ 各 skill 在「告知用户」步骤渲染「下一步」命令与「任务信息」段时,统一按本规则呈现任务 ID 形态。渲染下一步前先读取本文件。
4
+
5
+ ## 占位符语义
6
+
7
+ | 占位符 | 含义 | 渲染形态 |
8
+ |--------|------|----------|
9
+ | `{task-ref}` | 当前任务**短号** | 带 `#` 前缀,如 `#15`;取不到时回退完整 `TASK-id` |
10
+ | `{task-id}` | 当前任务**完整 ID** | `TASK-YYYYMMDD-HHMMSS` |
11
+
12
+ ## 适用范围
13
+
14
+ - **下一步 TUI 命令**(`/analyze-task`、`/{{project}}:review-code`、`$create-pr` 等,含 Markdown 表格单元格内的命令)→ 一律用 `{task-ref}`(短号)。
15
+ - **「任务信息」/「任务状态」结构化字段行** → 完整 ID 与短号同显:`- 任务 ID:{task-id}(短号 {task-ref})`。
16
+ - **报告标题**(`任务 {task-id} ... 完成`)与**产出文件路径**(`.agents/workspace/active/{task-id}/...`)→ 保持完整 `{task-id}`(物理路径与归档键,不可改)。
17
+
18
+ ## 取短号(`{task-ref}`)
19
+
20
+ 短号唯一真源是注册表 `.agents/workspace/active/.short-ids.json`(经 `task-short-id.js`)。**禁止**读取 task.md frontmatter 的 `short_id` 字段(该字段不可信)。
21
+
22
+ 在已解析出完整 `$task_id` 后,用以下片段反查短号;命中返回 `#NN`,未命中自动回退完整 `TASK-id`:
23
+
24
+ ```bash
25
+ task_ref=$(node -e '
26
+ const cp=require("child_process");
27
+ const out=cp.execSync("node .agents/scripts/task-short-id.js list",{encoding:"utf8"});
28
+ const ids=(JSON.parse(out).ids)||{};
29
+ const full=process.argv[1];
30
+ const hit=Object.entries(ids).find(([,v])=>v===full);
31
+ process.stdout.write(hit?("#"+hit[0]):full);
32
+ ' "$task_id")
33
+ # 示例:$task_id=TASK-20260613-225809 -> task_ref=#15
34
+ ```
35
+
36
+ ## 回退条件
37
+
38
+ `{task-ref}` 在以下情况回退为完整 `TASK-id`(即注册表查不到对应短号):
39
+
40
+ - **未分配**:任务尚未经 `create-task` / `import-*` / `restore-task` 分配短号的极早期路径。
41
+ - **已释放**:任务经 `complete-task` / `cancel-task` / `block-task` / `close-codescan` / `close-dependabot` 归档后,短号立即从注册表移除。这些归档类 skill 的终态/摘要行因此自然回退完整 `TASK-id`,无需特判。
42
+
43
+ `restore-task` 恢复任务时会重新分配短号(可能与历史不同),片段会取到新短号。
44
+
45
+ ## `#` 前缀与 shell 引用
46
+
47
+ 短号统一渲染为带 `#` 前缀的 `#NN`,与 task.md frontmatter 的 `short_id` 渲染一致。`#` 在 bash 中是注释起始符,示例命令若直接粘贴需视 TUI 而定(裸数字 `NN` 与 `#NN` 都被 `task-short-id.js resolve` 接受)。
48
+
49
+ ## 完成时间收尾行(Completed at)
50
+
51
+ 所有读取本规则、并向用户渲染「下一步 / 告知用户」输出的 skill,在面向用户输出的**绝对最后一行**统一追加一行完成时间,便于用户在 tmux 多窗口扫视时一眼判断各 Agent 的完成先后:
52
+
53
+ ```text
54
+ Completed at: YYYY-MM-DD HH:mm:ss
55
+ ```
56
+
57
+ - 取值命令(本地时区、不带偏移):`date "+%Y-%m-%d %H:%M:%S"`
58
+ - 位置:必须是整段面向用户输出的最后一行,排在所有「下一步」命令之后。若某场景在命令之后还有条件性提醒行(如 env-blocked 提醒),收尾行排在该提醒行之后。
59
+ - 该行只用于终端扫视,不写入任何产物文件或 Issue/PR 评论;完成时刻的单一事实源仍是 task.md 的 Activity Log。
@@ -1,27 +1,33 @@
1
1
  # Task short id
2
2
 
3
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.
4
+ `TASK-YYYYMMDD-HHMMSS` with bare numeric `N` (recommended) or `#NN` while a
5
+ task is active.
5
6
 
6
7
  ## Syntax
7
8
 
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.
9
+ - Two equivalent literal forms are accepted:
10
+ - **Bare numeric `N`** (recommended; no shell quoting needed): e.g. `1`, `7`, `42`.
11
+ - **`#`-prefixed `#N` / `#NN`** (also accepted; bash needs `'...'` quoting): e.g. `#1`, `#01`, `#42`.
12
+ - Resolution: drop leading zeros and take the numeric value `n`; if `n == 0`,
13
+ reject (reserved); if `n > 10^shortIdLength - 1`, reject (over capacity);
14
+ otherwise canonicalize to `#${n.padStart(shortIdLength, '0')}` as the
15
+ registry key.
16
+ - With the default `shortIdLength=2`, capacity is `n ∈ [1, 99]`; registry keys
17
+ look like `01`, `07`, `42`.
12
18
  - `#00` (or `#0` when `shortIdLength=1`) is reserved and never allocated; digits
13
19
  only, no letters.
14
- - The plain `TASK-…` form keeps working everywhere; `#NN` is an alias, not the
15
- persisted task id.
20
+ - The plain `TASK-…` form keeps working everywhere; bare numeric / `#NN` is an
21
+ alias, not the persisted task id.
16
22
 
17
23
  ## Lifecycle
18
24
 
19
- | Action | When | Effect on registry & task.md |
25
+ | Action | When | Effect on registry |
20
26
  |------------|-----------------------------------------------------------------------|---------------------------------------------------------------|
21
- | alloc | `create-task`, `import-issue`, `import-codescan`, `import-dependabot` | Assigns lowest free `#NN`; writes `short_id` into task.md. |
27
+ | alloc | `create-task`, `import-issue`, `import-codescan`, `import-dependabot` | Assigns lowest free `#NN` in the registry. |
22
28
  | 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. |
29
+ | release | `complete-task`, `cancel-task`, `block-task`, `close-codescan`, `close-dependabot` | Removes the registry entry. |
30
+ | re-alloc | `restore-task` | Re-allocates a (possibly new) `#NN` in the registry. |
25
31
 
26
32
  Short ids are valid only while a task lives in `.agents/workspace/active/`.
27
33
  Once it is moved to `completed/`, `blocked/`, or `archive/`, the `#NN` slot is
@@ -48,22 +54,23 @@ archiving all active tasks first (the registry key width depends on it).
48
54
  | Entrypoint | Hit | Miss |
49
55
  |-------------------------------------------------------------|----------------------|------------------------------------------------------|
50
56
  | 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`) |
57
+ | `ai sandbox exec <N \| '#N'>` / `ai sandbox create <N \| '#N'>` | resolve to full id, then read `branch` from task.md | **strict error** — no ls-index fallback, no literal-branch fallback; hint the user to pass a short id / `TASK-id` / branch name |
52
58
 
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.
59
+ `list --verify` is strictly read-only: it reports discrepancies between the
60
+ active dir and the registry, but never writes.
55
61
 
56
62
  ## SKILL parameter resolver
57
63
 
58
64
  Any SKILL (alloc / resolve / release / re-alloc lifecycle entry-points) that
59
65
  receives a `{task-id}` argument must follow this contract:
60
66
 
61
- 1. If `{task-id}` starts with `#`:
67
+ 1. If `{task-id}` matches `^[#]?[0-9]+$` (bare numeric `N` or `#`-prefixed `#N`):
62
68
 
63
69
  ```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.
70
+ if [[ "{task-id}" =~ ^[#]?[0-9]+$ ]]; then
71
+ # The script writes the full error message (covering reserved / exceeds
72
+ # shortIdLength capacity / malformed input) to stderr; callers only forward
73
+ # the exit.
67
74
  task_id=$(node .agents/scripts/task-short-id.js resolve "{task-id}") || exit 1
68
75
  else
69
76
  task_id="{task-id}"
@@ -77,14 +84,8 @@ fi
77
84
 
78
85
  ## Storage
79
86
 
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**:
87
+ Short ids are pure local state, persisted only in the registry
88
+ `.agents/workspace/active/.short-ids.json`; task.md does not hold the short id:
88
89
 
89
90
  - Path: `<repo-root>/.agents/workspace/active/.short-ids.json`
90
91
  - Schema: `{ "version": 1, "ids": { "01": "TASK-20260609-192644", "02": "TASK-…" } }`
@@ -92,50 +93,41 @@ The short id system persists state in two places that stay in sync at rest:
92
93
  full `TASK-…` task ids.
93
94
  - Automatically git-ignored (the whole active workspace is ignored; no new
94
95
  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).
96
+ - Created on demand by the first `alloc`; an absent file is treated as an empty
97
+ registry.
98
+ - Short ids are assigned only by an explicit `alloc` (`create-task` /
99
+ `import-*` / `restore-task`); `resolve` / `list` / `release` never allocate —
100
+ they only clean up stale entries pointing at non-active tasks.
102
101
  - 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.
102
+ registry entry is deleted immediately and the short id may be reused; archived
103
+ tasks are referenced by their full `TASK-…` id.
104
+
105
+ `resolve(<N|'#N'>)` workflow: validate arg matches `^[#]?[0-9]+$`
106
+ strip leading zeros and take the numeric value `n`; classify as reserved
107
+ (`n == 0`) / over capacity (`n > 10^shortIdLength - 1`) / normal → ③ on
108
+ normal, use `n.padStart(shortIdLength, '0')` as the registry `ids` key
109
+ → ④ return full task id on hit; on miss, exit 1 with the `list --verify`
110
+ repair hint.
114
111
 
115
112
  ## Error scenarios
116
113
 
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.
114
+ - **Short id not found**: the registry has no entry for the resolved key.
115
+ Either the task was archived (release freed the slot) or the input is
116
+ wrong. Exit code 1.
119
117
  - **Registry corruption** (duplicate registry entries for the same task id, or
120
118
  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.
119
+ - **Reserved key**: the resolved `n == 0` (inputs like `0`, `#0`, `#00`). Exit code 1.
120
+ - **Over capacity**: the resolved `n > 10^shortIdLength - 1` (e.g. `100` or
121
+ `#100` when `shortIdLength=2`). Exit code 1.
122
+ - **Parameter format error**: input matches neither `^[#]?[0-9]+$` nor a
123
+ `TASK-id` (e.g. `#abc`, `#`, `5.5`). Exit code 1.
123
124
 
124
125
  ## Cross-TUI quoting
125
126
 
126
- Bash treats `#` as a comment marker. Always single-quote: `ai sandbox exec '#03' 'npm test'`.
127
+ Bare numeric `N` is safe in every shell and TUI without quoting (recommended):
128
+ `ai sandbox exec 11 'npm test'`, `/review-analysis 11`.
129
+
130
+ The `#N` / `#NN` form is also accepted; but bash treats `#` as a comment
131
+ marker, so it must be single-quoted: `ai sandbox exec '#03' 'npm test'`.
127
132
  Claude Code / Codex / Gemini CLI / OpenCode all forward `#NN` to SKILL
128
133
  `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`).