@fitlab-ai/agent-infra 0.7.7 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/cli.ts +42 -15
- package/dist/bin/cli.js +46 -16
- package/dist/lib/decide.js +110 -0
- package/dist/lib/run/host.js +39 -0
- package/dist/lib/run/index.js +193 -0
- package/dist/lib/run/prompt.js +2 -0
- package/dist/lib/run/skills.js +29 -0
- package/dist/lib/run/tui.js +35 -0
- package/dist/lib/sandbox/capture.js +158 -0
- package/dist/lib/sandbox/commands/create.js +58 -9
- package/dist/lib/sandbox/commands/enter.js +7 -3
- package/dist/lib/sandbox/credentials.js +42 -3
- package/dist/lib/sandbox/runtimes/base.dockerfile +13 -0
- package/dist/lib/server/adapters/_contract.js +9 -0
- package/dist/lib/server/adapters/feishu/index.js +40 -0
- package/dist/lib/server/adapters/feishu/transport.js +95 -0
- package/dist/lib/server/auth.js +19 -0
- package/dist/lib/server/config.js +183 -0
- package/dist/lib/server/daemon.js +96 -0
- package/dist/lib/server/index.js +47 -0
- package/dist/lib/server/logger.js +34 -0
- package/dist/lib/server/plugin-loader.js +73 -0
- package/dist/lib/server/process-control.js +200 -0
- package/dist/lib/server/protocol.js +69 -0
- package/dist/lib/server/redact.js +12 -0
- package/dist/lib/server/runner.js +75 -0
- package/dist/lib/server/streamer.js +44 -0
- package/dist/lib/task/commands/decisions.js +258 -0
- package/dist/lib/task/commands/log.js +38 -41
- package/dist/lib/task/commands/status.js +144 -45
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +66 -0
- package/dist/lib/task/sections.js +30 -1
- package/lib/decide.ts +124 -0
- package/lib/run/host.ts +47 -0
- package/lib/run/index.ts +237 -0
- package/lib/run/prompt.ts +1 -0
- package/lib/run/skills.ts +34 -0
- package/lib/run/tui.ts +40 -0
- package/lib/sandbox/capture.ts +215 -0
- package/lib/sandbox/commands/create.ts +64 -12
- package/lib/sandbox/commands/enter.ts +8 -2
- package/lib/sandbox/credentials.ts +57 -3
- package/lib/sandbox/runtimes/base.dockerfile +13 -0
- package/lib/server/adapters/_contract.ts +42 -0
- package/lib/server/adapters/feishu/index.ts +47 -0
- package/lib/server/adapters/feishu/transport.ts +128 -0
- package/lib/server/auth.ts +28 -0
- package/lib/server/config.ts +228 -0
- package/lib/server/daemon.ts +110 -0
- package/lib/server/index.ts +50 -0
- package/lib/server/logger.ts +48 -0
- package/lib/server/plugin-loader.ts +84 -0
- package/lib/server/process-control.ts +225 -0
- package/lib/server/protocol.ts +79 -0
- package/lib/server/redact.ts +12 -0
- package/lib/server/runner.ts +95 -0
- package/lib/server/server.schema.json +127 -0
- package/lib/server/streamer.ts +57 -0
- package/lib/task/commands/decisions.ts +272 -0
- package/lib/task/commands/log.ts +38 -39
- package/lib/task/commands/status.ts +200 -58
- package/lib/task/index.ts +9 -0
- package/lib/task/ledger.ts +73 -0
- package/lib/task/sections.ts +30 -1
- package/package.json +8 -3
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/rules/issue-fields.github.en.md +1 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +5 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +5 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +7 -2
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +7 -2
- package/templates/.agents/rules/pr-sync.github.en.md +3 -3
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -3
- package/templates/.agents/rules/review-handshake.en.md +5 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +5 -1
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/scripts/lib/post-review-commit.js +15 -18
- package/templates/.agents/scripts/validate-artifact.js +8 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +15 -3
- package/templates/.agents/skills/analyze-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -3
- package/templates/.agents/skills/import-codescan/SKILL.en.md +0 -3
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +0 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -2
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +11 -11
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +11 -11
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -4
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +16 -16
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -4
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +5 -5
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +11 -11
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +11 -11
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +5 -5
- package/templates/.agents/templates/task.en.md +3 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -3
- package/templates/.github/workflows/metadata-sync.yml +0 -18
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { commandForEngine } from '../../sandbox/shell.ts';
|
|
3
5
|
import { resolveTaskRef } from '../resolve-ref.ts';
|
|
4
6
|
import { enumerateArtifacts, type Artifact } from '../artifacts.ts';
|
|
5
7
|
import { parseTaskFrontmatter, extractTitle, type Frontmatter } from '../frontmatter.ts';
|
|
6
8
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
9
|
+
import { parseActivityLog, pairEntries } from './log.ts';
|
|
7
10
|
|
|
8
11
|
const USAGE = `Usage: ai task status <N | #N | TASK-id>
|
|
9
12
|
|
|
10
|
-
Prints an aggregated "health check" view for a task: header, metadata,
|
|
11
|
-
artifacts
|
|
13
|
+
Prints an aggregated "health check" view for a task: header, metadata,
|
|
14
|
+
artifacts, workflow/runtime execution state, and git branch state.
|
|
12
15
|
<ref> Bare numeric / '#N' short id, or a full TASK-YYYYMMDD-HHMMSS id.
|
|
13
16
|
|
|
14
|
-
Git
|
|
15
|
-
|
|
17
|
+
Git rows are best-effort: a failed git call degrades that row to '-' without
|
|
18
|
+
failing the command.
|
|
16
19
|
`;
|
|
17
20
|
|
|
18
21
|
const DASH = '-';
|
|
19
22
|
|
|
20
23
|
// Subprocess boundary: the single place this command shells out. Injectable so
|
|
21
|
-
// the collectors below can be unit-tested without spawning git
|
|
24
|
+
// the collectors below can be unit-tested without spawning git. Returns the
|
|
22
25
|
// command's stdout; throws (like execFileSync) on a non-zero exit or spawn error.
|
|
23
26
|
type Runner = (file: string, args: string[]) => string;
|
|
24
27
|
|
|
@@ -27,7 +30,7 @@ function makeRunner(cwd: string): Runner {
|
|
|
27
30
|
execFileSync(file, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
// Run `run` and swallow any failure into null, so a single failing git
|
|
33
|
+
// Run `run` and swallow any failure into null, so a single failing git call
|
|
31
34
|
// degrades only its own field instead of aborting the whole view.
|
|
32
35
|
function tryRun(run: Runner, file: string, args: string[]): string | null {
|
|
33
36
|
try {
|
|
@@ -47,9 +50,7 @@ const METADATA_KEYS = [
|
|
|
47
50
|
'branch',
|
|
48
51
|
'assigned_to',
|
|
49
52
|
'created_at',
|
|
50
|
-
'updated_at'
|
|
51
|
-
'issue_number',
|
|
52
|
-
'pr_status'
|
|
53
|
+
'updated_at'
|
|
53
54
|
] as const;
|
|
54
55
|
|
|
55
56
|
function collectMetadata(fm: Frontmatter): [string, string][] {
|
|
@@ -155,55 +156,176 @@ function collectGit(frontmatterBranch: string, run: Runner): GitInfo {
|
|
|
155
156
|
return { current, frontmatter, match, exists, uncommitted, aheadBehind };
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
type
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
type WorkflowInfo = {
|
|
160
|
+
state: string;
|
|
161
|
+
step: string;
|
|
162
|
+
agent: string;
|
|
163
|
+
startedAt: string;
|
|
164
|
+
doneAt: string;
|
|
165
|
+
stale: string;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const STALE_MS = 60 * 60 * 1000;
|
|
169
|
+
|
|
170
|
+
function parseActivityTime(value: string): number {
|
|
171
|
+
const epoch = Date.parse(value.replace(' ', 'T'));
|
|
172
|
+
return Number.isFinite(epoch) ? epoch : Number.NaN;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function collectWorkflow(content: string, now: Date = new Date()): WorkflowInfo {
|
|
176
|
+
const parsed = parseActivityLog(content);
|
|
177
|
+
if (!parsed.sectionFound || parsed.entries.length === 0) {
|
|
178
|
+
return { state: 'unknown', step: DASH, agent: DASH, startedAt: DASH, doneAt: DASH, stale: DASH };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const rows = pairEntries(parsed.entries);
|
|
182
|
+
const latest = rows.at(-1);
|
|
183
|
+
if (!latest) {
|
|
184
|
+
return { state: 'unknown', step: DASH, agent: DASH, startedAt: DASH, doneAt: DASH, stale: DASH };
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
const inProgress = latest.started !== '' && latest.done === '';
|
|
188
|
+
const state = inProgress ? 'in-progress' : latest.done ? 'idle' : 'unknown';
|
|
189
|
+
let stale = DASH;
|
|
190
|
+
if (inProgress) {
|
|
191
|
+
const started = parseActivityTime(latest.started);
|
|
192
|
+
stale = Number.isFinite(started) ? (now.getTime() - started > STALE_MS ? 'yes' : 'no') : 'unknown';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
state,
|
|
197
|
+
step: latest.step || DASH,
|
|
198
|
+
agent: latest.agent || DASH,
|
|
199
|
+
startedAt: latest.started || DASH,
|
|
200
|
+
doneAt: latest.done || DASH,
|
|
201
|
+
stale
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type RuntimeInfo = {
|
|
206
|
+
mode: string;
|
|
207
|
+
status: string;
|
|
208
|
+
run: string;
|
|
209
|
+
tmux: string;
|
|
210
|
+
startedAt: string;
|
|
211
|
+
finishedAt: string;
|
|
212
|
+
exitCode: string;
|
|
213
|
+
log: string;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
type ManagedRunRecord = {
|
|
217
|
+
run_id: string;
|
|
218
|
+
engine: string;
|
|
219
|
+
container: string;
|
|
220
|
+
run_dir: string;
|
|
221
|
+
status_file: string;
|
|
222
|
+
log_file: string;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
function latestRunRecord(taskDir: string): ManagedRunRecord | null {
|
|
226
|
+
const runsDir = path.join(taskDir, 'runs');
|
|
227
|
+
if (!fs.existsSync(runsDir)) return null;
|
|
228
|
+
const candidates: { path: string; mtimeMs: number }[] = [];
|
|
229
|
+
for (const entry of fs.readdirSync(runsDir)) {
|
|
230
|
+
if (!entry.endsWith('.json')) continue;
|
|
231
|
+
const filePath = path.join(runsDir, entry);
|
|
232
|
+
const stat = fs.statSync(filePath);
|
|
233
|
+
if (stat.isFile()) candidates.push({ path: filePath, mtimeMs: stat.mtimeMs });
|
|
234
|
+
}
|
|
235
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
236
|
+
|
|
237
|
+
for (const candidate of candidates) {
|
|
238
|
+
try {
|
|
239
|
+
const data = JSON.parse(fs.readFileSync(candidate.path, 'utf8'));
|
|
240
|
+
if (
|
|
241
|
+
typeof data?.run_id === 'string' &&
|
|
242
|
+
typeof data?.engine === 'string' &&
|
|
243
|
+
typeof data?.container === 'string' &&
|
|
244
|
+
typeof data?.run_dir === 'string'
|
|
245
|
+
) {
|
|
246
|
+
return {
|
|
247
|
+
run_id: data.run_id,
|
|
248
|
+
engine: data.engine,
|
|
249
|
+
container: data.container,
|
|
250
|
+
run_dir: data.run_dir,
|
|
251
|
+
status_file:
|
|
252
|
+
typeof data.status_file === 'string' ? data.status_file : `${data.run_dir}/status`,
|
|
253
|
+
log_file:
|
|
254
|
+
typeof data.log_file === 'string' ? data.log_file : `${data.run_dir}/output.log`
|
|
255
|
+
};
|
|
191
256
|
}
|
|
257
|
+
} catch {
|
|
258
|
+
// Ignore malformed local records and try the next newest file.
|
|
192
259
|
}
|
|
193
260
|
}
|
|
194
261
|
|
|
195
|
-
return
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function readRuntimeFile(record: ManagedRunRecord, filePath: string, run: Runner): string | null {
|
|
266
|
+
const command = commandForEngine(record.engine, 'docker', ['exec', record.container, 'cat', filePath]);
|
|
267
|
+
const output = tryRun(run, command.cmd, command.args);
|
|
268
|
+
return output === null ? null : output.trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function runtimeValue(record: ManagedRunRecord, name: string, run: Runner): string {
|
|
272
|
+
const output = readRuntimeFile(record, `${record.run_dir}/${name}`, run);
|
|
273
|
+
return output ? output : DASH;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function collectRuntime(taskDir: string, workflow: WorkflowInfo, run: Runner): RuntimeInfo {
|
|
277
|
+
const record = latestRunRecord(taskDir);
|
|
278
|
+
if (!record) {
|
|
279
|
+
return workflow.state === 'in-progress'
|
|
280
|
+
? {
|
|
281
|
+
mode: 'unmanaged',
|
|
282
|
+
status: 'inferred-from-workflow',
|
|
283
|
+
run: DASH,
|
|
284
|
+
tmux: DASH,
|
|
285
|
+
startedAt: DASH,
|
|
286
|
+
finishedAt: DASH,
|
|
287
|
+
exitCode: DASH,
|
|
288
|
+
log: DASH
|
|
289
|
+
}
|
|
290
|
+
: {
|
|
291
|
+
mode: 'none',
|
|
292
|
+
status: DASH,
|
|
293
|
+
run: DASH,
|
|
294
|
+
tmux: DASH,
|
|
295
|
+
startedAt: DASH,
|
|
296
|
+
finishedAt: DASH,
|
|
297
|
+
exitCode: DASH,
|
|
298
|
+
log: DASH
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const status = readRuntimeFile(record, record.status_file, run)?.trim() || 'unknown';
|
|
303
|
+
const session = runtimeValue(record, 'session', run);
|
|
304
|
+
const window = runtimeValue(record, 'window', run);
|
|
305
|
+
const pane = runtimeValue(record, 'pane', run);
|
|
306
|
+
const tmux = session !== DASH && window !== DASH && pane !== DASH ? `${session}:${window}:${pane}` : DASH;
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
mode: 'managed-tmux',
|
|
310
|
+
status,
|
|
311
|
+
run: record.run_id,
|
|
312
|
+
tmux,
|
|
313
|
+
startedAt: runtimeValue(record, 'started_at', run),
|
|
314
|
+
finishedAt: runtimeValue(record, 'finished_at', run),
|
|
315
|
+
exitCode: runtimeValue(record, 'exit_code', run),
|
|
316
|
+
log: record.log_file
|
|
317
|
+
};
|
|
196
318
|
}
|
|
197
319
|
|
|
198
320
|
type StatusModel = {
|
|
199
321
|
taskId: string;
|
|
200
322
|
shortId: string;
|
|
201
323
|
title: string;
|
|
202
|
-
issueNumber: string;
|
|
203
324
|
metadata: [string, string][];
|
|
204
325
|
artifacts: { count: number; groups: { stage: string; files: string[] }[] };
|
|
326
|
+
workflow: WorkflowInfo;
|
|
327
|
+
runtime: RuntimeInfo;
|
|
205
328
|
git: GitInfo;
|
|
206
|
-
platform: PlatformInfo;
|
|
207
329
|
};
|
|
208
330
|
|
|
209
331
|
// Indent each label/value pair by two spaces and pad labels to a common width so
|
|
@@ -228,6 +350,34 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
228
350
|
lines.push(...renderPairs(model.artifacts.groups.map((group) => [group.stage, group.files.join(', ')])));
|
|
229
351
|
}
|
|
230
352
|
|
|
353
|
+
lines.push(
|
|
354
|
+
'',
|
|
355
|
+
'Workflow',
|
|
356
|
+
...renderPairs([
|
|
357
|
+
['state', model.workflow.state],
|
|
358
|
+
['step', model.workflow.step],
|
|
359
|
+
['agent', model.workflow.agent],
|
|
360
|
+
['started_at', model.workflow.startedAt],
|
|
361
|
+
['done_at', model.workflow.doneAt],
|
|
362
|
+
['stale', model.workflow.stale]
|
|
363
|
+
])
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
lines.push(
|
|
367
|
+
'',
|
|
368
|
+
'Runtime',
|
|
369
|
+
...renderPairs([
|
|
370
|
+
['mode', model.runtime.mode],
|
|
371
|
+
['status', model.runtime.status],
|
|
372
|
+
['run', model.runtime.run],
|
|
373
|
+
['tmux', model.runtime.tmux],
|
|
374
|
+
['started_at', model.runtime.startedAt],
|
|
375
|
+
['finished_at', model.runtime.finishedAt],
|
|
376
|
+
['exit_code', model.runtime.exitCode],
|
|
377
|
+
['log', model.runtime.log]
|
|
378
|
+
])
|
|
379
|
+
);
|
|
380
|
+
|
|
231
381
|
lines.push(
|
|
232
382
|
'',
|
|
233
383
|
'Git',
|
|
@@ -241,16 +391,6 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
241
391
|
])
|
|
242
392
|
);
|
|
243
393
|
|
|
244
|
-
const issueLabel = model.issueNumber ? `issue #${model.issueNumber}` : 'issue';
|
|
245
|
-
lines.push(
|
|
246
|
-
'',
|
|
247
|
-
'Platform',
|
|
248
|
-
...renderPairs([
|
|
249
|
-
[issueLabel, model.platform.issue],
|
|
250
|
-
['pr', model.platform.pr]
|
|
251
|
-
])
|
|
252
|
-
);
|
|
253
|
-
|
|
254
394
|
return lines;
|
|
255
395
|
}
|
|
256
396
|
|
|
@@ -272,16 +412,17 @@ function status(args: string[] = []): void {
|
|
|
272
412
|
const fm = parseTaskFrontmatter(content);
|
|
273
413
|
const run = makeRunner(resolved.repoRoot);
|
|
274
414
|
const artifacts = enumerateArtifacts(resolved.taskDir);
|
|
415
|
+
const workflow = collectWorkflow(content);
|
|
275
416
|
|
|
276
417
|
const model: StatusModel = {
|
|
277
418
|
taskId: resolved.taskId,
|
|
278
419
|
shortId: loadShortIdByTaskId(resolved.repoRoot).get(resolved.taskId) ?? DASH,
|
|
279
420
|
title: extractTitle(content),
|
|
280
|
-
issueNumber: fm.issue_number && /^\d+$/.test(fm.issue_number) ? fm.issue_number : '',
|
|
281
421
|
metadata: collectMetadata(fm),
|
|
282
422
|
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
283
|
-
|
|
284
|
-
|
|
423
|
+
workflow,
|
|
424
|
+
runtime: collectRuntime(resolved.taskDir, workflow, run),
|
|
425
|
+
git: collectGit(fm.branch ?? '', run)
|
|
285
426
|
};
|
|
286
427
|
|
|
287
428
|
for (const line of renderStatus(model)) {
|
|
@@ -295,8 +436,9 @@ export {
|
|
|
295
436
|
collectMetadata,
|
|
296
437
|
groupArtifacts,
|
|
297
438
|
collectGit,
|
|
298
|
-
|
|
439
|
+
collectWorkflow,
|
|
440
|
+
collectRuntime,
|
|
299
441
|
renderStatus,
|
|
300
442
|
METADATA_KEYS
|
|
301
443
|
};
|
|
302
|
-
export type { Runner, GitInfo,
|
|
444
|
+
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel };
|
package/lib/task/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ const USAGE = `Usage: ai task <command> [options]
|
|
|
2
2
|
|
|
3
3
|
Commands:
|
|
4
4
|
cat <ref> <artifact | N> Print a task artifact (by name or number)
|
|
5
|
+
decisions, d <ref> [selector] List human-decision (HD-) items, or show one's detail
|
|
5
6
|
files <ref> List artifacts in a task dir (numbered)
|
|
6
7
|
grep <pattern> [ref] [artifact | N] Literal search across task artifacts (omit ref to scan all)
|
|
7
8
|
issue-body <ref> [--template <path>] Print a deterministic Issue body from task.md (描述 + 需求, or an Issue Form)
|
|
@@ -13,6 +14,8 @@ Commands:
|
|
|
13
14
|
Examples:
|
|
14
15
|
ai task cat 11 analysis
|
|
15
16
|
ai task cat 11 3
|
|
17
|
+
ai task decisions 11
|
|
18
|
+
ai task d 11 HD-3 --format markdown
|
|
16
19
|
ai task files 11
|
|
17
20
|
ai task grep resolveArtifact
|
|
18
21
|
ai task grep resolveArtifact 11
|
|
@@ -46,6 +49,12 @@ export async function runTask(args: string[]): Promise<void> {
|
|
|
46
49
|
cat(rest);
|
|
47
50
|
break;
|
|
48
51
|
}
|
|
52
|
+
case 'decisions':
|
|
53
|
+
case 'd': {
|
|
54
|
+
const { decisions } = await import('./commands/decisions.ts');
|
|
55
|
+
decisions(rest);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
49
58
|
case 'files': {
|
|
50
59
|
const { files } = await import('./commands/files.ts');
|
|
51
60
|
files(rest);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Shared parser for the task.md `## 审查分歧账本` (Review Disagreement Ledger).
|
|
2
|
+
// Single source of truth for ledger row parsing across `ai task` commands
|
|
3
|
+
// (log, decisions) — see the plan's D1 constraint "no third parser". The gate
|
|
4
|
+
// parser in `.agents/scripts/validate-artifact.js` is a separate concern and
|
|
5
|
+
// must be kept semantically in sync by hand (noted in review-handshake.md).
|
|
6
|
+
|
|
7
|
+
// The ledger H2 heading is language-dependent (zh template / en template).
|
|
8
|
+
const LEDGER_HEADING_RE = /^##\s+(审查分歧账本|Review Disagreement Ledger)\s*$/;
|
|
9
|
+
const NEXT_H2_RE = /^##\s/;
|
|
10
|
+
|
|
11
|
+
type LedgerRow = {
|
|
12
|
+
id: string;
|
|
13
|
+
stage: string;
|
|
14
|
+
round: string;
|
|
15
|
+
severity: string;
|
|
16
|
+
status: string;
|
|
17
|
+
evidence: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Terminal statuses the completion gates treat as resolved.
|
|
21
|
+
const LEDGER_TERMINAL = new Set(['confirmed', 'closed', 'human-decided']);
|
|
22
|
+
// Statuses that represent an executor-raised human-decision row (pending or done).
|
|
23
|
+
const HUMAN_DECISION_STATUSES = new Set(['needs-human-decision', 'human-decided']);
|
|
24
|
+
|
|
25
|
+
// Parse all rows of the disagreement ledger table. Skips the heading, the
|
|
26
|
+
// header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
|
|
27
|
+
// Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
|
|
28
|
+
function parseLedger(content: string): LedgerRow[] {
|
|
29
|
+
const lines = content.split('\n');
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < lines.length && !LEDGER_HEADING_RE.test(lines[i]!)) i += 1;
|
|
32
|
+
if (i >= lines.length) return [];
|
|
33
|
+
|
|
34
|
+
const rows: LedgerRow[] = [];
|
|
35
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
36
|
+
if (NEXT_H2_RE.test(lines[j]!)) break;
|
|
37
|
+
const line = lines[j]!.trim();
|
|
38
|
+
if (!line.startsWith('|')) continue;
|
|
39
|
+
const cells = line
|
|
40
|
+
.split('|')
|
|
41
|
+
.slice(1, -1)
|
|
42
|
+
.map((cell) => cell.trim());
|
|
43
|
+
if (cells.length < 6) continue;
|
|
44
|
+
if (cells[0] === 'id' || /^-+$/.test(cells[0] ?? '')) continue;
|
|
45
|
+
rows.push({
|
|
46
|
+
id: cells[0]!,
|
|
47
|
+
stage: cells[1]!,
|
|
48
|
+
round: cells[2]!,
|
|
49
|
+
severity: cells[3]!,
|
|
50
|
+
status: cells[4]!,
|
|
51
|
+
evidence: cells[5]!
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Allocate the next globally-unique human-decision id. Scans every `HD-<n>`
|
|
58
|
+
// already present in the ledger (across all stages) and returns `HD-{max+1}`,
|
|
59
|
+
// or `HD-1` when none exist. Global monotonic allocation prevents id collisions
|
|
60
|
+
// across the analysis / plan / code stages (plan PL-2).
|
|
61
|
+
function nextHdId(rows: readonly LedgerRow[]): string {
|
|
62
|
+
let max = 0;
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const m = /^HD-(\d+)$/.exec(row.id);
|
|
65
|
+
if (!m) continue;
|
|
66
|
+
const n = Number.parseInt(m[1]!, 10);
|
|
67
|
+
if (n > max) max = n;
|
|
68
|
+
}
|
|
69
|
+
return `HD-${max + 1}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { parseLedger, nextHdId, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES };
|
|
73
|
+
export type { LedgerRow };
|
package/lib/task/sections.ts
CHANGED
|
@@ -41,4 +41,33 @@ function findSectionHeading(content: string, aliases: string[]): string {
|
|
|
41
41
|
return aliases[0]!;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Return the body of the first `### {headingPrefix}` sub-section, from the
|
|
46
|
+
* heading line (inclusive) to the next `### ` / `## ` heading or EOF. Used to
|
|
47
|
+
* pull a single `### HD-N` human-decision detail block out of an artifact. The
|
|
48
|
+
* prefix must be followed by a word boundary so `HD-1` does not match `HD-10`
|
|
49
|
+
* (e.g. `### HD-1`, `### HD-1:标题`, `### HD-1 [needs-human-decision]`). Leading
|
|
50
|
+
* and trailing blank lines are trimmed. Returns '' when no match is present.
|
|
51
|
+
*/
|
|
52
|
+
function extractSubSection(content: string, headingPrefix: string): string {
|
|
53
|
+
const lines = content.split('\n');
|
|
54
|
+
const headRe = new RegExp(`^###\\s+${escapeRegExp(headingPrefix)}(?![\\w-])`);
|
|
55
|
+
let start = -1;
|
|
56
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
57
|
+
if (headRe.test(lines[i]!.trim())) {
|
|
58
|
+
start = i;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (start === -1) return '';
|
|
63
|
+
let end = lines.length;
|
|
64
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
65
|
+
if (/^###?\s+/.test(lines[i]!)) {
|
|
66
|
+
end = i;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { extractSection, findSectionHeading, extractSubSection };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"installer"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@clack/prompts": "1.
|
|
46
|
+
"@clack/prompts": "1.7.0",
|
|
47
|
+
"@larksuiteoapi/node-sdk": "^1.68.0",
|
|
47
48
|
"cross-spawn": "^7.0.6",
|
|
48
49
|
"picocolors": "1.1.1",
|
|
49
50
|
"semver": "^7.8.1",
|
|
@@ -57,17 +58,21 @@
|
|
|
57
58
|
"typecheck": "tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.jschecks.json --noEmit",
|
|
58
59
|
"test:smoke": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/unit/**/*.test.ts\"",
|
|
59
60
|
"test:core": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/unit/**/*.test.ts\" \"tests/integration/**/*.test.ts\"",
|
|
61
|
+
"check": "npm run typecheck && npm test",
|
|
60
62
|
"test": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/**/*.test.ts\"",
|
|
61
63
|
"test:coverage": "npm run build && node --experimental-strip-types --no-warnings --test --experimental-test-coverage \"--test-coverage-exclude=tests/**\" --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage.lcov \"tests/**/*.test.ts\"",
|
|
62
64
|
"prepublishOnly": "npm run build && node --experimental-strip-types --no-warnings --test \"tests/**/*.test.ts\""
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@types/cross-spawn": "^6.0.6",
|
|
66
|
-
"@types/node": "^
|
|
68
|
+
"@types/node": "^22.20.0",
|
|
67
69
|
"@types/semver": "^7.7.1",
|
|
68
70
|
"typescript": "~6.0"
|
|
69
71
|
},
|
|
70
72
|
"optionalDependencies": {
|
|
71
73
|
"@lydell/node-pty": "^1.2.0-beta.12"
|
|
74
|
+
},
|
|
75
|
+
"overrides": {
|
|
76
|
+
"axios": "1.18.1"
|
|
72
77
|
}
|
|
73
78
|
}
|
|
@@ -47,8 +47,8 @@ cp .agents/templates/task.md .agents/workspace/active/task-001.md
|
|
|
47
47
|
|
|
48
48
|
```yaml
|
|
49
49
|
id: task-001
|
|
50
|
-
type: feature # feature | bugfix | refactor | docs |
|
|
51
|
-
status:
|
|
50
|
+
type: feature # feature | bugfix | refactor | docs | chore
|
|
51
|
+
status: active # active | blocked | completed
|
|
52
52
|
assigned_to: claude # claude | codex | gemini | opencode | human
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -29,7 +29,7 @@ Localized option input may be normalized before writing:
|
|
|
29
29
|
| `中` | `Medium` |
|
|
30
30
|
| `低` | `Low` |
|
|
31
31
|
|
|
32
|
-
AI agents may infer `priority` and `effort` from the title and description when creating or refining tasks,
|
|
32
|
+
AI agents may infer `priority` and `effort` from the title and description when creating or refining tasks. Date fields are factual, not estimated: `start_date` is written by the analyze stage (the analysis start date) and `target_date` by the complete stage (the completion date). Human edits in `task.md` take precedence.
|
|
33
33
|
|
|
34
34
|
## GraphQL Reference
|
|
35
35
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
| `中` | `Medium` |
|
|
30
30
|
| `低` | `Low` |
|
|
31
31
|
|
|
32
|
-
AI agent 在创建或修订任务时可根据标题与描述推断 `priority` 和 `effort
|
|
32
|
+
AI agent 在创建或修订任务时可根据标题与描述推断 `priority` 和 `effort`。日期字段为事实值、不估算:`start_date` 由 analyze 阶段写入(= 分析开始日),`target_date` 由 complete 阶段写入(= 完成日)。`task.md` 中人工填写的值优先。
|
|
33
33
|
|
|
34
34
|
## GraphQL 参考
|
|
35
35
|
|
|
@@ -59,7 +59,7 @@ Completed at: YYYY-MM-DD HH:mm:ss
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
- Value command (local timezone, no offset): `date "+%Y-%m-%d %H:%M:%S"`
|
|
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
|
|
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 manual-validation reminder), the completion line goes after that reminder.
|
|
63
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
64
|
|
|
65
65
|
## Pending human-decision pre-block (review-* only, when {h} > 0)
|
|
@@ -77,6 +77,10 @@ This section is a **third standalone rule, co-equal with the two above**, used o
|
|
|
77
77
|
Location: the matching row in task.md `## 审查分歧账本` · Evidence: {evidence}
|
|
78
78
|
…(one entry per status=needs-human-decision row of this stage in task.md `## 审查分歧账本`)
|
|
79
79
|
|
|
80
|
+
View details:
|
|
81
|
+
- All pending decisions: ai task decisions {task-ref}
|
|
82
|
+
- A single item's full background/options/impact/recommendation: ai task decisions {task-ref} <ordinal|HD-id>
|
|
83
|
+
|
|
80
84
|
To resolve:
|
|
81
85
|
1. In the task.md `## 人工裁决` section, record your ruling and rationale for each item above.
|
|
82
86
|
2. Flip the status of the matching row in `## 审查分歧账本` from `needs-human-decision` to `human-decided`.
|
|
@@ -59,7 +59,7 @@ Completed at: YYYY-MM-DD HH:mm:ss
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
- 取值命令(本地时区、不带偏移):`date "+%Y-%m-%d %H:%M:%S"`
|
|
62
|
-
- 位置:必须是整段面向用户输出的最后一行,排在所有「下一步」命令之后。若某场景在命令之后还有条件性提醒行(如
|
|
62
|
+
- 位置:必须是整段面向用户输出的最后一行,排在所有「下一步」命令之后。若某场景在命令之后还有条件性提醒行(如 manual-validation 提醒),收尾行排在该提醒行之后。
|
|
63
63
|
- 该行只用于终端扫视,不写入任何产物文件或 Issue/PR 评论;完成时刻的单一事实源仍是 task.md 的 Activity Log。
|
|
64
64
|
|
|
65
65
|
## 人工裁决待办前置块(review-* 专用,{h} > 0 时)
|
|
@@ -77,6 +77,10 @@ Completed at: YYYY-MM-DD HH:mm:ss
|
|
|
77
77
|
位置:task.md `## 审查分歧账本` 对应行 · 证据:{evidence}
|
|
78
78
|
…(task.md `## 审查分歧账本` 中本阶段每个 status=needs-human-decision 行一条)
|
|
79
79
|
|
|
80
|
+
查看详情:
|
|
81
|
+
- 全部待裁决项:ai task decisions {task-ref}
|
|
82
|
+
- 单项完整背景/选项/影响/建议:ai task decisions {task-ref} <序号|HD-id>
|
|
83
|
+
|
|
80
84
|
完成裁决:
|
|
81
85
|
1. 在 task.md `## 人工裁决` 段,逐项记录你对上述裁决项的裁定与理由。
|
|
82
86
|
2. 把 `## 审查分歧账本` 中对应行的 status 由 `needs-human-decision` 翻为 `human-decided`。
|
|
@@ -55,7 +55,10 @@ For every SKILL execution context not covered by any exemption above, the defaul
|
|
|
55
55
|
|
|
56
56
|
## Key Design Decision Marking And Ledgering
|
|
57
57
|
|
|
58
|
-
When an open question is a key design decision that needs human judgment, the executor must
|
|
58
|
+
When an open question is a key design decision that needs human judgment, the executor must:
|
|
59
|
+
|
|
60
|
+
1. Write the decision's **detail block** (background / options / impact / recommendation) into the artifact's `## 人工裁决待办` (Pending Human Decisions) section, with a heading like `### HD-N:<title> [needs-human-decision]`; ordinary assumptions / open questions still go under `## Assumptions` / `## Open Questions` (`## 假设` / `## 未决问题`), and `## Open Questions` may keep a one-line pointer to the matching `### HD-N`.
|
|
61
|
+
2. Upsert the matching `HD-` row in task.md `## Review Disagreement Ledger` according to `.agents/rules/review-handshake.md`, with `evidence` pointing to the stable anchor `<artifact>#HD-N`. The `HD-N` number is **globally unique** (scan existing `HD-(\d+)` in the ledger and take max+1, monotonically increasing across analysis / plan / code, never reused); see review-handshake.md for the allocation rule.
|
|
59
62
|
|
|
60
63
|
Use these checks together:
|
|
61
64
|
|
|
@@ -64,6 +67,8 @@ Use these checks together:
|
|
|
64
67
|
- **Small-impact exemption**: if it is only a local, reversible, low-cost execution detail, record it under `## Assumptions` instead of upgrading it to a human ruling.
|
|
65
68
|
- **Fallback**: when unsure whether it is key, treat it as key; `review-*` must check whether the executor missed any `[needs-human-decision]` markings that should have been upgraded.
|
|
66
69
|
|
|
70
|
+
> Use `ai task decisions <task-ref>` to view all pending decisions of a task and their detail blocks.
|
|
71
|
+
|
|
67
72
|
## Human Review Checkpoint Semantics
|
|
68
73
|
|
|
69
74
|
A mandatory human review checkpoint means:
|
|
@@ -71,7 +76,7 @@ A mandatory human review checkpoint means:
|
|
|
71
76
|
- Stop after producing the artifact: once the skill finishes an artifact such as `plan.md`, end the current invocation and wait for the user to explicitly trigger the next skill command
|
|
72
77
|
- Do not pause mid-process to ask for input: do not insert interruptions such as "Do you prefer option A or B?" between execution steps
|
|
73
78
|
|
|
74
|
-
If a key decision needs human judgment during execution, follow the
|
|
79
|
+
If a key decision needs human judgment during execution, follow the "Key Design Decision Marking And Ledgering" rule above: write the detail block into the artifact's `## 人工裁决待办` (Pending Human Decisions) section as `### HD-N` and upsert the `HD-` ledger row, for the user to address at the review checkpoint; ordinary open questions still go to `## Open Questions` / `未决问题`.
|
|
75
80
|
|
|
76
81
|
## Anchor Location
|
|
77
82
|
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
|
|
56
56
|
## 关键设计决策标记与落账
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
当未决问题属于需要人工裁定的关键设计决策时,执行方必须:
|
|
59
|
+
|
|
60
|
+
1. 把该决策的**详情块**(背景 / 选项 / 影响 / 推荐)写入产物的 `## 人工裁决待办` 段,标题形如 `### HD-N:<标题> [needs-human-decision]`;普通假设 / 未决问题仍分别写 `## 假设` / `## 未决问题`(`## Assumptions` / `## Open Questions`),`## 未决问题` 可保留一行指针指向对应 `### HD-N`。
|
|
61
|
+
2. 按 `.agents/rules/review-handshake.md` 在 task.md `## 审查分歧账本` upsert 对应 `HD-` 行,evidence 指向稳定锚点 `<artifact>#HD-N`。`HD-N` 编号**全局唯一**(扫描账本已有 `HD-(\d+)` 取 max+1,跨 analysis / plan / code 单调递增、禁止复用),分配规则详见 review-handshake.md。
|
|
59
62
|
|
|
60
63
|
判定时同时使用以下检查:
|
|
61
64
|
|
|
@@ -64,6 +67,8 @@
|
|
|
64
67
|
- **小影响豁免**:若它只是局部、可逆、低成本的执行细节,写入 `## 假设` 即可,不升级为人工裁决。
|
|
65
68
|
- **兜底**:无法判断是否关键时按关键处理;`review-*` 需要复核执行方是否漏标应升级的 `[needs-human-decision]`。
|
|
66
69
|
|
|
70
|
+
> 用户可用 `ai task decisions <task-ref>` 查看某任务全部待裁决项及其详情块。
|
|
71
|
+
|
|
67
72
|
## 人工审查检查点语义
|
|
68
73
|
|
|
69
74
|
「强制性人工审查检查点」(mandatory human review checkpoint)的语义是:
|
|
@@ -71,7 +76,7 @@
|
|
|
71
76
|
- **产出后停止**:技能完成产物(如 `plan.md`)后立即结束本轮调用,等待用户主动触发下一个技能命令
|
|
72
77
|
- **不是过程中暂停征求意见**:不允许在执行步骤之间插入「请问您倾向 A 还是 B?」之类的中断
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
如果在执行过程中发现需要用户裁定的关键决策,按上文「关键设计决策标记与落账」处理:详情块写入产物 `## 人工裁决待办` 的 `### HD-N`、回写 `HD-` 账本行,由用户在审查检查点统一回应;普通未决问题仍写 `## 未决问题` / `Open Questions`。
|
|
75
80
|
|
|
76
81
|
## 锚点位置
|
|
77
82
|
|