@fitlab-ai/agent-infra 0.7.1 → 0.7.3

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 (165) hide show
  1. package/README.md +7 -1
  2. package/README.zh-CN.md +9 -3
  3. package/bin/cli.ts +11 -0
  4. package/dist/bin/cli.js +12 -0
  5. package/dist/lib/defaults.json +0 -1
  6. package/dist/lib/init.js +0 -3
  7. package/dist/lib/sandbox/commands/create.js +10 -2
  8. package/dist/lib/sandbox/commands/enter.js +17 -18
  9. package/dist/lib/sandbox/commands/list-running.js +56 -32
  10. package/dist/lib/sandbox/commands/ls.js +27 -24
  11. package/dist/lib/sandbox/commands/start.js +36 -0
  12. package/dist/lib/sandbox/index.js +15 -3
  13. package/dist/lib/sandbox/task-resolver.js +1 -1
  14. package/dist/lib/sandbox/tools.js +1 -1
  15. package/dist/lib/table.js +38 -0
  16. package/dist/lib/task/commands/ls.js +122 -0
  17. package/dist/lib/task/commands/show.js +135 -0
  18. package/dist/lib/task/frontmatter.js +32 -0
  19. package/dist/lib/task/index.js +41 -0
  20. package/dist/lib/task/short-id.js +90 -0
  21. package/dist/lib/update.js +25 -8
  22. package/lib/defaults.json +0 -1
  23. package/lib/init.ts +0 -10
  24. package/lib/sandbox/commands/create.ts +11 -2
  25. package/lib/sandbox/commands/enter.ts +40 -20
  26. package/lib/sandbox/commands/list-running.ts +65 -37
  27. package/lib/sandbox/commands/ls.ts +35 -27
  28. package/lib/sandbox/commands/start.ts +61 -0
  29. package/lib/sandbox/index.ts +15 -3
  30. package/lib/sandbox/task-resolver.ts +1 -1
  31. package/lib/sandbox/tools.ts +1 -1
  32. package/lib/table.ts +44 -0
  33. package/lib/task/commands/ls.ts +138 -0
  34. package/lib/task/commands/show.ts +139 -0
  35. package/lib/task/frontmatter.ts +30 -0
  36. package/lib/task/index.ts +44 -0
  37. package/lib/task/short-id.ts +107 -0
  38. package/lib/update.ts +28 -10
  39. package/package.json +1 -1
  40. package/templates/.agents/hooks/auto-resume.sh +104 -0
  41. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  42. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  43. package/templates/.agents/rules/milestone-inference.github.en.md +4 -1
  44. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -1
  45. package/templates/.agents/rules/next-step-output.en.md +62 -0
  46. package/templates/.agents/rules/next-step-output.zh-CN.md +62 -0
  47. package/templates/.agents/rules/pr-checks-commands.en.md +5 -0
  48. package/templates/.agents/rules/pr-checks-commands.github.en.md +62 -0
  49. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +62 -0
  50. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +5 -0
  51. package/templates/.agents/rules/pr-sync.github.en.md +7 -0
  52. package/templates/.agents/rules/pr-sync.github.zh-CN.md +7 -0
  53. package/templates/.agents/rules/task-short-id.en.md +54 -62
  54. package/templates/.agents/rules/task-short-id.zh-CN.md +35 -54
  55. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
  56. package/templates/.agents/scripts/task-short-id.js +32 -189
  57. package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -12
  58. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -12
  59. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  60. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  61. package/templates/.agents/skills/block-task/SKILL.en.md +13 -6
  62. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +13 -6
  63. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  64. package/templates/.agents/skills/cancel-task/SKILL.en.md +13 -6
  65. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +13 -6
  66. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  67. package/templates/.agents/skills/check-task/SKILL.en.md +12 -10
  68. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +12 -10
  69. package/templates/.agents/skills/close-codescan/SKILL.en.md +13 -6
  70. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +13 -6
  71. package/templates/.agents/skills/close-dependabot/SKILL.en.md +13 -6
  72. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +13 -6
  73. package/templates/.agents/skills/code-task/SKILL.en.md +10 -6
  74. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -6
  75. package/templates/.agents/skills/code-task/config/verify.en.json +2 -1
  76. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +2 -1
  77. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +10 -5
  78. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +10 -5
  79. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  80. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  81. package/templates/.agents/skills/code-task/reference/report-template.en.md +8 -0
  82. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +8 -0
  83. package/templates/.agents/skills/commit/SKILL.en.md +3 -4
  84. package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
  85. package/templates/.agents/skills/commit/reference/task-status-update.en.md +37 -29
  86. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +37 -29
  87. package/templates/.agents/skills/complete-task/SKILL.en.md +41 -4
  88. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +41 -4
  89. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -1
  90. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -1
  91. package/templates/.agents/skills/create-pr/SKILL.en.md +20 -11
  92. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +20 -11
  93. package/templates/.agents/skills/create-pr/config/verify.json +2 -1
  94. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -1
  95. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -1
  96. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  97. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  98. package/templates/.agents/skills/create-task/SKILL.en.md +17 -17
  99. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +17 -17
  100. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  101. package/templates/.agents/skills/import-codescan/SKILL.en.md +8 -8
  102. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +8 -8
  103. package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
  104. package/templates/.agents/skills/import-dependabot/SKILL.en.md +8 -8
  105. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +8 -8
  106. package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
  107. package/templates/.agents/skills/import-issue/SKILL.en.md +7 -7
  108. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +7 -7
  109. package/templates/.agents/skills/plan-task/SKILL.en.md +10 -12
  110. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -12
  111. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  112. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  113. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  114. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  115. package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -2
  116. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -2
  117. package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -2
  118. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -2
  119. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +15 -15
  120. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +15 -15
  121. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -1
  122. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +7 -1
  123. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +2 -0
  124. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +2 -0
  125. package/templates/.agents/skills/review-code/SKILL.en.md +5 -2
  126. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -2
  127. package/templates/.agents/skills/review-code/config/verify.en.json +3 -2
  128. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -2
  129. package/templates/.agents/skills/review-code/reference/output-templates.en.md +9 -9
  130. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +9 -9
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +7 -1
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +7 -1
  133. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -0
  134. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -0
  135. package/templates/.agents/skills/review-plan/SKILL.en.md +4 -2
  136. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -2
  137. package/templates/.agents/skills/review-plan/config/verify.en.json +3 -2
  138. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -2
  139. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +15 -15
  140. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +15 -15
  141. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -1
  142. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -1
  143. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +2 -0
  144. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +2 -0
  145. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +0 -1
  146. package/templates/.agents/skills/watch-pr/SKILL.en.md +131 -0
  147. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +131 -0
  148. package/templates/.agents/skills/watch-pr/config/verify.json +22 -0
  149. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +43 -0
  150. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +43 -0
  151. package/templates/.agents/templates/task.en.md +1 -1
  152. package/templates/.agents/templates/task.zh-CN.md +1 -1
  153. package/templates/.agents/workflows/bug-fix.en.yaml +7 -5
  154. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +6 -5
  155. package/templates/.agents/workflows/feature-development.en.yaml +7 -5
  156. package/templates/.agents/workflows/feature-development.zh-CN.yaml +6 -5
  157. package/templates/.agents/workflows/refactoring.en.yaml +7 -5
  158. package/templates/.agents/workflows/refactoring.zh-CN.yaml +6 -5
  159. package/templates/.claude/commands/watch-pr.en.md +8 -0
  160. package/templates/.claude/commands/watch-pr.zh-CN.md +8 -0
  161. package/templates/.claude/settings.json +11 -0
  162. package/templates/.gemini/commands/_project_/watch-pr.en.toml +8 -0
  163. package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +8 -0
  164. package/templates/.opencode/commands/watch-pr.en.md +11 -0
  165. package/templates/.opencode/commands/watch-pr.zh-CN.md +11 -0
@@ -0,0 +1,122 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { formatTable } from "../../table.js";
5
+ import { parseTaskFrontmatter, extractTitle } from "../frontmatter.js";
6
+ import { loadShortIdByTaskId } from "../short-id.js";
7
+ const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
8
+
9
+ Lists tasks under .agents/workspace/. Defaults to active tasks only.
10
+ --all Include active + blocked + completed (excludes archive)
11
+ --blocked Only blocked tasks
12
+ --completed Only completed tasks
13
+
14
+ Columns: # (display-only row number) / SHORT (task short id, usable as an argument) / type / status / current_step / branch / title
15
+ `;
16
+ const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
17
+ const TABLE_HEADERS = ['#', 'SHORT', 'TYPE', 'STATUS', 'STEP', 'BRANCH', 'TITLE'];
18
+ function detectRepoRoot() {
19
+ try {
20
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], {
21
+ encoding: 'utf8',
22
+ stdio: ['pipe', 'pipe', 'pipe']
23
+ }).trim();
24
+ }
25
+ catch {
26
+ throw new Error('ai task: current directory is not inside a git repository');
27
+ }
28
+ }
29
+ function parseSelection(args) {
30
+ const positional = args.filter((a) => !a.startsWith('--'));
31
+ if (positional.length > 0) {
32
+ return {
33
+ ok: false,
34
+ message: `ai task ls: unexpected positional argument(s): ${positional.join(' ')}`
35
+ };
36
+ }
37
+ const flags = args.filter((a) => a.startsWith('--'));
38
+ if (flags.length === 0)
39
+ return { ok: true, selection: ['active'] };
40
+ if (flags.length > 1) {
41
+ return {
42
+ ok: false,
43
+ message: 'ai task ls: pass at most one of --all / --blocked / --completed'
44
+ };
45
+ }
46
+ switch (flags[0]) {
47
+ case '--all':
48
+ return { ok: true, selection: ['active', 'blocked', 'completed'] };
49
+ case '--blocked':
50
+ return { ok: true, selection: ['blocked'] };
51
+ case '--completed':
52
+ return { ok: true, selection: ['completed'] };
53
+ default:
54
+ return { ok: false, message: `ai task ls: unknown flag: ${flags[0]}` };
55
+ }
56
+ }
57
+ function collectTasks(repoRoot, state) {
58
+ const dir = path.join(repoRoot, '.agents', 'workspace', state);
59
+ if (!fs.existsSync(dir))
60
+ return [];
61
+ // Short ids live only in the registry and only for active tasks; archived
62
+ // (blocked/completed) tasks have released their short id and render '-'.
63
+ const shortIdByTaskId = state === 'active' ? loadShortIdByTaskId(repoRoot) : new Map();
64
+ const rows = [];
65
+ for (const entry of fs.readdirSync(dir).sort()) {
66
+ if (!TASK_ID_RE.test(entry))
67
+ continue;
68
+ const taskMdPath = path.join(dir, entry, 'task.md');
69
+ if (!fs.existsSync(taskMdPath))
70
+ continue;
71
+ const content = fs.readFileSync(taskMdPath, 'utf8');
72
+ const fm = parseTaskFrontmatter(content);
73
+ const title = extractTitle(content);
74
+ const shortId = shortIdByTaskId.get(entry) ?? '-';
75
+ rows.push({
76
+ shortId,
77
+ type: fm.type ?? '-',
78
+ status: fm.status ?? state,
79
+ step: fm.current_step ?? '-',
80
+ branch: fm.branch ?? '-',
81
+ title: title || fm.id || entry
82
+ });
83
+ }
84
+ return rows;
85
+ }
86
+ function ls(args = []) {
87
+ if (args[0] === '--help' || args[0] === '-h') {
88
+ process.stdout.write(USAGE);
89
+ return;
90
+ }
91
+ const result = parseSelection(args);
92
+ if (!result.ok) {
93
+ process.stderr.write(`${result.message}\n`);
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+ const { selection } = result;
98
+ const repoRoot = detectRepoRoot();
99
+ const rows = [];
100
+ for (const state of selection) {
101
+ rows.push(...collectTasks(repoRoot, state));
102
+ }
103
+ if (rows.length === 0) {
104
+ process.stdout.write(`No tasks under .agents/workspace/${selection.join('|')}\n`);
105
+ return;
106
+ }
107
+ const tableRows = rows.map((r, i) => [
108
+ String(i + 1),
109
+ r.shortId,
110
+ r.type,
111
+ r.status,
112
+ r.step,
113
+ r.branch,
114
+ r.title
115
+ ]);
116
+ for (const line of formatTable(TABLE_HEADERS, tableRows, { zebra: Boolean(process.stdout.isTTY) })) {
117
+ process.stdout.write(`${line}\n`);
118
+ }
119
+ process.stdout.write(`Total: ${rows.length} tasks\n`);
120
+ }
121
+ export { ls };
122
+ //# sourceMappingURL=ls.js.map
@@ -0,0 +1,135 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync, spawnSync } from 'node:child_process';
4
+ import { normalizeShortIdInput } from "../short-id.js";
5
+ const USAGE = `Usage: ai task show <N | #N | TASK-id>
6
+
7
+ Prints the task.md content for the matching task.
8
+ N (bare numeric) Recommended; resolves the active short id via the registry.
9
+ '#N' Compatibility form for old commands.
10
+ TASK-YYYYMMDD-HHMMSS Locates a task in active / blocked / completed / archive.
11
+ `;
12
+ const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
13
+ // Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
14
+ // Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
15
+ const FLAT_WORKSPACE_DIRS = ['active', 'blocked', 'completed'];
16
+ function detectRepoRoot() {
17
+ try {
18
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], {
19
+ encoding: 'utf8',
20
+ stdio: ['pipe', 'pipe', 'pipe']
21
+ }).trim();
22
+ }
23
+ catch {
24
+ throw new Error('ai task: current directory is not inside a git repository');
25
+ }
26
+ }
27
+ function readShortIdLength(repoRoot) {
28
+ try {
29
+ const cfg = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
30
+ const v = cfg?.task?.shortIdLength;
31
+ if (typeof v === 'number' && Number.isFinite(v) && v >= 1)
32
+ return v;
33
+ }
34
+ catch {
35
+ // fall through to default
36
+ }
37
+ return 2;
38
+ }
39
+ function resolveShortIdToTaskId(arg, repoRoot) {
40
+ const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
41
+ if (!fs.existsSync(scriptPath)) {
42
+ throw new Error(`task-short-id.js not found at ${scriptPath}`);
43
+ }
44
+ const result = spawnSync('node', [scriptPath, 'resolve', arg], {
45
+ encoding: 'utf8',
46
+ cwd: repoRoot
47
+ });
48
+ if (result.status !== 0) {
49
+ throw new Error((result.stderr || '').trim() || `failed to resolve '${arg}'`);
50
+ }
51
+ return result.stdout.trim();
52
+ }
53
+ function listSortedNumeric(dir, width) {
54
+ if (!fs.existsSync(dir))
55
+ return [];
56
+ const pattern = new RegExp(`^\\d{${width}}$`);
57
+ return fs
58
+ .readdirSync(dir)
59
+ .filter((entry) => pattern.test(entry))
60
+ .sort()
61
+ .reverse();
62
+ }
63
+ function findInArchive(repoRoot, taskId) {
64
+ // archive-tasks SKILL writes to .agents/workspace/archive/YYYY/MM/DD/{taskId}/task.md
65
+ // where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
66
+ // the task id's creation date. So we cannot derive the path from taskId alone;
67
+ // walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
68
+ const archiveDir = path.join(repoRoot, '.agents', 'workspace', 'archive');
69
+ for (const year of listSortedNumeric(archiveDir, 4)) {
70
+ const yearDir = path.join(archiveDir, year);
71
+ for (const month of listSortedNumeric(yearDir, 2)) {
72
+ const monthDir = path.join(yearDir, month);
73
+ for (const day of listSortedNumeric(monthDir, 2)) {
74
+ const candidate = path.join(monthDir, day, taskId, 'task.md');
75
+ if (fs.existsSync(candidate))
76
+ return candidate;
77
+ }
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function findTaskMd(repoRoot, taskId) {
83
+ for (const sub of FLAT_WORKSPACE_DIRS) {
84
+ const candidate = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
85
+ if (fs.existsSync(candidate))
86
+ return candidate;
87
+ }
88
+ return findInArchive(repoRoot, taskId);
89
+ }
90
+ function show(args = []) {
91
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
92
+ process.stdout.write(USAGE);
93
+ if (args.length === 0)
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+ const repoRoot = detectRepoRoot();
98
+ const arg = args[0];
99
+ let taskId;
100
+ if (TASK_ID_RE.test(arg)) {
101
+ taskId = arg;
102
+ }
103
+ else {
104
+ const shortIdLength = readShortIdLength(repoRoot);
105
+ const normalized = normalizeShortIdInput(arg, { shortIdLength });
106
+ if (normalized.kind === 'error') {
107
+ process.stderr.write(`ai task show: ${normalized.message}\n`);
108
+ process.exitCode = 1;
109
+ return;
110
+ }
111
+ if (normalized.kind === 'pass') {
112
+ process.stderr.write(`ai task show: '${arg}' is not a valid short id or TASK-id; ` +
113
+ `expected bare digits, '#N', or 'TASK-YYYYMMDD-HHMMSS'\n`);
114
+ process.exitCode = 1;
115
+ return;
116
+ }
117
+ try {
118
+ taskId = resolveShortIdToTaskId(normalized.value, repoRoot);
119
+ }
120
+ catch (e) {
121
+ process.stderr.write(`ai task show: ${e.message}\n`);
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ }
126
+ const taskMdPath = findTaskMd(repoRoot, taskId);
127
+ if (!taskMdPath) {
128
+ process.stderr.write(`ai task show: task ${taskId} not found in active / blocked / completed / archive\n`);
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+ process.stdout.write(fs.readFileSync(taskMdPath, 'utf8'));
133
+ }
134
+ export { show };
135
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1,32 @@
1
+ function parseTaskFrontmatter(content) {
2
+ const result = {};
3
+ if (!content.startsWith('---'))
4
+ return result;
5
+ const end = content.indexOf('\n---', 3);
6
+ if (end === -1)
7
+ 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())
12
+ continue;
13
+ const colon = line.indexOf(':');
14
+ if (colon === -1)
15
+ continue;
16
+ const key = line.slice(0, colon).trim();
17
+ const value = line.slice(colon + 1).trim();
18
+ if (key)
19
+ result[key] = value;
20
+ }
21
+ return result;
22
+ }
23
+ function extractTitle(content) {
24
+ for (const line of content.split('\n')) {
25
+ const m = /^#\s+(?:任务[::]?\s*)?(.+)$/.exec(line.trim());
26
+ if (m && m[1])
27
+ return m[1].trim();
28
+ }
29
+ return '';
30
+ }
31
+ export { parseTaskFrontmatter, extractTitle };
32
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1,41 @@
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
+ export async function runTask(args) {
14
+ const [subcommand, ...rest] = args;
15
+ if (!subcommand) {
16
+ process.stdout.write(`${USAGE}\n`);
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
21
+ process.stdout.write(`${USAGE}\n`);
22
+ return;
23
+ }
24
+ switch (subcommand) {
25
+ case 'ls': {
26
+ const { ls } = await import("./commands/ls.js");
27
+ ls(rest);
28
+ break;
29
+ }
30
+ case 'show': {
31
+ const { show } = await import("./commands/show.js");
32
+ show(rest);
33
+ break;
34
+ }
35
+ default:
36
+ process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
37
+ process.stdout.write(`${USAGE}\n`);
38
+ process.exitCode = 1;
39
+ }
40
+ }
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,90 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const REGISTRY_NAME = '.short-ids.json';
4
+ function normalizeShortIdInput(input, opts) {
5
+ const L = opts.shortIdLength;
6
+ const m = /^#?(\d+)$/.exec(input);
7
+ if (!m) {
8
+ return { kind: 'pass', value: input };
9
+ }
10
+ const n = Number(m[1]);
11
+ if (n === 0) {
12
+ return {
13
+ kind: 'error',
14
+ message: `short id '${input}' is invalid (#${'0'.repeat(L)} is reserved)`
15
+ };
16
+ }
17
+ const max = Math.pow(10, L) - 1;
18
+ if (n > max) {
19
+ return {
20
+ kind: 'error',
21
+ message: `short id ${n} exceeds shortIdLength=${L} capacity (max=${max}); archive tasks or raise task.shortIdLength in .agents/.airc.json`
22
+ };
23
+ }
24
+ return { kind: 'shortId', value: `#${String(n).padStart(L, '0')}` };
25
+ }
26
+ function readRegistry(repoRoot) {
27
+ const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', REGISTRY_NAME);
28
+ if (!fs.existsSync(registryPath))
29
+ return null;
30
+ try {
31
+ const raw = fs.readFileSync(registryPath, 'utf8');
32
+ const data = JSON.parse(raw);
33
+ if (!data || typeof data !== 'object' || !data.ids)
34
+ return null;
35
+ return data;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ function readBranchFromTaskMd(repoRoot, taskId) {
42
+ const taskMdPath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
43
+ if (!fs.existsSync(taskMdPath))
44
+ return null;
45
+ const content = fs.readFileSync(taskMdPath, 'utf8');
46
+ const m = content.match(/^branch:\s*(.+)$/m);
47
+ if (!m || !m[1])
48
+ return null;
49
+ return m[1].trim().replace(/^(["'])(.*)\1$/, '$2');
50
+ }
51
+ function loadShortIdByTaskId(repoRoot) {
52
+ const registry = readRegistry(repoRoot);
53
+ const map = new Map();
54
+ if (!registry)
55
+ return map;
56
+ for (const [key, taskId] of Object.entries(registry.ids)) {
57
+ map.set(taskId, `#${key}`);
58
+ }
59
+ return map;
60
+ }
61
+ /**
62
+ * Resolve a branch to its active-task short id (`#NN`), or `null` when no
63
+ * active task is bound to that branch.
64
+ *
65
+ * Two-state semantics: this only consults the active registry
66
+ * (`active/.short-ids.json`) plus each `active/{taskId}/task.md`. Tasks moved
67
+ * to completed/blocked/cancelled/archive have already released their short id,
68
+ * so their branches return `null` — in `ai sandbox ls` that surfaces as `-`,
69
+ * meaning the sandbox is free to remove.
70
+ */
71
+ function lookupShortIdByBranch(branch, repoRoot, _opts) {
72
+ const registry = readRegistry(repoRoot);
73
+ if (!registry)
74
+ return null;
75
+ const matches = [];
76
+ for (const [key, taskId] of Object.entries(registry.ids)) {
77
+ const taskBranch = readBranchFromTaskMd(repoRoot, taskId);
78
+ if (taskBranch && taskBranch === branch) {
79
+ matches.push(`#${key}`);
80
+ }
81
+ }
82
+ if (matches.length === 0)
83
+ return null;
84
+ if (matches.length > 1) {
85
+ process.stderr.write(`Warning: branch '${branch}' is bound to multiple active tasks: ${matches.join(', ')}; using ${matches[0]}\n`);
86
+ }
87
+ return matches[0];
88
+ }
89
+ export { normalizeShortIdInput, lookupShortIdByBranch, loadShortIdByTaskId };
90
+ //# sourceMappingURL=short-id.js.map
@@ -7,6 +7,24 @@ import { isPathOwnedByDisabledTUI, resolveEnabledTUIs } from "./builtin-tuis.js"
7
7
  const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
8
8
  const CONFIG_DIR = '.agents';
9
9
  const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
10
+ // One-time migration of the legacy project-level PR switch to the three-state
11
+ // `prFlow` preference. `true` (the old default / "PR flow on") maps to the
12
+ // strong constraint `required`; `false` maps to `disabled`. A missing or
13
+ // already-migrated config is left untouched (idempotent). Returns the new
14
+ // prFlow value when a migration happened, otherwise null.
15
+ function migratePrFlow(config) {
16
+ if (config.requiresPullRequest === true) {
17
+ delete config.requiresPullRequest;
18
+ config.prFlow = 'required';
19
+ return 'required';
20
+ }
21
+ if (config.requiresPullRequest === false) {
22
+ delete config.requiresPullRequest;
23
+ config.prFlow = 'disabled';
24
+ return 'disabled';
25
+ }
26
+ return null;
27
+ }
10
28
  function isPathOwnedByOtherPlatform(relativePath, platformType) {
11
29
  const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
12
30
  if (!top.startsWith('.'))
@@ -134,7 +152,7 @@ async function cmdUpdate() {
134
152
  const sandboxAdded = !config.sandbox;
135
153
  const taskAdded = !config.task;
136
154
  const labelsAdded = !config.labels;
137
- const requiresPullRequestAdded = config.requiresPullRequest === undefined;
155
+ const prFlowMigrated = migratePrFlow(config);
138
156
  let configChanged = changed;
139
157
  if (platformAdded) {
140
158
  config.platform = structuredClone(defaults.platform);
@@ -152,8 +170,7 @@ async function cmdUpdate() {
152
170
  config.labels = structuredClone(defaults.labels);
153
171
  configChanged = true;
154
172
  }
155
- if (requiresPullRequestAdded) {
156
- config.requiresPullRequest = defaults.requiresPullRequest;
173
+ if (prFlowMigrated) {
157
174
  configChanged = true;
158
175
  }
159
176
  if (configChanged) {
@@ -167,7 +184,7 @@ async function cmdUpdate() {
167
184
  ok(` merged: ${entry}`);
168
185
  }
169
186
  }
170
- else if (platformAdded || sandboxAdded || taskAdded || labelsAdded || requiresPullRequestAdded) {
187
+ else if (platformAdded || sandboxAdded || taskAdded || labelsAdded || prFlowMigrated) {
171
188
  if (platformAdded) {
172
189
  info(`Default platform config added to ${CONFIG_PATH}.`);
173
190
  }
@@ -180,8 +197,8 @@ async function cmdUpdate() {
180
197
  if (labelsAdded) {
181
198
  info(`Default labels.in config added to ${CONFIG_PATH}.`);
182
199
  }
183
- if (requiresPullRequestAdded) {
184
- info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
200
+ if (prFlowMigrated) {
201
+ info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
185
202
  }
186
203
  }
187
204
  else {
@@ -199,8 +216,8 @@ async function cmdUpdate() {
199
216
  if (hasNewEntries && platformAdded) {
200
217
  info(`Default platform config added to ${CONFIG_PATH}.`);
201
218
  }
202
- if (hasNewEntries && requiresPullRequestAdded) {
203
- info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
219
+ if (hasNewEntries && prFlowMigrated) {
220
+ info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
204
221
  }
205
222
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
206
223
  ok(`Updated ${CONFIG_PATH}`);
package/lib/defaults.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "platform": {
3
3
  "type": "github"
4
4
  },
5
- "requiresPullRequest": true,
6
5
  "sandbox": {
7
6
  "engine": null,
8
7
  "runtimes": [
package/lib/init.ts CHANGED
@@ -32,7 +32,6 @@ type Defaults = {
32
32
  sandbox: Record<string, unknown>;
33
33
  task: { shortIdLength: number };
34
34
  labels: Record<string, unknown>;
35
- requiresPullRequest: boolean;
36
35
  };
37
36
 
38
37
  type AgentConfig = {
@@ -40,7 +39,6 @@ type AgentConfig = {
40
39
  org: string;
41
40
  language: string;
42
41
  platform: { type: string };
43
- requiresPullRequest: boolean;
44
42
  templateVersion: string;
45
43
  sandbox: Record<string, unknown>;
46
44
  task: { shortIdLength: number };
@@ -224,13 +222,6 @@ async function cmdInit(): Promise<void> {
224
222
  );
225
223
  }
226
224
 
227
- const requiresPRChoice = await select(
228
- 'Require Pull Request flow?',
229
- ['yes', 'no'],
230
- 'yes'
231
- );
232
- const requiresPullRequest = requiresPRChoice !== 'no';
233
-
234
225
  let enabledTUIs: string[];
235
226
  try {
236
227
  enabledTUIs = await multiSelect(
@@ -324,7 +315,6 @@ async function cmdInit(): Promise<void> {
324
315
  org: orgName,
325
316
  language,
326
317
  platform: { type: platformType },
327
- requiresPullRequest,
328
318
  templateVersion: VERSION,
329
319
  sandbox: structuredClone(defaults.sandbox),
330
320
  task: structuredClone(defaults.task),
@@ -868,13 +868,22 @@ export function ensureCodexModelInheritance(toolDir: string, hostHomeDir?: strin
868
868
  }
869
869
  }
870
870
 
871
+ const inheritSpecs: Array<readonly [string, 'string' | 'number']> = [
872
+ ['model', 'string'],
873
+ ['model_reasoning_effort', 'string'],
874
+ ['model_auto_compact_token_limit', 'number']
875
+ ];
876
+
871
877
  let changed = false;
872
- for (const key of ['model', 'model_reasoning_effort']) {
878
+ for (const [key, type] of inheritSpecs) {
873
879
  if (Object.hasOwn(sandboxParsed, key)) {
874
880
  continue;
875
881
  }
876
882
  const value = hostParsed[key];
877
- if (typeof value !== 'string' || value === '') {
883
+ if (type === 'string' && (typeof value !== 'string' || value === '')) {
884
+ continue;
885
+ }
886
+ if (type === 'number' && (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
878
887
  continue;
879
888
  }
880
889
  sandboxParsed[key] = value;
@@ -1,5 +1,10 @@
1
1
  import { loadConfig } from '../config.ts';
2
- import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.ts';
2
+ import {
3
+ assertValidBranchName,
4
+ containerNameCandidates,
5
+ sandboxBranchLabel,
6
+ sandboxLabel
7
+ } from '../constants.ts';
3
8
  import { detectEngine } from '../engine.ts';
4
9
  import {
5
10
  formatCredentialWarnings,
@@ -8,17 +13,23 @@ import {
8
13
  redactCommandError,
9
14
  validateClaudeCredentialsEnvOverride
10
15
  } from '../credentials.ts';
11
- import { runInteractiveEngine, runSafeEngine } from '../shell.ts';
12
- import { resolveTaskBranch } from '../task-resolver.ts';
16
+ import { runInteractiveEngine } from '../shell.ts';
13
17
  import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
14
18
  import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.ts';
15
19
  import { detectHostTimezone } from '../host-timezone.ts';
16
- import { fetchSandboxRows, isTaskShortRef, resolveTaskShortRef } from './list-running.ts';
17
-
18
- const USAGE = `Usage: ai sandbox exec <branch | TASK-id | '#N'> [cmd...]
19
-
20
- '#N' references the N-th running sandbox in 'ai sandbox ls' order (1-based).
21
- Quote it as '#N' to avoid shell '#' comment handling.`;
20
+ import {
21
+ fetchSandboxRows,
22
+ resolveBranchArg,
23
+ selectSandboxContainer,
24
+ startSandboxContainer
25
+ } from './list-running.ts';
26
+
27
+ const USAGE = `Usage: ai sandbox exec <branch | TASK-id | N | '#N'> [cmd...]
28
+
29
+ N (bare) and '#N' both reference the same active task short id from
30
+ .agents/workspace/active/.short-ids.json. They resolve only via that
31
+ registry — they do not reference a container's row position in
32
+ 'ai sandbox ls' output.`;
22
33
  const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
23
34
 
24
35
  // Terminal-detection variables that interactive TUIs (e.g. claude-code)
@@ -120,20 +131,29 @@ export async function enter(args: string[]): Promise<number> {
120
131
  validateClaudeCredentialsEnvOverride();
121
132
  const engine = detectEngine(config);
122
133
  const [firstArg = '', ...cmd] = args;
123
- let branch: string;
124
- if (isTaskShortRef(firstArg)) {
125
- const { running } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
126
- branch = resolveTaskShortRef(firstArg, { running, repoRoot: config.repoRoot });
127
- } else {
128
- branch = resolveTaskBranch(firstArg, config.repoRoot);
129
- }
134
+ const branch = resolveBranchArg(firstArg, { repoRoot: config.repoRoot });
130
135
  assertValidBranchName(branch);
131
- const running = runSafeEngine(engine, 'docker', ['ps', '--format', '{{.Names}}']).split('\n');
132
- const container = containerNameCandidates(config, branch).find((name) => running.includes(name));
133
136
 
134
- if (!container) {
135
- throw new Error(`No running sandbox found for branch '${branch}'`);
137
+ const { running, nonRunning } = fetchSandboxRows(
138
+ engine,
139
+ sandboxLabel(config),
140
+ sandboxBranchLabel(config)
141
+ );
142
+ const found = selectSandboxContainer(
143
+ [...running, ...nonRunning],
144
+ containerNameCandidates(config, branch)
145
+ );
146
+
147
+ if (!found) {
148
+ throw new Error(
149
+ `No sandbox found for branch '${branch}'. Run 'ai sandbox create ${branch}' to create one.`
150
+ );
151
+ }
152
+ if (!found.running) {
153
+ process.stderr.write(`Sandbox '${found.name}' is stopped; starting it...\n`);
154
+ startSandboxContainer(engine, found.name);
136
155
  }
156
+ const container = found.name;
137
157
 
138
158
  if (config.tools.includes('claude-code')) {
139
159
  try {