@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.
- package/README.md +7 -1
- package/README.zh-CN.md +9 -3
- package/bin/cli.ts +11 -0
- package/dist/bin/cli.js +12 -0
- package/dist/lib/defaults.json +0 -1
- package/dist/lib/init.js +0 -3
- package/dist/lib/sandbox/commands/create.js +10 -2
- package/dist/lib/sandbox/commands/enter.js +17 -18
- package/dist/lib/sandbox/commands/list-running.js +56 -32
- package/dist/lib/sandbox/commands/ls.js +27 -24
- package/dist/lib/sandbox/commands/start.js +36 -0
- package/dist/lib/sandbox/index.js +15 -3
- package/dist/lib/sandbox/task-resolver.js +1 -1
- package/dist/lib/sandbox/tools.js +1 -1
- package/dist/lib/table.js +38 -0
- package/dist/lib/task/commands/ls.js +122 -0
- package/dist/lib/task/commands/show.js +135 -0
- package/dist/lib/task/frontmatter.js +32 -0
- package/dist/lib/task/index.js +41 -0
- package/dist/lib/task/short-id.js +90 -0
- package/dist/lib/update.js +25 -8
- package/lib/defaults.json +0 -1
- package/lib/init.ts +0 -10
- package/lib/sandbox/commands/create.ts +11 -2
- package/lib/sandbox/commands/enter.ts +40 -20
- package/lib/sandbox/commands/list-running.ts +65 -37
- package/lib/sandbox/commands/ls.ts +35 -27
- package/lib/sandbox/commands/start.ts +61 -0
- package/lib/sandbox/index.ts +15 -3
- package/lib/sandbox/task-resolver.ts +1 -1
- package/lib/sandbox/tools.ts +1 -1
- package/lib/table.ts +44 -0
- package/lib/task/commands/ls.ts +138 -0
- package/lib/task/commands/show.ts +139 -0
- package/lib/task/frontmatter.ts +30 -0
- package/lib/task/index.ts +44 -0
- package/lib/task/short-id.ts +107 -0
- package/lib/update.ts +28 -10
- package/package.json +1 -1
- package/templates/.agents/hooks/auto-resume.sh +104 -0
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.en.md +4 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -1
- package/templates/.agents/rules/next-step-output.en.md +62 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.en.md +5 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +62 -0
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +5 -0
- package/templates/.agents/rules/pr-sync.github.en.md +7 -0
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +7 -0
- package/templates/.agents/rules/task-short-id.en.md +54 -62
- package/templates/.agents/rules/task-short-id.zh-CN.md +35 -54
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
- package/templates/.agents/scripts/task-short-id.js +32 -189
- package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -12
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -12
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +13 -6
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +13 -6
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +12 -10
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +12 -10
- package/templates/.agents/skills/close-codescan/SKILL.en.md +13 -6
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +13 -6
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +13 -6
- package/templates/.agents/skills/code-task/SKILL.en.md +10 -6
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/code-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +10 -5
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +10 -5
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/reference/report-template.en.md +8 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +8 -0
- package/templates/.agents/skills/commit/SKILL.en.md +3 -4
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +37 -29
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +37 -29
- package/templates/.agents/skills/complete-task/SKILL.en.md +41 -4
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +41 -4
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +20 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +20 -11
- package/templates/.agents/skills/create-pr/config/verify.json +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +17 -17
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +17 -17
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +7 -7
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/plan-task/SKILL.en.md +10 -12
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -12
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +9 -9
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +9 -9
- package/templates/.agents/skills/review-code/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +0 -1
- package/templates/.agents/skills/watch-pr/SKILL.en.md +131 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +131 -0
- package/templates/.agents/skills/watch-pr/config/verify.json +22 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +43 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +43 -0
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +7 -5
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +6 -5
- package/templates/.agents/workflows/feature-development.en.yaml +7 -5
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +6 -5
- package/templates/.agents/workflows/refactoring.en.yaml +7 -5
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +6 -5
- package/templates/.claude/commands/watch-pr.en.md +8 -0
- package/templates/.claude/commands/watch-pr.zh-CN.md +8 -0
- package/templates/.claude/settings.json +11 -0
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +8 -0
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +8 -0
- package/templates/.opencode/commands/watch-pr.en.md +11 -0
- package/templates/.opencode/commands/watch-pr.zh-CN.md +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,107 @@
|
|
|
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
|
+
/**
|
|
74
|
+
* Resolve a branch to its active-task short id (`#NN`), or `null` when no
|
|
75
|
+
* active task is bound to that branch.
|
|
76
|
+
*
|
|
77
|
+
* Two-state semantics: this only consults the active registry
|
|
78
|
+
* (`active/.short-ids.json`) plus each `active/{taskId}/task.md`. Tasks moved
|
|
79
|
+
* to completed/blocked/cancelled/archive have already released their short id,
|
|
80
|
+
* so their branches return `null` — in `ai sandbox ls` that surfaces as `-`,
|
|
81
|
+
* meaning the sandbox is free to remove.
|
|
82
|
+
*/
|
|
83
|
+
function lookupShortIdByBranch(
|
|
84
|
+
branch: string,
|
|
85
|
+
repoRoot: string,
|
|
86
|
+
_opts?: { shortIdLength?: number }
|
|
87
|
+
): string | null {
|
|
88
|
+
const registry = readRegistry(repoRoot);
|
|
89
|
+
if (!registry) return null;
|
|
90
|
+
const matches: string[] = [];
|
|
91
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
92
|
+
const taskBranch = readBranchFromTaskMd(repoRoot, taskId);
|
|
93
|
+
if (taskBranch && taskBranch === branch) {
|
|
94
|
+
matches.push(`#${key}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (matches.length === 0) return null;
|
|
98
|
+
if (matches.length > 1) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`Warning: branch '${branch}' is bound to multiple active tasks: ${matches.join(', ')}; using ${matches[0]}\n`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return matches[0]!;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { normalizeShortIdInput, lookupShortIdByBranch, loadShortIdByTaskId };
|
|
107
|
+
export type { NormalizeResult, NormalizeOpts };
|
package/lib/update.ts
CHANGED
|
@@ -17,7 +17,8 @@ type UpdateConfig = {
|
|
|
17
17
|
org: string;
|
|
18
18
|
language: string;
|
|
19
19
|
platform?: { type?: string };
|
|
20
|
-
requiresPullRequest?: boolean;
|
|
20
|
+
requiresPullRequest?: boolean; // legacy field; read-only, migrated to prFlow then removed
|
|
21
|
+
prFlow?: 'required' | 'disabled';
|
|
21
22
|
sandbox?: Record<string, unknown>;
|
|
22
23
|
task?: { shortIdLength: number };
|
|
23
24
|
labels?: Record<string, unknown>;
|
|
@@ -27,7 +28,6 @@ type UpdateConfig = {
|
|
|
27
28
|
|
|
28
29
|
type Defaults = {
|
|
29
30
|
platform: { type: string };
|
|
30
|
-
requiresPullRequest: boolean;
|
|
31
31
|
sandbox: Record<string, unknown>;
|
|
32
32
|
task: { shortIdLength: number };
|
|
33
33
|
labels: Record<string, unknown>;
|
|
@@ -41,6 +41,25 @@ const defaults = JSON.parse(
|
|
|
41
41
|
const CONFIG_DIR = '.agents';
|
|
42
42
|
const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
|
|
43
43
|
|
|
44
|
+
// One-time migration of the legacy project-level PR switch to the three-state
|
|
45
|
+
// `prFlow` preference. `true` (the old default / "PR flow on") maps to the
|
|
46
|
+
// strong constraint `required`; `false` maps to `disabled`. A missing or
|
|
47
|
+
// already-migrated config is left untouched (idempotent). Returns the new
|
|
48
|
+
// prFlow value when a migration happened, otherwise null.
|
|
49
|
+
function migratePrFlow(config: UpdateConfig): 'required' | 'disabled' | null {
|
|
50
|
+
if (config.requiresPullRequest === true) {
|
|
51
|
+
delete config.requiresPullRequest;
|
|
52
|
+
config.prFlow = 'required';
|
|
53
|
+
return 'required';
|
|
54
|
+
}
|
|
55
|
+
if (config.requiresPullRequest === false) {
|
|
56
|
+
delete config.requiresPullRequest;
|
|
57
|
+
config.prFlow = 'disabled';
|
|
58
|
+
return 'disabled';
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
|
|
45
64
|
const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
|
|
46
65
|
if (!top.startsWith('.')) return false;
|
|
@@ -195,7 +214,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
195
214
|
const sandboxAdded = !config.sandbox;
|
|
196
215
|
const taskAdded = !config.task;
|
|
197
216
|
const labelsAdded = !config.labels;
|
|
198
|
-
const
|
|
217
|
+
const prFlowMigrated = migratePrFlow(config);
|
|
199
218
|
let configChanged = changed;
|
|
200
219
|
|
|
201
220
|
if (platformAdded) {
|
|
@@ -218,8 +237,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
218
237
|
configChanged = true;
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
if (
|
|
222
|
-
config.requiresPullRequest = defaults.requiresPullRequest;
|
|
240
|
+
if (prFlowMigrated) {
|
|
223
241
|
configChanged = true;
|
|
224
242
|
}
|
|
225
243
|
|
|
@@ -233,7 +251,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
233
251
|
for (const entry of added.merged) {
|
|
234
252
|
ok(` merged: ${entry}`);
|
|
235
253
|
}
|
|
236
|
-
} else if (platformAdded || sandboxAdded || taskAdded || labelsAdded ||
|
|
254
|
+
} else if (platformAdded || sandboxAdded || taskAdded || labelsAdded || prFlowMigrated) {
|
|
237
255
|
if (platformAdded) {
|
|
238
256
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
239
257
|
}
|
|
@@ -246,8 +264,8 @@ async function cmdUpdate(): Promise<void> {
|
|
|
246
264
|
if (labelsAdded) {
|
|
247
265
|
info(`Default labels.in config added to ${CONFIG_PATH}.`);
|
|
248
266
|
}
|
|
249
|
-
if (
|
|
250
|
-
info(`
|
|
267
|
+
if (prFlowMigrated) {
|
|
268
|
+
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
251
269
|
}
|
|
252
270
|
} else {
|
|
253
271
|
info(`File registry changed in ${CONFIG_PATH}.`);
|
|
@@ -264,8 +282,8 @@ async function cmdUpdate(): Promise<void> {
|
|
|
264
282
|
if (hasNewEntries && platformAdded) {
|
|
265
283
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
266
284
|
}
|
|
267
|
-
if (hasNewEntries &&
|
|
268
|
-
info(`
|
|
285
|
+
if (hasNewEntries && prFlowMigrated) {
|
|
286
|
+
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
269
287
|
}
|
|
270
288
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
271
289
|
ok(`Updated ${CONFIG_PATH}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
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 the resume message with a deliberately timing-insensitive sequence:
|
|
84
|
+
# 1. Escape leaves any non-input TUI state.
|
|
85
|
+
# 2. A 1s settle covers every known TUI escape timeout (vim 1000ms,
|
|
86
|
+
# xterm/readline 50ms) so the next bytes are delivered as fresh input
|
|
87
|
+
# instead of being folded into the escape sequence (the dropped-`U` race).
|
|
88
|
+
# 3. The text travels through a NAMED paste buffer pasted with bracketed
|
|
89
|
+
# paste (-p): the TUI ingests it as a single paste rather than per-character
|
|
90
|
+
# keypresses, so no leading char is eaten and the body is not read as a
|
|
91
|
+
# submit. The named buffer (-b) guarantees we paste exactly this text, and
|
|
92
|
+
# -d deletes it afterward so the user's anonymous paste stack is untouched.
|
|
93
|
+
# 4. Enter is a separate send-keys after the paste, so the submit signal is
|
|
94
|
+
# never merged into the pasted content (the must-press-Enter race).
|
|
95
|
+
# Every step stays non-blocking (2>/dev/null, exit 0 below) and logs a WARN on
|
|
96
|
+
# failure so the log can localize which tmux step broke.
|
|
97
|
+
log "tmux inject start (error=$error)"
|
|
98
|
+
tmux send-keys -t "$TMUX_PANE" Escape 2>/dev/null || log "WARN: tmux Escape failed (error=$error)"
|
|
99
|
+
sleep 1
|
|
100
|
+
tmux set-buffer -b auto-resume -- "$RESUME_TEXT" 2>/dev/null || log "WARN: tmux set-buffer failed (error=$error)"
|
|
101
|
+
tmux paste-buffer -t "$TMUX_PANE" -b auto-resume -p -d 2>/dev/null || log "WARN: tmux paste-buffer failed (error=$error)"
|
|
102
|
+
tmux send-keys -t "$TMUX_PANE" Enter 2>/dev/null || log "WARN: tmux Enter failed (error=$error)"
|
|
103
|
+
log "tmux inject done (error=$error)"
|
|
104
|
+
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
|
|
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
|
|
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.
|
|
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.
|
|
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,62 @@
|
|
|
1
|
+
# Next-Step Output Rule
|
|
2
|
+
|
|
3
|
+
This file defines two **independent** rules for a skill's "notify-user / Next steps" output; read this file before rendering the final output and apply both:
|
|
4
|
+
|
|
5
|
+
1. **Next-step output structure**: how "Next steps" commands and the "Task info" block present the task ID (placeholders / short-id lookup / fallback).
|
|
6
|
+
2. **Agent output trailing line (Completed at)**: the **very last line** of user-facing output, **independent of the "Next steps" block**, applying to normal / error / early-return paths alike.
|
|
7
|
+
|
|
8
|
+
## Placeholder semantics
|
|
9
|
+
|
|
10
|
+
| Placeholder | Meaning | Rendered form |
|
|
11
|
+
|-------------|---------|---------------|
|
|
12
|
+
| `{task-ref}` | Current task **short id** | `#`-prefixed, e.g. `#15`; falls back to the full `TASK-id` when unavailable |
|
|
13
|
+
| `{task-id}` | Current task **full id** | `TASK-YYYYMMDD-HHMMSS` |
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
- **Next-step TUI commands** (`/analyze-task`, `/{{project}}:review-code`, `$create-pr`, etc., including commands inside Markdown table cells) → always use `{task-ref}` (short id).
|
|
18
|
+
- **"Task info" / "Task status" structured field lines** → show full id and short id together: `- Task ID: {task-id} (short id {task-ref})`.
|
|
19
|
+
- **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).
|
|
20
|
+
|
|
21
|
+
## Obtaining the short id (`{task-ref}`)
|
|
22
|
+
|
|
23
|
+
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).
|
|
24
|
+
|
|
25
|
+
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:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
task_ref=$(node -e '
|
|
29
|
+
const cp=require("child_process");
|
|
30
|
+
const out=cp.execSync("node .agents/scripts/task-short-id.js list",{encoding:"utf8"});
|
|
31
|
+
const ids=(JSON.parse(out).ids)||{};
|
|
32
|
+
const full=process.argv[1];
|
|
33
|
+
const hit=Object.entries(ids).find(([,v])=>v===full);
|
|
34
|
+
process.stdout.write(hit?("#"+hit[0]):full);
|
|
35
|
+
' "$task_id")
|
|
36
|
+
# Example: $task_id=TASK-20260613-225809 -> task_ref=#15
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Fallback conditions
|
|
40
|
+
|
|
41
|
+
`{task-ref}` falls back to the full `TASK-id` in these cases (i.e. the registry has no matching short id):
|
|
42
|
+
|
|
43
|
+
- **Unallocated**: very early paths before `create-task` / `import-*` / `restore-task` has allocated a short id.
|
|
44
|
+
- **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.
|
|
45
|
+
|
|
46
|
+
`restore-task` re-allocates a short id when restoring a task (possibly different from before); the snippet picks up the new short id.
|
|
47
|
+
|
|
48
|
+
## `#` prefix and shell quoting
|
|
49
|
+
|
|
50
|
+
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`).
|
|
51
|
+
|
|
52
|
+
## Agent output trailing line (Completed at)
|
|
53
|
+
|
|
54
|
+
This section is a standalone rule, **co-equal with the next-step output structure** and **not part of the "Next steps" block**. Every skill that renders user-facing output must append the completion-time line as the **very last line** of that output — including **complete-task, which renders no next-step commands**, and **error / early-return paths** where a precondition is unmet. This lets users scanning across tmux windows tell at a glance which agent finished most recently:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
Completed at: YYYY-MM-DD HH:mm:ss
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- Value command (local timezone, no offset): `date "+%Y-%m-%d %H:%M:%S"`
|
|
61
|
+
- 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.
|
|
62
|
+
- 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,62 @@
|
|
|
1
|
+
# 下一步输出规则
|
|
2
|
+
|
|
3
|
+
本文件定义 skill「告知用户 / 下一步」输出的两类**相互独立**的规则;渲染最终输出前先读取本文件并同时落实两者:
|
|
4
|
+
|
|
5
|
+
1. **下一步输出结构**:「下一步」命令与「任务信息」段如何呈现任务 ID 形态(占位符 / 取短号 / 回退)。
|
|
6
|
+
2. **Agent 输出收尾行(Completed at)**:面向用户输出的**绝对最后一行**,**独立于「下一步」块**,正常 / 错误 / 早退路径都适用。
|
|
7
|
+
|
|
8
|
+
## 占位符语义
|
|
9
|
+
|
|
10
|
+
| 占位符 | 含义 | 渲染形态 |
|
|
11
|
+
|--------|------|----------|
|
|
12
|
+
| `{task-ref}` | 当前任务**短号** | 带 `#` 前缀,如 `#15`;取不到时回退完整 `TASK-id` |
|
|
13
|
+
| `{task-id}` | 当前任务**完整 ID** | `TASK-YYYYMMDD-HHMMSS` |
|
|
14
|
+
|
|
15
|
+
## 适用范围
|
|
16
|
+
|
|
17
|
+
- **下一步 TUI 命令**(`/analyze-task`、`/{{project}}:review-code`、`$create-pr` 等,含 Markdown 表格单元格内的命令)→ 一律用 `{task-ref}`(短号)。
|
|
18
|
+
- **「任务信息」/「任务状态」结构化字段行** → 完整 ID 与短号同显:`- 任务 ID:{task-id}(短号 {task-ref})`。
|
|
19
|
+
- **报告标题**(`任务 {task-id} ... 完成`)与**产出文件路径**(`.agents/workspace/active/{task-id}/...`)→ 保持完整 `{task-id}`(物理路径与归档键,不可改)。
|
|
20
|
+
|
|
21
|
+
## 取短号(`{task-ref}`)
|
|
22
|
+
|
|
23
|
+
短号唯一真源是注册表 `.agents/workspace/active/.short-ids.json`(经 `task-short-id.js`)。**禁止**读取 task.md frontmatter 的 `short_id` 字段(该字段不可信)。
|
|
24
|
+
|
|
25
|
+
在已解析出完整 `$task_id` 后,用以下片段反查短号;命中返回 `#NN`,未命中自动回退完整 `TASK-id`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
task_ref=$(node -e '
|
|
29
|
+
const cp=require("child_process");
|
|
30
|
+
const out=cp.execSync("node .agents/scripts/task-short-id.js list",{encoding:"utf8"});
|
|
31
|
+
const ids=(JSON.parse(out).ids)||{};
|
|
32
|
+
const full=process.argv[1];
|
|
33
|
+
const hit=Object.entries(ids).find(([,v])=>v===full);
|
|
34
|
+
process.stdout.write(hit?("#"+hit[0]):full);
|
|
35
|
+
' "$task_id")
|
|
36
|
+
# 示例:$task_id=TASK-20260613-225809 -> task_ref=#15
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 回退条件
|
|
40
|
+
|
|
41
|
+
`{task-ref}` 在以下情况回退为完整 `TASK-id`(即注册表查不到对应短号):
|
|
42
|
+
|
|
43
|
+
- **未分配**:任务尚未经 `create-task` / `import-*` / `restore-task` 分配短号的极早期路径。
|
|
44
|
+
- **已释放**:任务经 `complete-task` / `cancel-task` / `block-task` / `close-codescan` / `close-dependabot` 归档后,短号立即从注册表移除。这些归档类 skill 的终态/摘要行因此自然回退完整 `TASK-id`,无需特判。
|
|
45
|
+
|
|
46
|
+
`restore-task` 恢复任务时会重新分配短号(可能与历史不同),片段会取到新短号。
|
|
47
|
+
|
|
48
|
+
## `#` 前缀与 shell 引用
|
|
49
|
+
|
|
50
|
+
短号统一渲染为带 `#` 前缀的 `#NN`,与 task.md frontmatter 的 `short_id` 渲染一致。`#` 在 bash 中是注释起始符,示例命令若直接粘贴需视 TUI 而定(裸数字 `NN` 与 `#NN` 都被 `task-short-id.js resolve` 接受)。
|
|
51
|
+
|
|
52
|
+
## Agent 输出收尾行(Completed at)
|
|
53
|
+
|
|
54
|
+
本节是与「下一步输出结构」**并列的独立规则**,不隶属于「下一步」块。任何向用户渲染输出的 skill 都必须在面向用户输出的**绝对最后一行**追加完成时间收尾行——包括**声明「不渲染下一步命令」的 complete-task**,以及前置条件未满足而提前 return 的**错误 / 早退路径**。便于用户在 tmux 多窗口扫视时一眼判断各 Agent 的完成先后:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
Completed at: YYYY-MM-DD HH:mm:ss
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- 取值命令(本地时区、不带偏移):`date "+%Y-%m-%d %H:%M:%S"`
|
|
61
|
+
- 位置:必须是整段面向用户输出的最后一行,排在所有「下一步」命令之后。若某场景在命令之后还有条件性提醒行(如 env-blocked 提醒),收尾行排在该提醒行之后。
|
|
62
|
+
- 该行只用于终端扫视,不写入任何产物文件或 Issue/PR 评论;完成时刻的单一事实源仍是 task.md 的 Activity Log。
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# PR Checks Platform Commands
|
|
2
|
+
|
|
3
|
+
This code platform does not provide built-in pull request check commands.
|
|
4
|
+
|
|
5
|
+
Platform-specific check monitoring is skipped for custom platforms unless you provide matching `.{platform}.en.md` rule templates. Keep local task artifacts as the source of truth, or install a platform-specific template pack before running the `watch-pr` skill.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# PR Checks Platform Commands (GitHub)
|
|
2
|
+
|
|
3
|
+
Read this file before watching a PR's required checks, resolving a failing run, pulling failure logs, or reading the current branch's PR. The `watch-pr` skill's platform-specific commands live here; the skill body and `reference/` stay platform-agnostic.
|
|
4
|
+
|
|
5
|
+
## Current Branch PR / Repository Info
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gh pr view --json number -q .number # PR number for the current branch
|
|
9
|
+
gh pr view {pr#} --json headRefOid -q .headRefOid # PR head SHA
|
|
10
|
+
gh repo view --json nameWithOwner -q .nameWithOwner # {owner}/{repo}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If `gh` is not authenticated or a command fails, stop or degrade per the calling skill's error handling.
|
|
14
|
+
|
|
15
|
+
## Watch Required Checks
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gh pr checks {pr#} --required --watch --fail-fast -i 30 \
|
|
19
|
+
--json name,bucket,link,workflow
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `--required`: include only checks the repository's branch protection marks as required.
|
|
23
|
+
- `--watch`: block until those checks finish; `--fail-fast`: exit watch on the first failure.
|
|
24
|
+
- `-i 30`: poll every 30 seconds (backoff). **Overall time cap default 30 minutes (1800 seconds)**: use the timeout mechanism that matches the execution environment; on timeout, treat as "pending" (exit code 8).
|
|
25
|
+
- POSIX shell: `timeout 1800 gh pr checks {pr#} --required --watch --fail-fast -i 30 …`
|
|
26
|
+
- PowerShell (Windows): use a job timeout —
|
|
27
|
+
```powershell
|
|
28
|
+
$job = Start-Job { gh pr checks {pr#} --required --watch --fail-fast -i 30 }
|
|
29
|
+
if (Wait-Job $job -Timeout 1800) { Receive-Job $job } else { Stop-Job $job; <treat as "pending"> }
|
|
30
|
+
```
|
|
31
|
+
- Platform-neutral fallback (no external timeout tool): record the start time, loop `gh pr checks {pr#} --required --json name,bucket,link,workflow` **without** `--watch`, sleeping `-i` seconds each round and checking whether any `bucket` is still `pending`; if the elapsed time reaches 1800 seconds without finishing, exit the loop and treat as "pending".
|
|
32
|
+
- The `bucket` field of `--json` classifies each check as `pass` / `fail` / `pending` / `skipping` / `cancel`.
|
|
33
|
+
|
|
34
|
+
Exit code semantics:
|
|
35
|
+
|
|
36
|
+
| Exit code | Meaning | Outcome class |
|
|
37
|
+
|-----------|---------|---------------|
|
|
38
|
+
| 0 | all required checks passed | all green |
|
|
39
|
+
| 1 | at least one failed / errored | failure |
|
|
40
|
+
| 8 | still pending (watch timed out or was cut off by `timeout`) | pending |
|
|
41
|
+
|
|
42
|
+
Old `gh` (< 2.93) without `--required`: fall back to `gh pr checks {pr#} --watch --fail-fast` (i.e. "all checks must succeed"), and note this degradation in the help/report and suggest upgrading `gh`.
|
|
43
|
+
|
|
44
|
+
## Resolve a Failing Run id and Pull Logs
|
|
45
|
+
|
|
46
|
+
`gh pr checks --json` does not return a run id directly, but it returns each failing check's `link` (a URL to the run/job). Resolve in this deterministic order:
|
|
47
|
+
|
|
48
|
+
1. Extract from the failing check's `link` via regex: `https://github.com/{owner}/{repo}/actions/runs/(\d+)(?:/job/(\d+))?` → group 1 is the run id (optional group 2 is the job id).
|
|
49
|
+
2. When `link` is not a run URL or cannot be parsed, query check-runs by head SHA:
|
|
50
|
+
```bash
|
|
51
|
+
sha=$(gh pr view {pr#} --json headRefOid -q .headRefOid)
|
|
52
|
+
gh api "repos/{owner}/{repo}/commits/$sha/check-runs" \
|
|
53
|
+
--jq '.check_runs[] | select(.name=="{failed-check-name}") | .details_url'
|
|
54
|
+
```
|
|
55
|
+
then extract the run id from `details_url` the same way.
|
|
56
|
+
3. If neither path yields a run id → treat as "unlocatable" and use the skill's help exit; do not self-heal blindly.
|
|
57
|
+
|
|
58
|
+
Once the run id is known, pull the failure logs:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gh run view {run-id} --log-failed
|
|
62
|
+
```
|