@fitlab-ai/agent-infra 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +11 -3
- package/dist/bin/cli.js +10 -3
- package/dist/lib/sandbox/commands/create.js +26 -4
- package/dist/lib/sandbox/commands/ls.js +4 -33
- package/dist/lib/sandbox/commands/show.js +67 -0
- package/dist/lib/sandbox/index.js +7 -0
- package/dist/lib/sandbox/tools.js +20 -1
- package/dist/lib/task/commands/issue-body.js +94 -0
- package/dist/lib/task/commands/log.js +139 -6
- package/dist/lib/task/index.js +8 -0
- package/dist/lib/task/issue-form.js +66 -0
- package/dist/lib/task/sections.js +44 -0
- package/lib/sandbox/commands/create.ts +33 -4
- package/lib/sandbox/commands/ls.ts +4 -36
- package/lib/sandbox/commands/show.ts +80 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/tools.ts +28 -1
- package/lib/task/commands/issue-body.ts +102 -0
- package/lib/task/commands/log.ts +146 -6
- package/lib/task/index.ts +8 -0
- package/lib/task/issue-form.ts +77 -0
- package/lib/task/sections.ts +44 -0
- package/package.json +3 -2
- package/templates/.agents/README.en.md +3 -1
- package/templates/.agents/README.zh-CN.md +3 -1
- package/templates/.agents/rules/create-issue.github.en.md +20 -29
- package/templates/.agents/rules/create-issue.github.zh-CN.md +20 -29
- package/templates/.agents/rules/issue-pr-commands.github.en.md +6 -0
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/next-step-output.en.md +26 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +26 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +11 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +11 -0
- package/templates/.agents/rules/review-handshake.en.md +15 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +15 -1
- package/templates/.agents/rules/task-management.en.md +27 -0
- package/templates/.agents/rules/task-management.zh-CN.md +31 -0
- package/templates/.agents/scripts/validate-artifact.js +11 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +3 -1
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +14 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +30 -81
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +30 -80
- package/templates/.agents/skills/close-codescan/SKILL.en.md +13 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +13 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +7 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +7 -2
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +6 -14
- package/templates/.agents/skills/commit/SKILL.en.md +37 -5
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +37 -5
- package/templates/.agents/skills/complete-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +23 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +23 -2
- package/templates/.agents/skills/create-release-note/SKILL.en.md +19 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +19 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +14 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +14 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +35 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +35 -2
- package/templates/.agents/skills/init-labels/SKILL.en.md +3 -1
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -1
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +15 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +15 -1
- package/templates/.agents/skills/post-release/SKILL.en.md +3 -1
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/refine-title/SKILL.en.md +3 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/release/SKILL.en.md +3 -1
- package/templates/.agents/skills/release/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +13 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +13 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +16 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +16 -2
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +1 -2
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +15 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +15 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +1 -2
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +16 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +16 -2
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +1 -2
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +1 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +1 -0
- package/templates/.agents/skills/test/SKILL.en.md +3 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +3 -1
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +3 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +3 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -1
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -1
- package/templates/.agents/templates/task.en.md +5 -0
- package/templates/.agents/templates/task.zh-CN.md +5 -0
package/lib/task/commands/log.ts
CHANGED
|
@@ -4,16 +4,23 @@ import { resolveTaskRef } from '../resolve-ref.ts';
|
|
|
4
4
|
|
|
5
5
|
const USAGE = `Usage: ai task log <N | #N | TASK-id>
|
|
6
6
|
|
|
7
|
-
Renders a task's activity log as a
|
|
7
|
+
Renders a task's activity log as a per-step status table. A step's start and
|
|
8
|
+
completion are paired onto one row: STARTED holds the start time, DONE the
|
|
9
|
+
completion time (or '(in progress)' while still running).
|
|
8
10
|
<ref> Bare numeric / '#N' short id, or a full TASK-YYYYMMDD-HHMMSS id.
|
|
9
11
|
|
|
10
|
-
Columns: # (
|
|
12
|
+
Columns: # (row) / STEP / AGENT / STARTED / DONE / NOTE
|
|
13
|
+
A human-executed step shows AGENT as 'human' and, when it has no start marker,
|
|
14
|
+
a '-' STARTED placeholder. Review-step NOTE also carries two human counts in
|
|
15
|
+
the verdict list, right after blockers/major/minor: manual-verify (env-blocked)
|
|
16
|
+
and human-decision (current ledger stage total).
|
|
11
17
|
`;
|
|
12
18
|
|
|
13
|
-
const TABLE_HEADERS = ['#', '
|
|
19
|
+
const TABLE_HEADERS = ['#', 'STEP', 'AGENT', 'STARTED', 'DONE', 'NOTE'] as const;
|
|
14
20
|
|
|
15
21
|
// The activity-log H2 heading is language-dependent (zh template / en template).
|
|
16
22
|
const HEADING_RE = /^##\s+(活动日志|Activity Log)\s*$/;
|
|
23
|
+
const LEDGER_HEADING_RE = /^##\s+(审查分歧账本|Review Disagreement Ledger)\s*$/;
|
|
17
24
|
const NEXT_H2_RE = /^##\s/;
|
|
18
25
|
// `- {time} — **{step}** by {agent} — {note}` ; the separator is an em-dash
|
|
19
26
|
// (U+2014). STEP/AGENT are non-greedy so a note that itself contains ' — ' or
|
|
@@ -22,6 +29,34 @@ const ENTRY_RE =
|
|
|
22
29
|
/^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*\*(.+?)\*\* by (.+?) — (.*)$/;
|
|
23
30
|
|
|
24
31
|
type LogEntry = { time: string; step: string; agent: string; note: string };
|
|
32
|
+
type ReviewStage = 'analysis' | 'plan' | 'code';
|
|
33
|
+
type LedgerRow = { stage: string; status: string };
|
|
34
|
+
|
|
35
|
+
// One rendered row = one step instance. `started`/`done` are timestamps; an empty
|
|
36
|
+
// `done` with a non-empty `started` means the step is still in flight, while an
|
|
37
|
+
// empty `started` is a historical done-only entry (no start marker was written).
|
|
38
|
+
type StepRow = { step: string; agent: string; started: string; done: string; note: string };
|
|
39
|
+
|
|
40
|
+
// A start marker reuses the normal entry grammar and only suffixes its action
|
|
41
|
+
// with ` [started]`; the matching done entry carries the identical base action
|
|
42
|
+
// without the suffix. Pairing therefore keys on the base action (including any
|
|
43
|
+
// `(Round N)`), so every round and every repeated execution pairs on its own.
|
|
44
|
+
const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
|
|
45
|
+
// Short agent tokens that actually appear in activity logs for AI executors
|
|
46
|
+
// (workflow recommended_agents claude/codex/gemini/cursor + enabled TUI opencode;
|
|
47
|
+
// note these differ from the `.airc.json` long names claude-code/gemini-cli).
|
|
48
|
+
// Any other executor token (a human name, possibly CJK) is treated as human.
|
|
49
|
+
const KNOWN_AI_AGENTS = new Set(['claude', 'codex', 'gemini', 'opencode', 'cursor']);
|
|
50
|
+
const HUMAN_DECISION_STATUSES = new Set(['needs-human-decision', 'human-decided']);
|
|
51
|
+
const ENV_BLOCKED_RE = /\(\+\s*(\d+)\s+env-blocked\)/i;
|
|
52
|
+
// Same match plus any leading whitespace, so folding the count into the verdict
|
|
53
|
+
// text drops the redundant `(+ n env-blocked)` fragment without leaving a gap.
|
|
54
|
+
const ENV_BLOCKED_STRIP_RE = /\s*\(\+\s*\d+\s+env-blocked\)/i;
|
|
55
|
+
const REVIEW_STAGE_PREFIXES: { prefix: string; stage: ReviewStage }[] = [
|
|
56
|
+
{ prefix: 'Review Analysis', stage: 'analysis' },
|
|
57
|
+
{ prefix: 'Review Plan', stage: 'plan' },
|
|
58
|
+
{ prefix: 'Review Code', stage: 'code' }
|
|
59
|
+
];
|
|
25
60
|
|
|
26
61
|
function parseActivityLog(content: string): { sectionFound: boolean; entries: LogEntry[] } {
|
|
27
62
|
const lines = content.split('\n');
|
|
@@ -44,6 +79,100 @@ function parseActivityLog(content: string): { sectionFound: boolean; entries: Lo
|
|
|
44
79
|
return { sectionFound: true, entries: parsed.map((p) => p.entry) };
|
|
45
80
|
}
|
|
46
81
|
|
|
82
|
+
// Collapse a chronological entry list into per-step rows: a `[started]` marker
|
|
83
|
+
// opens a row, the next matching done entry fills it in place (FIFO per base
|
|
84
|
+
// action). Started-only rows stay in flight; done-only entries (legacy logs with
|
|
85
|
+
// no start marker) render as standalone rows. Result order = first-seen order,
|
|
86
|
+
// which is already ascending because `entries` is sorted ascending.
|
|
87
|
+
function pairEntries(entries: LogEntry[]): StepRow[] {
|
|
88
|
+
const rows: StepRow[] = [];
|
|
89
|
+
const open = new Map<string, StepRow[]>();
|
|
90
|
+
for (const e of entries) {
|
|
91
|
+
const isStarted = STARTED_SUFFIX_RE.test(e.step);
|
|
92
|
+
const base = e.step.replace(STARTED_SUFFIX_RE, '');
|
|
93
|
+
if (isStarted) {
|
|
94
|
+
const row: StepRow = { step: base, agent: e.agent, started: e.time, done: '', note: e.note };
|
|
95
|
+
rows.push(row);
|
|
96
|
+
const queue = open.get(base);
|
|
97
|
+
if (queue) queue.push(row);
|
|
98
|
+
else open.set(base, [row]);
|
|
99
|
+
} else {
|
|
100
|
+
const pending = open.get(base)?.shift();
|
|
101
|
+
if (pending) {
|
|
102
|
+
// Done fills the open row; the done entry carries the meaningful note.
|
|
103
|
+
pending.done = e.time;
|
|
104
|
+
pending.agent = e.agent;
|
|
105
|
+
pending.note = e.note;
|
|
106
|
+
} else {
|
|
107
|
+
rows.push({ step: base, agent: e.agent, started: '', done: e.time, note: e.note });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return rows;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseReviewLedger(content: string): LedgerRow[] {
|
|
115
|
+
const lines = content.split('\n');
|
|
116
|
+
let i = 0;
|
|
117
|
+
while (i < lines.length && !LEDGER_HEADING_RE.test(lines[i]!)) i += 1;
|
|
118
|
+
if (i >= lines.length) return [];
|
|
119
|
+
|
|
120
|
+
const rows: LedgerRow[] = [];
|
|
121
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
122
|
+
if (NEXT_H2_RE.test(lines[j]!)) break;
|
|
123
|
+
const line = lines[j]!.trim();
|
|
124
|
+
if (!line.startsWith('|')) continue;
|
|
125
|
+
const cells = line
|
|
126
|
+
.split('|')
|
|
127
|
+
.slice(1, -1)
|
|
128
|
+
.map((cell) => cell.trim());
|
|
129
|
+
if (cells.length < 6 || cells[0] === 'id' || /^-+$/.test(cells[0] ?? '')) continue;
|
|
130
|
+
rows.push({ stage: cells[1]!, status: cells[4]! });
|
|
131
|
+
}
|
|
132
|
+
return rows;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function countHumanDecisionsByStage(rows: LedgerRow[]): Map<ReviewStage, number> {
|
|
136
|
+
const counts = new Map<ReviewStage, number>();
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
if (!isReviewStage(row.stage) || !HUMAN_DECISION_STATUSES.has(row.status)) continue;
|
|
139
|
+
counts.set(row.stage, (counts.get(row.stage) ?? 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
return counts;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isReviewStage(stage: string): stage is ReviewStage {
|
|
145
|
+
return stage === 'analysis' || stage === 'plan' || stage === 'code';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function reviewStageForStep(step: string): ReviewStage | undefined {
|
|
149
|
+
return REVIEW_STAGE_PREFIXES.find(({ prefix }) => step.startsWith(prefix))?.stage;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function humanValidationCount(note: string): number {
|
|
153
|
+
const match = ENV_BLOCKED_RE.exec(note);
|
|
154
|
+
return match ? Number(match[1]) : 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A step is human-executed when its agent token is not a known AI token. Take
|
|
158
|
+
// the first whitespace-delimited token and drop any trailing parenthetical
|
|
159
|
+
// annotation (e.g. `张三 (executed on host)` -> `张三`) before the lookup.
|
|
160
|
+
function isHumanAgent(agent: string): boolean {
|
|
161
|
+
const token = agent.trim().split(/\s+/)[0]?.replace(/\(.*$/, '') ?? '';
|
|
162
|
+
return token !== '' && !KNOWN_AI_AGENTS.has(token);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fold the two human counts into a review row's verdict NOTE: comma-joined, right
|
|
166
|
+
// after the blockers/major/minor list and before the ` → artifact` link, mirroring
|
|
167
|
+
// the review count line. The raw `(+ n env-blocked)` fragment is dropped so the
|
|
168
|
+
// env-blocked number is not shown twice (it becomes the manual-verify count).
|
|
169
|
+
function foldHumanCounts(note: string, decisions: number, envBlocked: number): string {
|
|
170
|
+
const base = note.replace(ENV_BLOCKED_STRIP_RE, '');
|
|
171
|
+
const group = `Manual-verify: ${envBlocked}, Human-decision: ${decisions}`;
|
|
172
|
+
const arrow = base.indexOf(' → ');
|
|
173
|
+
return arrow === -1 ? `${base}, ${group}` : `${base.slice(0, arrow)}, ${group}${base.slice(arrow)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
47
176
|
function log(args: string[] = []): void {
|
|
48
177
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
49
178
|
process.stdout.write(USAGE);
|
|
@@ -70,11 +199,22 @@ function log(args: string[] = []): void {
|
|
|
70
199
|
process.exitCode = 1;
|
|
71
200
|
return;
|
|
72
201
|
}
|
|
73
|
-
const
|
|
202
|
+
const steps = pairEntries(entries);
|
|
203
|
+
const humanDecisionCounts = countHumanDecisionsByStage(parseReviewLedger(content));
|
|
204
|
+
const rows = steps.map((s, idx) => {
|
|
205
|
+
const stage = reviewStageForStep(s.step);
|
|
206
|
+
const note = stage
|
|
207
|
+
? foldHumanCounts(s.note, humanDecisionCounts.get(stage) ?? 0, humanValidationCount(s.note))
|
|
208
|
+
: s.note;
|
|
209
|
+
const human = isHumanAgent(s.agent);
|
|
210
|
+
const agent = human ? 'human' : s.agent;
|
|
211
|
+
const started = s.started || (human ? '-' : '');
|
|
212
|
+
return [String(idx + 1), s.step, agent, started, s.done || (s.started ? '(in progress)' : ''), note];
|
|
213
|
+
});
|
|
74
214
|
for (const line of formatTable(TABLE_HEADERS, rows, { zebra: Boolean(process.stdout.isTTY) })) {
|
|
75
215
|
process.stdout.write(`${line}\n`);
|
|
76
216
|
}
|
|
77
|
-
process.stdout.write(`Total: ${
|
|
217
|
+
process.stdout.write(`Total: ${steps.length} steps\n`);
|
|
78
218
|
}
|
|
79
219
|
|
|
80
|
-
export { log, parseActivityLog };
|
|
220
|
+
export { log, parseActivityLog, pairEntries, isHumanAgent };
|
package/lib/task/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ Commands:
|
|
|
4
4
|
cat <ref> <artifact | N> Print a task artifact (by name or number)
|
|
5
5
|
files <ref> List artifacts in a task dir (numbered)
|
|
6
6
|
grep <pattern> [ref] [artifact | N] Literal search across task artifacts (omit ref to scan all)
|
|
7
|
+
issue-body <ref> [--template <path>] Print a deterministic Issue body from task.md (描述 + 需求, or an Issue Form)
|
|
7
8
|
log <ref> Render a task's activity log as a timeline
|
|
8
9
|
ls [--all | --blocked | --completed] List tasks (default: active)
|
|
9
10
|
show <N | #N | TASK-id> Print a task.md
|
|
@@ -15,6 +16,8 @@ Examples:
|
|
|
15
16
|
ai task files 11
|
|
16
17
|
ai task grep resolveArtifact
|
|
17
18
|
ai task grep resolveArtifact 11
|
|
19
|
+
ai task issue-body 11
|
|
20
|
+
ai task issue-body 11 --template .github/ISSUE_TEMPLATE/05_other.yml
|
|
18
21
|
ai task log 11
|
|
19
22
|
ai task ls
|
|
20
23
|
ai task show 11
|
|
@@ -53,6 +56,11 @@ export async function runTask(args: string[]): Promise<void> {
|
|
|
53
56
|
grep(rest);
|
|
54
57
|
break;
|
|
55
58
|
}
|
|
59
|
+
case 'issue-body': {
|
|
60
|
+
const { issueBody } = await import('./commands/issue-body.ts');
|
|
61
|
+
issueBody(rest);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
56
64
|
case 'log': {
|
|
57
65
|
const { log } = await import('./commands/log.ts');
|
|
58
66
|
log(rest);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parse } from 'yaml';
|
|
2
|
+
|
|
3
|
+
type TaskFields = {
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
requirements: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Field id -> task value mapping (single source of truth, per HD-1).
|
|
10
|
+
// Only ids that map cleanly to title / description / requirements get a value;
|
|
11
|
+
// every other text field gets `N/A`. This deliberately tightens the older
|
|
12
|
+
// "suggested" mapping table (impact / context / alternatives / steps / expected
|
|
13
|
+
// no longer flow into requirements) so the requirements checklist is never
|
|
14
|
+
// pushed into an unrelated field.
|
|
15
|
+
const TITLE_IDS = new Set(['summary', 'title']);
|
|
16
|
+
const DESCRIPTION_IDS = new Set([
|
|
17
|
+
'description',
|
|
18
|
+
'problem',
|
|
19
|
+
'what-happened',
|
|
20
|
+
'question',
|
|
21
|
+
'current-content',
|
|
22
|
+
'issue-description',
|
|
23
|
+
'detailed-description'
|
|
24
|
+
]);
|
|
25
|
+
const REQUIREMENTS_IDS = new Set(['requirements', 'solution', 'suggested-content']);
|
|
26
|
+
|
|
27
|
+
// Only free-text field types carry task content; structural / static fields are skipped.
|
|
28
|
+
const TEXT_FIELD_TYPES = new Set(['input', 'textarea']);
|
|
29
|
+
const PLACEHOLDER = 'N/A';
|
|
30
|
+
|
|
31
|
+
function mapFieldValue(id: string, fields: TaskFields): string {
|
|
32
|
+
if (TITLE_IDS.has(id)) return fields.title;
|
|
33
|
+
if (DESCRIPTION_IDS.has(id)) return fields.description;
|
|
34
|
+
if (REQUIREMENTS_IDS.has(id)) return fields.requirements;
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type IssueFormField = {
|
|
39
|
+
type?: unknown;
|
|
40
|
+
id?: unknown;
|
|
41
|
+
attributes?: { label?: unknown };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render the final Issue body for a GitHub Issue Form (scenario A).
|
|
46
|
+
*
|
|
47
|
+
* Walks the form's `body[]` in order, skips `markdown` / `dropdown` /
|
|
48
|
+
* `checkboxes`, and renders each `input` / `textarea` as `### {label}` followed
|
|
49
|
+
* by the deterministically-mapped task value (or `N/A` when the field has no
|
|
50
|
+
* reliable source). The template structure is preserved; the whole task.md and
|
|
51
|
+
* its scaffolding sections are never emitted.
|
|
52
|
+
*
|
|
53
|
+
* Throws on unreadable / non-object YAML or a missing `body[]` list so the
|
|
54
|
+
* caller can fall back to the default body.
|
|
55
|
+
*/
|
|
56
|
+
function renderTemplateBody(formText: string, fields: TaskFields): string {
|
|
57
|
+
const doc = parse(formText) as { body?: unknown } | null;
|
|
58
|
+
if (!doc || typeof doc !== 'object' || !Array.isArray(doc.body)) {
|
|
59
|
+
throw new Error('Issue Form has no body[] list');
|
|
60
|
+
}
|
|
61
|
+
const sections: string[] = [];
|
|
62
|
+
for (const raw of doc.body as IssueFormField[]) {
|
|
63
|
+
if (!raw || typeof raw !== 'object') continue;
|
|
64
|
+
const type = typeof raw.type === 'string' ? raw.type : '';
|
|
65
|
+
if (!TEXT_FIELD_TYPES.has(type)) continue;
|
|
66
|
+
const label = typeof raw.attributes?.label === 'string' ? raw.attributes.label.trim() : '';
|
|
67
|
+
if (!label) continue;
|
|
68
|
+
const id = typeof raw.id === 'string' ? raw.id : '';
|
|
69
|
+
const mapped = mapFieldValue(id, fields);
|
|
70
|
+
const value = mapped.trim() === '' ? PLACEHOLDER : mapped;
|
|
71
|
+
sections.push(`### ${label}\n\n${value}`);
|
|
72
|
+
}
|
|
73
|
+
return `${sections.join('\n\n')}\n`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { renderTemplateBody, mapFieldValue, PLACEHOLDER };
|
|
77
|
+
export type { TaskFields };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function escapeRegExp(value: string): string {
|
|
2
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Return the body of the first `## {alias}` section (any alias matches), from
|
|
7
|
+
* the heading line to the next `## ` heading or EOF. Lines are preserved
|
|
8
|
+
* verbatim (checkbox text is never normalized); only leading/trailing blank
|
|
9
|
+
* lines are trimmed. Returns '' when no alias heading is present.
|
|
10
|
+
*/
|
|
11
|
+
function extractSection(content: string, aliases: string[]): string {
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
let start = -1;
|
|
14
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
15
|
+
const line = lines[i]!.trim();
|
|
16
|
+
if (aliases.some((alias) => new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`).test(line))) {
|
|
17
|
+
start = i + 1;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (start === -1) return '';
|
|
22
|
+
let end = lines.length;
|
|
23
|
+
for (let i = start; i < lines.length; i += 1) {
|
|
24
|
+
if (/^##\s+/.test(lines[i]!)) {
|
|
25
|
+
end = i;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return the heading alias that actually appears as a `## {alias}` line, so a
|
|
34
|
+
* rendered section can mirror the source language. Falls back to the first
|
|
35
|
+
* alias when none is present.
|
|
36
|
+
*/
|
|
37
|
+
function findSectionHeading(content: string, aliases: string[]): string {
|
|
38
|
+
for (const alias of aliases) {
|
|
39
|
+
if (new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`, 'm').test(content)) return alias;
|
|
40
|
+
}
|
|
41
|
+
return aliases[0]!;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { extractSection, findSectionHeading };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
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",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"cross-spawn": "^7.0.6",
|
|
48
48
|
"picocolors": "1.1.1",
|
|
49
49
|
"semver": "^7.8.1",
|
|
50
|
-
"smol-toml": "^1.6.1"
|
|
50
|
+
"smol-toml": "^1.6.1",
|
|
51
|
+
"yaml": "^2.9.0"
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
54
|
"build": "tsc -p tsconfig.json && node scripts/build.js && node scripts/build-inline.js",
|
|
@@ -163,11 +163,13 @@ Recommended frontmatter:
|
|
|
163
163
|
```yaml
|
|
164
164
|
---
|
|
165
165
|
name: enforce-style
|
|
166
|
-
description: "Apply the team style guide before code review"
|
|
166
|
+
description: "Apply the team style guide before code review. Use when you need to align the team's code style before review"
|
|
167
167
|
args: "<task-id>" # optional
|
|
168
168
|
---
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
+
Write `description` as "one-line responsibility + scenario clause": after the short responsibility, append "Use when …" (the Chinese `SKILL.zh-CN.md` variant uses "当……时使用") as the cross-TUI trigger semantics, so Agent Skills-capable tools can self-discover the skill in natural conversation. Do **not** add a separate `triggers` (or similar) frontmatter field for this.
|
|
172
|
+
|
|
171
173
|
After adding or updating a custom skill, run `update-agent-infra` again. The sync step detects non-built-in skills and generates matching commands for Claude Code, Gemini CLI, and OpenCode automatically.
|
|
172
174
|
|
|
173
175
|
### Shared skill sources
|
|
@@ -163,11 +163,13 @@
|
|
|
163
163
|
```yaml
|
|
164
164
|
---
|
|
165
165
|
name: enforce-style
|
|
166
|
-
description: "
|
|
166
|
+
description: "在代码审查前应用团队风格规范。当需要在评审前统一团队代码风格时使用"
|
|
167
167
|
args: "<task-id>" # 可选
|
|
168
168
|
---
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
+
`description` 采用「一句话职责 + 场景触发子句」写法:在简短职责描述后补充「当……时使用」(英文 `SKILL.en.md` 用「Use when …」),作为跨 TUI 的触发语义,供支持 Agent Skills 的工具在自然对话中自发现该 skill。**不要**为此新增 `triggers` 等额外 frontmatter 字段。
|
|
172
|
+
|
|
171
173
|
新增或修改自定义 skill 后,再执行一次 `update-agent-infra`。同步过程会自动检测非内置 skill,并为 Claude Code、Gemini CLI、OpenCode 生成对应命令。
|
|
172
174
|
|
|
173
175
|
### 共享 skill 源
|
|
@@ -22,11 +22,11 @@ After `create-task` writes the local `task.md`, follow this rule to cascade Issu
|
|
|
22
22
|
|
|
23
23
|
Pull the following from `task.md`:
|
|
24
24
|
|
|
25
|
-
- Task title (the first `# ` heading, stripped of `任务:` / `Task:` prefixes)
|
|
26
|
-
- The `## Description` / `## 描述` section
|
|
27
|
-
- The `## Requirements` / `## 需求` section
|
|
25
|
+
- Task title (the first `# ` heading, stripped of `任务:` / `Task:` prefixes) — used to build the Issue title
|
|
28
26
|
- frontmatter fields `type` and (optionally) `milestone`
|
|
29
27
|
|
|
28
|
+
> The Issue **body** is not extracted by hand here. It is generated deterministically in §3 by the `ai task issue-body` command from the `## 描述` / `## 需求` sections; callers must not assemble it themselves.
|
|
29
|
+
|
|
30
30
|
Build the Issue title:
|
|
31
31
|
|
|
32
32
|
| task.md `type` | Conventional Commits type |
|
|
@@ -41,45 +41,35 @@ Scope inference: read known module names from `.agents/.airc.json`'s `labels.in`
|
|
|
41
41
|
|
|
42
42
|
### 3. Build the Issue Body
|
|
43
43
|
|
|
44
|
+
> **Mechanization boundary (mandatory)**: the Issue body is always produced deterministically by the `ai task issue-body` command; callers only pass the command's stdout to `gh issue create` via `--body-file` and must **not** assemble, rewrite, or truncate the body themselves. The command emits only the task title / `## 描述` / `## 需求` content; every other task.md scaffolding section never enters the body.
|
|
45
|
+
|
|
44
46
|
Issue Form detection: follow the "Issue Template Detection" section in `.agents/rules/issue-pr-commands.md` to scan `.github/ISSUE_TEMPLATE/*.yml` (excluding `config.yml`).
|
|
45
47
|
|
|
46
48
|
#### Scenario A: A matching template was detected
|
|
47
49
|
|
|
48
50
|
Pick the form whose `name` (or filename) best matches the task type (e.g., a task with `type: bugfix` prefers a form whose name contains `bug`); if no match, fall back to a generic form like `other.yml`; if none, take the first form in the directory.
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- `textarea` / `input` fields: use `attributes.label` as a markdown heading and pull values from task.md
|
|
53
|
-
- `markdown` fields: skip (these are description blurbs)
|
|
54
|
-
- `dropdown` / `checkboxes` fields: skip
|
|
52
|
+
Once the form file `{form-path}` is chosen, let the command render the final body from that Issue Form and write it to the body file `{body-file}` for §5:
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|---|---|
|
|
60
|
-
| `summary`, `title` | task title |
|
|
61
|
-
| `description`, `problem`, `what happened`, `issue-description`, `current-content` | task description |
|
|
62
|
-
| `solution`, `requirements`, `steps`, `suggested-content`, `impact`, `context`, `alternatives`, `expected` | requirements list (preserve checked / unchecked state as-is) |
|
|
63
|
-
| Other `textarea` / `input` fields | task description, or `N/A` if missing |
|
|
54
|
+
```bash
|
|
55
|
+
ai task issue-body {task-id} --template "{form-path}" > "{body-file}"
|
|
56
|
+
```
|
|
64
57
|
|
|
65
|
-
|
|
58
|
+
The command skips `markdown` / `dropdown` / `checkboxes` fields and, for `input` / `textarea` fields, uses `attributes.label` as the heading and deterministically fills the task title / description / requirements by field `id`, writing `N/A` for fields with no reliable source (the field mapping table is the single source of truth inside the command and is not restated here). When the command exits non-zero (missing file / invalid YAML / no `body`), regenerate `{body-file}` with the Scenario B command instead.
|
|
66
59
|
|
|
67
60
|
#### Scenario B: No template, or template parsing failed
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```markdown
|
|
72
|
-
## Description
|
|
62
|
+
Let the command emit the default body (only `## 描述` + `## 需求`, checkbox text preserved verbatim, missing sections filled with `N/A`) into `{body-file}`:
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
```bash
|
|
65
|
+
ai task issue-body {task-id} > "{body-file}"
|
|
66
|
+
```
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
#### Red line: never use the entire task.md as the body
|
|
77
69
|
|
|
78
|
-
-
|
|
79
|
-
- [ ] {requirement-2}
|
|
80
|
-
```
|
|
70
|
+
In both Scenario A and B, the body may only come from `ai task issue-body` stdout and contains **only the description + requirements content** (Scenario A is the equivalent content mapped onto template fields).
|
|
81
71
|
|
|
82
|
-
|
|
72
|
+
Wrong example (❌ forbidden): pasting the entire task.md — including scaffolding sections such as `## 分析` / `## 设计` / `## 实现备注` / `## 审查反馈` / `## 审查分歧账本` / `## 人工裁决` / `## 活动日志` / `## 完成检查清单` and `#XXX` placeholders — directly as the Issue body. Those sections only go to the `sync-issue:{task-id}:task` comment, never the body.
|
|
83
73
|
|
|
84
74
|
### 4. Resolve labels / Issue Type / milestone
|
|
85
75
|
|
|
@@ -137,17 +127,18 @@ When a milestone is selected, pass the release line (or `General Backlog` or the
|
|
|
137
127
|
|
|
138
128
|
### 5. Call the GitHub CLI to Create the Issue
|
|
139
129
|
|
|
140
|
-
Run the "Create Issue" command from `.agents/rules/issue-pr-commands.md`:
|
|
130
|
+
Run the "Create Issue" command from `.agents/rules/issue-pr-commands.md`; always use the `{body-file}` produced in §3, overriding the generic command's `--body`:
|
|
141
131
|
|
|
142
132
|
```bash
|
|
143
133
|
gh issue create -R "$upstream_repo" \
|
|
144
134
|
--title "{title}" \
|
|
145
|
-
--body "{body}" \
|
|
135
|
+
--body-file "{body-file}" \
|
|
146
136
|
--assignee @me \
|
|
147
137
|
{label-args} \
|
|
148
138
|
{milestone-arg}
|
|
149
139
|
```
|
|
150
140
|
|
|
141
|
+
- `{body-file}` is the body file produced in §3 by `ai task issue-body`; do **not** switch back to `--body` and assemble the body by hand
|
|
151
142
|
- `{label-args}` is expanded from the result of §4 into multiple `--label "..."`; if empty, omit the entire argument
|
|
152
143
|
- `{milestone-arg}` is only expanded to `--milestone "..."` when `has_triage=true` and milestone is non-empty; otherwise omit
|
|
153
144
|
- `--assignee @me` requires no permission probe; on failure, skip silently
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
|
|
23
23
|
从 `task.md` 提取以下字段:
|
|
24
24
|
|
|
25
|
-
- 任务标题(首个 `# ` 标题,去掉 `任务:` / `Task:`
|
|
26
|
-
- `## Description` / `## 描述` 段落
|
|
27
|
-
- `## Requirements` / `## 需求` 段落
|
|
25
|
+
- 任务标题(首个 `# ` 标题,去掉 `任务:` / `Task:` 前缀)—— 用于构造 Issue 标题
|
|
28
26
|
- frontmatter 中的 `type` 与(可选的)`milestone`
|
|
29
27
|
|
|
28
|
+
> Issue **正文**不在此处手工提取。正文由 §3 调用 `ai task issue-body` 命令从 `## 描述` / `## 需求` 段确定性生成,调用方不得自行拼装。
|
|
29
|
+
|
|
30
30
|
构造 Issue 标题:
|
|
31
31
|
|
|
32
32
|
| task.md `type` | Conventional Commits type |
|
|
@@ -41,45 +41,35 @@ scope 推断:从 `.agents/.airc.json` 的 `labels.in` 字段读取已知模块
|
|
|
41
41
|
|
|
42
42
|
### 3. 构建 Issue 正文
|
|
43
43
|
|
|
44
|
+
> **机械化边界(必须遵守)**:Issue 正文一律由 `ai task issue-body` 命令确定性生成;调用方只把命令 stdout 作为 `gh issue create` 的 `--body-file` 传入,**不得**自行拼装、改写或截断正文。命令只输出 task 标题 / `## 描述` / `## 需求` 对应内容,task.md 其余脚手架段落永不进入 body。
|
|
45
|
+
|
|
44
46
|
Issue 模板检测:按 `.agents/rules/issue-pr-commands.md` 的 "Issue 模板检测" 规则扫描 `.github/ISSUE_TEMPLATE/*.yml`(排除 `config.yml`)。
|
|
45
47
|
|
|
46
48
|
#### 场景 A:检测到匹配模板
|
|
47
49
|
|
|
48
50
|
按模板顶层 `name` 与任务类型挑选最匹配的 form(如任务 `type: bugfix` 优先选名称含 `bug` 的模板);找不到匹配时,回退到通用 form(如 `other.yml`),仍找不到时取目录中第一个 form。
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- `textarea` / `input` 字段:使用 `attributes.label` 作为 markdown 标题,从 task.md 取值
|
|
53
|
-
- `markdown` 字段:跳过(说明文案)
|
|
54
|
-
- `dropdown` / `checkboxes` 字段:跳过
|
|
52
|
+
确定模板文件 `{form-path}` 后,由命令按该 Issue Form 渲染最终正文,并写入正文文件 `{body-file}` 供 §5 使用:
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|---|---|
|
|
60
|
-
| `summary`, `title` | 任务标题 |
|
|
61
|
-
| `description`, `problem`, `what happened`, `issue-description`, `current-content` | 任务描述 |
|
|
62
|
-
| `solution`, `requirements`, `steps`, `suggested-content`, `impact`, `context`, `alternatives`, `expected` | 需求列表(已勾选与未勾选混合,原样保留) |
|
|
63
|
-
| 其它 `textarea` / `input` 字段 | 任务描述;缺失时填 `N/A` |
|
|
54
|
+
```bash
|
|
55
|
+
ai task issue-body {task-id} --template "{form-path}" > "{body-file}"
|
|
56
|
+
```
|
|
64
57
|
|
|
65
|
-
|
|
58
|
+
命令跳过 `markdown` / `dropdown` / `checkboxes` 字段,对 `input` / `textarea` 字段以 `attributes.label` 作标题、按字段 `id` 确定性填入 task 标题 / 描述 / 需求,无可靠映射源的字段填 `N/A`(字段映射表是命令内的单一真源,不在本规则重述)。命令退出码非 0(文件缺失 / 非法 YAML / 无 `body`)时,改用场景 B 命令重新生成 `{body-file}`。
|
|
66
59
|
|
|
67
60
|
#### 场景 B:无模板或解析失败
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```markdown
|
|
72
|
-
## Description
|
|
62
|
+
由命令输出默认正文(仅 `## 描述` + `## 需求`,复选框逐字保留,缺失段落填 `N/A`),写入 `{body-file}`:
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
```bash
|
|
65
|
+
ai task issue-body {task-id} > "{body-file}"
|
|
66
|
+
```
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
#### 红线:禁止把整份 task.md 当 body
|
|
77
69
|
|
|
78
|
-
-
|
|
79
|
-
- [ ] {requirement-2}
|
|
80
|
-
```
|
|
70
|
+
无论场景 A / B,body 只能来自 `ai task issue-body` 的 stdout,**只含 描述 + 需求两段内容**(场景 A 为按模板字段映射后的等价内容)。
|
|
81
71
|
|
|
82
|
-
|
|
72
|
+
错误示范(❌ 禁止):把含 `## 分析` / `## 设计` / `## 实现备注` / `## 审查反馈` / `## 审查分歧账本` / `## 人工裁决` / `## 活动日志` / `## 完成检查清单` 等脚手架段落、以及 `#XXX` 占位的整份 task.md 直接作为 Issue body。这些段落只走 `sync-issue:{task-id}:task` 评论,绝不进 body。
|
|
83
73
|
|
|
84
74
|
### 4. 解析 labels / Issue Type / milestone
|
|
85
75
|
|
|
@@ -137,17 +127,18 @@ Issue 模板检测:按 `.agents/rules/issue-pr-commands.md` 的 "Issue 模板
|
|
|
137
127
|
|
|
138
128
|
### 5. 调用 GitHub CLI 创建 Issue
|
|
139
129
|
|
|
140
|
-
按 `.agents/rules/issue-pr-commands.md` 中的 "创建 Issue"
|
|
130
|
+
按 `.agents/rules/issue-pr-commands.md` 中的 "创建 Issue" 命令执行;正文统一用 §3 生成的 `{body-file}`,覆盖通用命令的 `--body`:
|
|
141
131
|
|
|
142
132
|
```bash
|
|
143
133
|
gh issue create -R "$upstream_repo" \
|
|
144
134
|
--title "{title}" \
|
|
145
|
-
--body "{body}" \
|
|
135
|
+
--body-file "{body-file}" \
|
|
146
136
|
--assignee @me \
|
|
147
137
|
{label-args} \
|
|
148
138
|
{milestone-arg}
|
|
149
139
|
```
|
|
150
140
|
|
|
141
|
+
- `{body-file}` 为 §3 由 `ai task issue-body` 生成的正文文件;**不得**改用 `--body` 自行拼装正文
|
|
151
142
|
- `{label-args}` 由 §4 计算结果展开为多个 `--label "..."`;为空则整体省略
|
|
152
143
|
- `{milestone-arg}` 仅当 `has_triage=true` 且 milestone 非空时展开为 `--milestone "..."`;否则整体省略
|
|
153
144
|
- `--assignee @me` 不做权限预判,失败时静默跳过
|
|
@@ -186,6 +186,12 @@ List PRs:
|
|
|
186
186
|
gh pr list --state {state} --base {base-branch} --json number,title,url,headRefName,baseRefName
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
Find the open PR whose head is the current branch (used by the `commit` push wrap-up):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
gh pr list --head "{branch}" --state open --json number,url --jq '.[0].url // empty'
|
|
193
|
+
```
|
|
194
|
+
|
|
189
195
|
Create a PR:
|
|
190
196
|
|
|
191
197
|
```bash
|
|
@@ -186,6 +186,12 @@ gh pr view {pr-number} --json number,title,body,labels,state,milestone,url,files
|
|
|
186
186
|
gh pr list --state {state} --base {base-branch} --json number,title,url,headRefName,baseRefName
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
按 head 分支查询当前分支是否存在开放 PR(`commit` 推送收尾用):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
gh pr list --head "{branch}" --state open --json number,url --jq '.[0].url // empty'
|
|
193
|
+
```
|
|
194
|
+
|
|
189
195
|
创建 PR:
|
|
190
196
|
|
|
191
197
|
```bash
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Next-Step Output Rule
|
|
2
2
|
|
|
3
|
-
This file defines
|
|
3
|
+
This file defines three **independent** rules for a skill's "notify-user / Next steps" output (the 3rd applies to review-* only); read this file before rendering the final output and apply whichever rules apply:
|
|
4
4
|
|
|
5
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
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
|
+
3. **Pending human-decision pre-block**: applies only to `review-analysis` / `review-plan` / `review-code` when this stage has pending rulings (`{h} > 0`) — expand the pending items before the "Next steps" commands and prompt to resolve them first.
|
|
7
8
|
|
|
8
9
|
## Placeholder semantics
|
|
9
10
|
|
|
@@ -60,3 +61,27 @@ Completed at: YYYY-MM-DD HH:mm:ss
|
|
|
60
61
|
- Value command (local timezone, no offset): `date "+%Y-%m-%d %H:%M:%S"`
|
|
61
62
|
- 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
63
|
- 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.
|
|
64
|
+
|
|
65
|
+
## Pending human-decision pre-block (review-* only, when {h} > 0)
|
|
66
|
+
|
|
67
|
+
This section is a **third standalone rule, co-equal with the two above**, used only by the "notify-user / report conclusion" step of `review-analysis` / `review-plan` / `review-code`.
|
|
68
|
+
|
|
69
|
+
`{h}` has the same meaning as the count line in each review skill's `reference/output-templates.md`: the number of rows in task.md `## 审查分歧账本` (Review Disagreement Ledger) for **this stage** (`stage ∈ {analysis|plan|code}`) whose `status = needs-human-decision` — **pending items only, excluding rows already `human-decided`**.
|
|
70
|
+
|
|
71
|
+
- **`{h} = 0`**: do not emit this block; render "Next steps" exactly as the selected output-templates scenario.
|
|
72
|
+
- **`{h} > 0`**: insert the block below **before** the selected scenario's "Next steps - <stage>" commands; the next-stage commands are still listed after the block.
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
⚠️ Pending human decisions ({h}) — please rule on each before continuing to the next stage:
|
|
76
|
+
- {ledger-id} ({stage}/{severity}): {summary}
|
|
77
|
+
Location: the matching row in task.md `## 审查分歧账本` · Evidence: {evidence}
|
|
78
|
+
…(one entry per status=needs-human-decision row of this stage in task.md `## 审查分歧账本`)
|
|
79
|
+
|
|
80
|
+
To resolve:
|
|
81
|
+
1. In the task.md `## 人工裁决` section, record your ruling and rationale for each item above.
|
|
82
|
+
2. Flip the status of the matching row in `## 审查分歧账本` from `needs-human-decision` to `human-decided`.
|
|
83
|
+
|
|
84
|
+
Note: until all those rows are flipped to `human-decided`, running the next-stage command directly will be blocked by gates such as complete-task (`needs-human-decision` is non-terminal). The next-stage commands are still listed below for use after the decisions are made.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Field values: `{ledger-id}` / `{stage}` / `{severity}` / `{evidence}` come from the same-named columns of the matching `## 审查分歧账本` row; `{summary}` comes from the artifact anchor referenced by `{evidence}` (e.g. the decision title at `plan.md#HD-1`), falling back to a one-line summary of the finding when no anchor title exists.
|