@fitlab-ai/agent-infra 0.8.1 → 0.8.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/cli.ts +3 -3
- package/dist/bin/cli.js +4 -4
- package/dist/lib/cp.js +1 -1
- package/dist/lib/decide.js +2 -2
- package/dist/lib/defaults.json +2 -0
- package/dist/lib/init.js +7 -7
- package/dist/lib/merge.js +1 -1
- package/dist/lib/prompt.js +1 -1
- package/dist/lib/run/index.js +7 -7
- package/dist/lib/run/prompt.js +1 -1
- package/dist/lib/sandbox/capture.js +5 -5
- package/dist/lib/sandbox/clipboard/bridge.js +48 -13
- package/dist/lib/sandbox/clipboard/index.js +5 -6
- package/dist/lib/sandbox/clipboard/linux.js +91 -0
- package/dist/lib/sandbox/clipboard/paths.js +1 -1
- package/dist/lib/sandbox/clipboard/win32.js +144 -0
- package/dist/lib/sandbox/commands/create.js +101 -66
- package/dist/lib/sandbox/commands/enter.js +9 -9
- package/dist/lib/sandbox/commands/list-running.js +2 -2
- package/dist/lib/sandbox/commands/ls.js +7 -7
- package/dist/lib/sandbox/commands/prune.js +8 -8
- package/dist/lib/sandbox/commands/rebuild.js +38 -50
- package/dist/lib/sandbox/commands/refresh.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +11 -11
- package/dist/lib/sandbox/commands/show.js +4 -4
- package/dist/lib/sandbox/commands/start.js +4 -4
- package/dist/lib/sandbox/commands/vm.js +4 -4
- package/dist/lib/sandbox/config.js +11 -5
- package/dist/lib/sandbox/constants.js +4 -1
- package/dist/lib/sandbox/credentials.js +1 -1
- package/dist/lib/sandbox/dotfiles.js +1 -1
- package/dist/lib/sandbox/engine.js +3 -3
- package/dist/lib/sandbox/engines/index.js +5 -5
- package/dist/lib/sandbox/engines/wsl2-paths.js +1 -1
- package/dist/lib/sandbox/image-build.js +80 -0
- package/dist/lib/sandbox/image-prune.js +2 -2
- package/dist/lib/sandbox/index.js +10 -10
- package/dist/lib/sandbox/managed-fs.js +1 -1
- package/dist/lib/sandbox/readme-scaffold.js +1 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/dist/lib/sandbox/tools.js +11 -2
- package/dist/lib/server/adapters/feishu/index.js +12 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +9 -2
- package/dist/lib/server/daemon.js +64 -43
- package/dist/lib/server/display.js +83 -0
- package/dist/lib/server/index.js +2 -2
- package/dist/lib/server/process-control.js +2 -2
- package/dist/lib/server/protocol.js +22 -6
- package/dist/lib/server/streamer.js +5 -4
- package/dist/lib/task/commands/cat.js +2 -2
- package/dist/lib/task/commands/decisions.js +4 -4
- package/dist/lib/task/commands/files.js +3 -3
- package/dist/lib/task/commands/grep.js +3 -3
- package/dist/lib/task/commands/issue-body.js +4 -4
- package/dist/lib/task/commands/log.js +5 -5
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/commands/show.js +1 -1
- package/dist/lib/task/commands/status.js +71 -25
- package/dist/lib/task/index.js +9 -9
- package/dist/lib/task/resolve-ref.js +1 -1
- package/dist/lib/task/workflow-warnings.js +94 -0
- package/dist/lib/update.js +35 -4
- package/lib/defaults.json +2 -0
- package/lib/sandbox/clipboard/bridge.ts +50 -8
- package/lib/sandbox/clipboard/index.ts +5 -6
- package/lib/sandbox/clipboard/linux.ts +124 -0
- package/lib/sandbox/clipboard/win32.ts +173 -0
- package/lib/sandbox/commands/create.ts +123 -65
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +14 -2
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/node20.dockerfile +1 -1
- package/lib/sandbox/runtimes/node22.dockerfile +1 -1
- package/lib/sandbox/tools.ts +9 -0
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +12 -2
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +19 -3
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- package/lib/server/protocol.ts +20 -4
- package/lib/server/streamer.ts +5 -4
- package/lib/task/commands/log.ts +3 -3
- package/lib/task/commands/status.ts +102 -21
- package/lib/task/workflow-warnings.ts +121 -0
- package/lib/update.ts +34 -0
- package/package.json +3 -3
- package/templates/.agents/README.en.md +3 -3
- package/templates/.agents/README.zh-CN.md +3 -3
- package/templates/.agents/rules/create-issue.github.en.md +3 -3
- package/templates/.agents/rules/create-issue.github.zh-CN.md +3 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +7 -2
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +7 -2
- package/templates/.agents/rules/issue-sync.github.en.md +13 -0
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +13 -0
- package/templates/.agents/rules/next-step-output.en.md +15 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +15 -1
- package/templates/.agents/rules/pr-sync.github.en.md +17 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +17 -2
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +120 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +118 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +118 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +49 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +48 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +60 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +87 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +33 -1
- package/templates/.agents/templates/task.en.md +7 -0
- package/templates/.agents/templates/task.zh-CN.md +7 -0
- package/templates/.claude/commands/complete-manual-validation.en.md +9 -0
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +9 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +8 -0
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +8 -0
- package/templates/.github/workflows/metadata-sync.yml +5 -0
- package/templates/.opencode/commands/complete-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/complete-manual-validation.zh-CN.md +11 -0
|
@@ -6,7 +6,9 @@ import { resolveTaskRef } from '../resolve-ref.ts';
|
|
|
6
6
|
import { enumerateArtifacts, type Artifact } from '../artifacts.ts';
|
|
7
7
|
import { parseTaskFrontmatter, extractTitle, type Frontmatter } from '../frontmatter.ts';
|
|
8
8
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
9
|
+
import { getOpenWorkflowWarnings, formatWorkflowWarningSummary, type WorkflowWarning } from '../workflow-warnings.ts';
|
|
9
10
|
import { parseActivityLog, pairEntries } from './log.ts';
|
|
11
|
+
import { statusCard, type DisplayMessage } from '../../server/display.ts';
|
|
10
12
|
|
|
11
13
|
const USAGE = `Usage: ai task status <N | #N | TASK-id>
|
|
12
14
|
|
|
@@ -322,12 +324,28 @@ type StatusModel = {
|
|
|
322
324
|
shortId: string;
|
|
323
325
|
title: string;
|
|
324
326
|
metadata: [string, string][];
|
|
327
|
+
workflowWarnings: WorkflowWarning[];
|
|
325
328
|
artifacts: { count: number; groups: { stage: string; files: string[] }[] };
|
|
326
329
|
workflow: WorkflowInfo;
|
|
327
330
|
runtime: RuntimeInfo;
|
|
328
331
|
git: GitInfo;
|
|
329
332
|
};
|
|
330
333
|
|
|
334
|
+
type BuildStatusModelInput = {
|
|
335
|
+
taskId: string;
|
|
336
|
+
taskDir: string;
|
|
337
|
+
taskMdPath: string;
|
|
338
|
+
repoRoot: string;
|
|
339
|
+
shortId?: string;
|
|
340
|
+
run?: Runner;
|
|
341
|
+
now?: Date;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
type BuildStatusModelOptions = {
|
|
345
|
+
run?: Runner;
|
|
346
|
+
now?: Date;
|
|
347
|
+
};
|
|
348
|
+
|
|
331
349
|
// Indent each label/value pair by two spaces and pad labels to a common width so
|
|
332
350
|
// every section reads as an aligned "key value" block.
|
|
333
351
|
function renderPairs(rows: [string, string][]): string[] {
|
|
@@ -343,6 +361,14 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
343
361
|
|
|
344
362
|
lines.push('', 'Metadata', ...renderPairs(model.metadata));
|
|
345
363
|
|
|
364
|
+
if (model.workflowWarnings.length > 0) {
|
|
365
|
+
lines.push(
|
|
366
|
+
'',
|
|
367
|
+
`Workflow Warnings (${model.workflowWarnings.length} open)`,
|
|
368
|
+
...formatWorkflowWarningSummary(model.workflowWarnings).map((line) => ` ${line}`)
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
346
372
|
lines.push('', `Artifacts (${model.artifacts.count})`);
|
|
347
373
|
if (model.artifacts.groups.length === 0) {
|
|
348
374
|
lines.push(' (none)');
|
|
@@ -394,36 +420,89 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
394
420
|
return lines;
|
|
395
421
|
}
|
|
396
422
|
|
|
397
|
-
function
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const resolved = resolveTaskRef(args[0]!);
|
|
405
|
-
if (!resolved.ok) {
|
|
406
|
-
process.stderr.write(`ai task status: ${resolved.message}\n`);
|
|
407
|
-
process.exitCode = 1;
|
|
408
|
-
return;
|
|
423
|
+
function modelTone(model: StatusModel): 'info' | 'success' | 'warning' | 'danger' | 'running' {
|
|
424
|
+
if (model.workflow.state === 'in-progress') return model.workflow.stale === 'yes' ? 'warning' : 'running';
|
|
425
|
+
if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed')) return 'success';
|
|
426
|
+
if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
|
|
427
|
+
return 'danger';
|
|
409
428
|
}
|
|
429
|
+
return 'info';
|
|
430
|
+
}
|
|
410
431
|
|
|
411
|
-
|
|
432
|
+
function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
433
|
+
const content = fs.readFileSync(input.taskMdPath, 'utf8');
|
|
412
434
|
const fm = parseTaskFrontmatter(content);
|
|
413
|
-
const run = makeRunner(
|
|
414
|
-
const artifacts = enumerateArtifacts(
|
|
415
|
-
const workflow = collectWorkflow(content);
|
|
435
|
+
const run = input.run ?? makeRunner(input.repoRoot);
|
|
436
|
+
const artifacts = enumerateArtifacts(input.taskDir);
|
|
437
|
+
const workflow = collectWorkflow(content, input.now);
|
|
416
438
|
|
|
417
|
-
|
|
418
|
-
taskId:
|
|
419
|
-
shortId: loadShortIdByTaskId(
|
|
439
|
+
return {
|
|
440
|
+
taskId: input.taskId,
|
|
441
|
+
shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
|
|
420
442
|
title: extractTitle(content),
|
|
421
443
|
metadata: collectMetadata(fm),
|
|
444
|
+
workflowWarnings: getOpenWorkflowWarnings(content),
|
|
422
445
|
artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
|
|
423
446
|
workflow,
|
|
424
|
-
runtime: collectRuntime(
|
|
447
|
+
runtime: collectRuntime(input.taskDir, workflow, run),
|
|
425
448
|
git: collectGit(fm.branch ?? '', run)
|
|
426
449
|
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function buildStatusModel(ref: string, options?: BuildStatusModelOptions): StatusModel;
|
|
453
|
+
function buildStatusModel(input: BuildStatusModelInput): StatusModel;
|
|
454
|
+
function buildStatusModel(
|
|
455
|
+
refOrInput: string | BuildStatusModelInput,
|
|
456
|
+
options: BuildStatusModelOptions = {}
|
|
457
|
+
): StatusModel {
|
|
458
|
+
if (typeof refOrInput !== 'string') {
|
|
459
|
+
return buildFromResolved(refOrInput);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const resolved = resolveTaskRef(refOrInput);
|
|
463
|
+
if (!resolved.ok) {
|
|
464
|
+
throw new Error(resolved.message);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return buildFromResolved({
|
|
468
|
+
taskId: resolved.taskId,
|
|
469
|
+
taskDir: resolved.taskDir,
|
|
470
|
+
taskMdPath: resolved.taskMdPath,
|
|
471
|
+
repoRoot: resolved.repoRoot,
|
|
472
|
+
run: options.run,
|
|
473
|
+
now: options.now
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function statusModelToDisplay(model: StatusModel): DisplayMessage {
|
|
478
|
+
return statusCard(
|
|
479
|
+
`Task ${model.taskId} (${model.shortId})`,
|
|
480
|
+
modelTone(model),
|
|
481
|
+
[
|
|
482
|
+
['workflow', model.workflow.state],
|
|
483
|
+
['step', model.workflow.step],
|
|
484
|
+
['runtime', model.runtime.status],
|
|
485
|
+
['git', model.git.uncommitted]
|
|
486
|
+
],
|
|
487
|
+
renderStatus(model).join('\n')
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function status(args: string[] = []): void {
|
|
492
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
493
|
+
process.stdout.write(USAGE);
|
|
494
|
+
if (args.length === 0) process.exitCode = 1;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let model: StatusModel;
|
|
499
|
+
try {
|
|
500
|
+
model = buildStatusModel(args[0]!);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
process.stderr.write(`ai task status: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
503
|
+
process.exitCode = 1;
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
427
506
|
|
|
428
507
|
for (const line of renderStatus(model)) {
|
|
429
508
|
process.stdout.write(`${line}\n`);
|
|
@@ -433,12 +512,14 @@ function status(args: string[] = []): void {
|
|
|
433
512
|
export {
|
|
434
513
|
status,
|
|
435
514
|
makeRunner,
|
|
515
|
+
buildStatusModel,
|
|
436
516
|
collectMetadata,
|
|
437
517
|
groupArtifacts,
|
|
438
518
|
collectGit,
|
|
439
519
|
collectWorkflow,
|
|
440
520
|
collectRuntime,
|
|
441
521
|
renderStatus,
|
|
522
|
+
statusModelToDisplay,
|
|
442
523
|
METADATA_KEYS
|
|
443
524
|
};
|
|
444
|
-
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel };
|
|
525
|
+
export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel, BuildStatusModelInput };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { extractSection } from './sections.ts';
|
|
2
|
+
|
|
3
|
+
const WORKFLOW_WARNING_HEADINGS = ['工作流告警', 'Workflow Warnings'];
|
|
4
|
+
const WORKFLOW_WARNING_STATUSES = new Set(['open', 'resolved', 'ignored']);
|
|
5
|
+
const WORKFLOW_WARNING_SEVERITIES = new Set(['IMPORTANT', 'ACTION_REQUIRED']);
|
|
6
|
+
|
|
7
|
+
type WorkflowWarning = {
|
|
8
|
+
id: string;
|
|
9
|
+
time: string;
|
|
10
|
+
step: string;
|
|
11
|
+
severity: string;
|
|
12
|
+
code: string;
|
|
13
|
+
status: string;
|
|
14
|
+
target: string;
|
|
15
|
+
message: string;
|
|
16
|
+
action: string;
|
|
17
|
+
resolvedAt: string;
|
|
18
|
+
resolution: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function splitTableRow(line: string): string[] {
|
|
22
|
+
let value = line.trim();
|
|
23
|
+
if (!value.startsWith('|')) return [];
|
|
24
|
+
value = value.replace(/^\|/, '').replace(/\|$/, '');
|
|
25
|
+
|
|
26
|
+
const cells: string[] = [];
|
|
27
|
+
let cell = '';
|
|
28
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
+
const char = value[index]!;
|
|
30
|
+
if (char === '|' && !isEscapedAt(value, index)) {
|
|
31
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
32
|
+
cell = '';
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
cell += char;
|
|
36
|
+
}
|
|
37
|
+
cells.push(unescapeTableCell(cell.trim()));
|
|
38
|
+
return cells;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unescapeTableCell(value: string): string {
|
|
42
|
+
let output = '';
|
|
43
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
44
|
+
const char = value[index]!;
|
|
45
|
+
const next = value[index + 1];
|
|
46
|
+
if (char === '\\' && (next === '\\' || next === '|')) {
|
|
47
|
+
output += next;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
output += char;
|
|
52
|
+
}
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isEscapedAt(value: string, index: number): boolean {
|
|
57
|
+
let backslashes = 0;
|
|
58
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor -= 1) {
|
|
59
|
+
backslashes += 1;
|
|
60
|
+
}
|
|
61
|
+
return backslashes % 2 === 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isSeparatorRow(cells: string[]): boolean {
|
|
65
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses well-formed workflow warning rows. Malformed rows are intentionally
|
|
70
|
+
* ignored here; validate-artifact is responsible for reporting structure errors.
|
|
71
|
+
*/
|
|
72
|
+
function parseWorkflowWarnings(content: string): WorkflowWarning[] {
|
|
73
|
+
const section = extractSection(content, WORKFLOW_WARNING_HEADINGS);
|
|
74
|
+
if (!section) return [];
|
|
75
|
+
|
|
76
|
+
const rows: WorkflowWarning[] = [];
|
|
77
|
+
for (const rawLine of section.split(/\r?\n/)) {
|
|
78
|
+
const cells = splitTableRow(rawLine);
|
|
79
|
+
if (cells.length === 0) continue;
|
|
80
|
+
if ((cells[0] ?? '').toLowerCase() === 'id') continue;
|
|
81
|
+
if (isSeparatorRow(cells)) continue;
|
|
82
|
+
if (cells.length < 11) continue;
|
|
83
|
+
|
|
84
|
+
rows.push({
|
|
85
|
+
id: cells[0] ?? '',
|
|
86
|
+
time: cells[1] ?? '',
|
|
87
|
+
step: cells[2] ?? '',
|
|
88
|
+
severity: cells[3] ?? '',
|
|
89
|
+
code: cells[4] ?? '',
|
|
90
|
+
status: cells[5] ?? '',
|
|
91
|
+
target: cells[6] ?? '',
|
|
92
|
+
message: cells[7] ?? '',
|
|
93
|
+
action: cells[8] ?? '',
|
|
94
|
+
resolvedAt: cells[9] ?? '',
|
|
95
|
+
resolution: cells[10] ?? ''
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return rows;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getOpenWorkflowWarnings(content: string): WorkflowWarning[] {
|
|
102
|
+
return parseWorkflowWarnings(content).filter((warning) => warning.status === 'open');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatWorkflowWarningSummary(warnings: readonly WorkflowWarning[]): string[] {
|
|
106
|
+
return warnings.map((warning) => {
|
|
107
|
+
const target = warning.target ? ` ${warning.target}` : '';
|
|
108
|
+
const action = warning.action ? ` - ${warning.action}` : '';
|
|
109
|
+
return `${warning.id} [${warning.severity}] ${warning.code}${target}${action}`;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
WORKFLOW_WARNING_HEADINGS,
|
|
115
|
+
WORKFLOW_WARNING_STATUSES,
|
|
116
|
+
WORKFLOW_WARNING_SEVERITIES,
|
|
117
|
+
parseWorkflowWarnings,
|
|
118
|
+
getOpenWorkflowWarnings,
|
|
119
|
+
formatWorkflowWarningSummary
|
|
120
|
+
};
|
|
121
|
+
export type { WorkflowWarning };
|
package/lib/update.ts
CHANGED
|
@@ -40,6 +40,9 @@ const defaults = JSON.parse(
|
|
|
40
40
|
|
|
41
41
|
const CONFIG_DIR = '.agents';
|
|
42
42
|
const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
|
|
43
|
+
const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
|
|
44
|
+
const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
|
|
45
|
+
const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
|
|
43
46
|
|
|
44
47
|
// One-time migration of the legacy project-level PR switch to the three-state
|
|
45
48
|
// `prFlow` preference. `true` (the old default / "PR flow on") maps to the
|
|
@@ -60,6 +63,26 @@ function migratePrFlow(config: UpdateConfig): 'required' | 'disabled' | null {
|
|
|
60
63
|
return null;
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
function isLegacyDefaultSandboxTools(value: unknown): value is string[] {
|
|
67
|
+
if (!Array.isArray(value) || value.length !== LEGACY_DEFAULT_SANDBOX_TOOLS.length) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const tools = new Set(value);
|
|
71
|
+
return LEGACY_DEFAULT_SANDBOX_TOOLS.every((tool) => tools.has(tool));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function migrateSandboxTools(config: UpdateConfig): boolean {
|
|
75
|
+
const tools = config.sandbox?.tools;
|
|
76
|
+
if (!isLegacyDefaultSandboxTools(tools)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
config.sandbox = {
|
|
80
|
+
...config.sandbox,
|
|
81
|
+
tools: [...DEFAULT_SANDBOX_TOOLS]
|
|
82
|
+
};
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
63
86
|
function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
|
|
64
87
|
const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
|
|
65
88
|
if (!top.startsWith('.')) return false;
|
|
@@ -215,6 +238,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
215
238
|
const taskAdded = !config.task;
|
|
216
239
|
const labelsAdded = !config.labels;
|
|
217
240
|
const prFlowMigrated = migratePrFlow(config);
|
|
241
|
+
const sandboxToolsMigrated = !sandboxAdded && migrateSandboxTools(config);
|
|
218
242
|
let configChanged = changed;
|
|
219
243
|
|
|
220
244
|
if (platformAdded) {
|
|
@@ -241,6 +265,10 @@ async function cmdUpdate(): Promise<void> {
|
|
|
241
265
|
configChanged = true;
|
|
242
266
|
}
|
|
243
267
|
|
|
268
|
+
if (sandboxToolsMigrated) {
|
|
269
|
+
configChanged = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
244
272
|
if (configChanged) {
|
|
245
273
|
console.log('');
|
|
246
274
|
if (hasNewEntries) {
|
|
@@ -267,6 +295,9 @@ async function cmdUpdate(): Promise<void> {
|
|
|
267
295
|
if (prFlowMigrated) {
|
|
268
296
|
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
269
297
|
}
|
|
298
|
+
if (sandboxToolsMigrated) {
|
|
299
|
+
info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
|
|
300
|
+
}
|
|
270
301
|
} else {
|
|
271
302
|
info(`File registry changed in ${CONFIG_PATH}.`);
|
|
272
303
|
}
|
|
@@ -285,6 +316,9 @@ async function cmdUpdate(): Promise<void> {
|
|
|
285
316
|
if (hasNewEntries && prFlowMigrated) {
|
|
286
317
|
info(`Migrated legacy requiresPullRequest to prFlow="${prFlowMigrated}" in ${CONFIG_PATH}.`);
|
|
287
318
|
}
|
|
319
|
+
if (hasNewEntries && sandboxToolsMigrated) {
|
|
320
|
+
info(`Migrated default sandbox.tools to include ${AGENT_INFRA_SANDBOX_TOOL} in ${CONFIG_PATH}.`);
|
|
321
|
+
}
|
|
288
322
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
289
323
|
ok(`Updated ${CONFIG_PATH}`);
|
|
290
324
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"templates/"
|
|
30
30
|
],
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=22"
|
|
32
|
+
"node": ">=22.9.0"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"ai",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@types/cross-spawn": "^6.0.6",
|
|
68
68
|
"@types/node": "^22.20.0",
|
|
69
69
|
"@types/semver": "^7.7.1",
|
|
70
|
-
"typescript": "~
|
|
70
|
+
"typescript": "~7.0"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
73
|
"@lydell/node-pty": "^1.2.0-beta.12"
|
|
@@ -260,7 +260,7 @@ Supported `invoke` placeholders:
|
|
|
260
260
|
| Placeholder | Replaced with | Example |
|
|
261
261
|
|-------------|---------------|---------|
|
|
262
262
|
| `${skillName}` | The skill command name, such as `review-code` or `commit`. | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
|
|
263
|
-
| `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/
|
|
263
|
+
| `${projectName}` | The `.airc.json` `project` value. Use this for namespaced commands. | `/${projectName}:${skillName}` -> `/your-project:review-code` |
|
|
264
264
|
|
|
265
265
|
Non-namespaced custom TUI:
|
|
266
266
|
|
|
@@ -280,7 +280,7 @@ Namespaced custom TUI:
|
|
|
280
280
|
|
|
281
281
|
```json
|
|
282
282
|
{
|
|
283
|
-
"project": "
|
|
283
|
+
"project": "your-project",
|
|
284
284
|
"customTUIs": [
|
|
285
285
|
{
|
|
286
286
|
"name": "<your-tui-name>",
|
|
@@ -295,7 +295,7 @@ Namespaced custom TUI:
|
|
|
295
295
|
|
|
296
296
|
## Sandbox Custom Tools
|
|
297
297
|
|
|
298
|
-
`customTUIs`
|
|
298
|
+
`customTUIs` generates slash-command files but does not change the sandbox image. To install a non-npm CLI or tool (pip / cargo / curl-based / pre-built binary) into the sandbox image and live-mount its credentials, declare it under `sandbox.customTools` in `.agents/.airc.json`. Built-in sandbox tools (`claude-code`, `codex`, `opencode`, `gemini-cli`, `agent-infra`) keep working unchanged; `agent-infra` only provides the in-sandbox `ai` / `agent-infra` CLI and is not part of `tuis` or `customTUIs`.
|
|
299
299
|
|
|
300
300
|
### Required fields
|
|
301
301
|
|
|
@@ -260,7 +260,7 @@ args: "<task-id>" # 可选
|
|
|
260
260
|
| 占位符 | 替换为 | 示例 |
|
|
261
261
|
|--------|--------|------|
|
|
262
262
|
| `${skillName}` | skill 命令名,例如 `review-code` 或 `commit`。 | `<your-cli> ${skillName}` -> `<your-cli> review-code` |
|
|
263
|
-
| `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/
|
|
263
|
+
| `${projectName}` | `.airc.json` 中的 `project` 值,适用于带命名空间的命令。 | `/${projectName}:${skillName}` -> `/your-project:review-code` |
|
|
264
264
|
|
|
265
265
|
不带命名空间的自定义 TUI:
|
|
266
266
|
|
|
@@ -280,7 +280,7 @@ args: "<task-id>" # 可选
|
|
|
280
280
|
|
|
281
281
|
```json
|
|
282
282
|
{
|
|
283
|
-
"project": "
|
|
283
|
+
"project": "your-project",
|
|
284
284
|
"customTUIs": [
|
|
285
285
|
{
|
|
286
286
|
"name": "<your-tui-name>",
|
|
@@ -295,7 +295,7 @@ args: "<task-id>" # 可选
|
|
|
295
295
|
|
|
296
296
|
## 沙箱自定义工具(Sandbox Custom Tools)
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
`customTUIs` 只负责生成 slash-command 文件,**不影响沙箱镜像**。如果要把一个非 npm 分发的 CLI 或工具(pip / cargo / curl 脚本 / 裸二进制)装进沙箱镜像、并 live-mount 它的凭证目录,需要在 `.agents/.airc.json` 的 `sandbox.customTools` 中声明。内建 sandbox 工具(`claude-code` / `codex` / `opencode` / `gemini-cli` / `agent-infra`)行为保持不变;其中 `agent-infra` 只提供沙箱内 `ai` / `agent-infra` CLI,不属于 `tuis` 或 `customTUIs` 配置。
|
|
299
299
|
|
|
300
300
|
### 必填字段
|
|
301
301
|
|
|
@@ -101,7 +101,7 @@ If the final label set is empty, omit the `--label` argument.
|
|
|
101
101
|
| `feature`, `enhancement` | `Feature` |
|
|
102
102
|
| `task`, `documentation`, `dependency-upgrade`, `chore`, `docs`, `refactor`, `refactoring`, others | `Task` |
|
|
103
103
|
|
|
104
|
-
When applying the Issue Type, follow the "Set Issue Type" command in `.agents/rules/issue-pr-commands.md`; first call `gh api orgs/{owner}/issue-types` to list the org's actually available Types, and only set the inferred value when it is present in that list.
|
|
104
|
+
When applying the Issue Type, follow the "Set Issue Type" command in `.agents/rules/issue-pr-commands.md`; only when the owner type is `Organization`, first call `gh api orgs/{owner}/issue-types` to list the org's actually available Types, and only set the inferred value when it is present in that list. User repositories, failed owner type probes, and failed writes are non-blocking.
|
|
105
105
|
|
|
106
106
|
#### milestone
|
|
107
107
|
|
|
@@ -149,7 +149,7 @@ After success, parse the Issue number from the output (match only the `https://.
|
|
|
149
149
|
|
|
150
150
|
### 6. Set Issue Type (Optional)
|
|
151
151
|
|
|
152
|
-
Execute only when `has_push=true` and the Issue Type inferred in §4 is in the org's actually available list:
|
|
152
|
+
Execute only when `has_push=true`, the owner type is `Organization`, and the Issue Type inferred in §4 is in the org's actually available list:
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
155
|
gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
|
|
@@ -188,4 +188,4 @@ Hand the following back to the caller `create-task`:
|
|
|
188
188
|
- Auth failure / command unavailable: return a structured `{code: "AUTH_FAILED", message}` to `create-task`; do not modify task.md
|
|
189
189
|
- Network timeout / DNS failure: `{code: "NETWORK", message}`
|
|
190
190
|
- Template parsing failure, Issue number parsing failure, other anomalies: `{code: "VALIDATION", message}`
|
|
191
|
-
- All failures keep task.md untouched; `create-task`
|
|
191
|
+
- All failures keep task.md untouched; `create-task` must record the structured error in `## Workflow Warnings` (`severity=ACTION_REQUIRED`, `code=ISSUE_CREATE_FAILED`, `target=issue`) before taking the "Scenario C: failure fallback" output branch and prompting the user to retry manually or fill `issue_number` later
|
|
@@ -101,7 +101,7 @@ ai task issue-body {task-id} > "{body-file}"
|
|
|
101
101
|
| `feature`, `enhancement` | `Feature` |
|
|
102
102
|
| `task`, `documentation`, `dependency-upgrade`, `chore`, `docs`, `refactor`, `refactoring` 及其它 | `Task` |
|
|
103
103
|
|
|
104
|
-
实际设置时按 `.agents/rules/issue-pr-commands.md` 的 "设置 Issue Type"
|
|
104
|
+
实际设置时按 `.agents/rules/issue-pr-commands.md` 的 "设置 Issue Type" 命令;仅当 owner type 为 `Organization` 时,先调 `gh api orgs/{owner}/issue-types` 列出 org 实际可用的 Type,并且仅当推断值在列表中时才设置;个人仓库、owner type 探测失败或设置失败都不阻断流程。
|
|
105
105
|
|
|
106
106
|
#### milestone
|
|
107
107
|
|
|
@@ -149,7 +149,7 @@ gh issue create -R "$upstream_repo" \
|
|
|
149
149
|
|
|
150
150
|
### 6. 设置 Issue Type(可选)
|
|
151
151
|
|
|
152
|
-
仅当 `has_push=true`
|
|
152
|
+
仅当 `has_push=true`、owner type 为 `Organization`,且 §4 推断的 Issue Type 在 org 实际可用列表中时执行:
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
155
|
gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
|
|
@@ -188,4 +188,4 @@ gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH \
|
|
|
188
188
|
- 认证失败 / 命令不可用:返回结构化错误 `{code: "AUTH_FAILED", message}` 给 `create-task`,不修改 task.md
|
|
189
189
|
- 网络超时 / DNS 失败:`{code: "NETWORK", message}`
|
|
190
190
|
- 模板解析失败、Issue 编号解析失败、其它异常:`{code: "VALIDATION", message}`
|
|
191
|
-
- 任意失败均不回滚 task.md;`create-task`
|
|
191
|
+
- 任意失败均不回滚 task.md;`create-task` 必须把结构化错误写入 `## 工作流告警`(`severity=ACTION_REQUIRED`,`code=ISSUE_CREATE_FAILED`,`target=issue`),再走"场景 C 失败兜底"输出,提示用户手动重试或在事后写入 `issue_number`
|
|
@@ -83,11 +83,16 @@ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee
|
|
|
83
83
|
Set the Issue Type:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
gh api "
|
|
87
|
-
|
|
86
|
+
owner_type=$(gh api "repos/$upstream_repo" --jq '.owner.type // empty' 2>/dev/null || true)
|
|
87
|
+
if [ "$owner_type" = "Organization" ]; then
|
|
88
|
+
owner=${upstream_repo%%/*}
|
|
89
|
+
gh api "orgs/$owner/issue-types" --jq '.[].name'
|
|
90
|
+
gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
|
|
91
|
+
fi
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
- set the Issue Type only when `has_push=true`; otherwise skip and continue
|
|
95
|
+
- query and set Issue Type only when the owner type is `Organization`; skip and continue for user repositories or failed owner type probes
|
|
91
96
|
- when changing an existing Issue Type, read `.agents/rules/issue-fields.md` and use Flow B so same-name pinned fields are migrated and fields absent from the new type are cleared
|
|
92
97
|
|
|
93
98
|
## Update Issues
|
|
@@ -83,11 +83,16 @@ gh issue create -R "$upstream_repo" --title "{title}" --body "{body}" --assignee
|
|
|
83
83
|
设置 Issue Type:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
gh api "
|
|
87
|
-
|
|
86
|
+
owner_type=$(gh api "repos/$upstream_repo" --jq '.owner.type // empty' 2>/dev/null || true)
|
|
87
|
+
if [ "$owner_type" = "Organization" ]; then
|
|
88
|
+
owner=${upstream_repo%%/*}
|
|
89
|
+
gh api "orgs/$owner/issue-types" --jq '.[].name'
|
|
90
|
+
gh api "repos/$upstream_repo/issues/{issue-number}" -X PATCH -f type="{issue-type}" --silent
|
|
91
|
+
fi
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
- 仅当 `has_push=true` 时执行 Issue Type 设置;否则跳过并继续
|
|
95
|
+
- 仅当 owner type 为 `Organization` 时查询和设置 Issue Type;个人仓库或 owner type 探测失败时跳过并继续
|
|
91
96
|
- 变更现有 Issue Type 时,先读取 `.agents/rules/issue-fields.md` 并使用流程 B,确保同名 pinned fields 迁移,且新 type 不包含的字段被清空
|
|
92
97
|
|
|
93
98
|
## Issue 更新
|
|
@@ -66,6 +66,17 @@ Key rules:
|
|
|
66
66
|
- insufficient permission only affects direct Issue metadata writes and must not stop the skill
|
|
67
67
|
- keep the existing `2>/dev/null || true` error-tolerance pattern
|
|
68
68
|
|
|
69
|
+
When the caller has a `{task-id}` / task directory, permission degradation or critical sync failure must be recorded in `## Workflow Warnings`:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node .agents/scripts/workflow-warnings.js add .agents/workspace/active/{task-id} \
|
|
73
|
+
--step issue-sync --severity IMPORTANT --code PERMISSION_DEGRADED \
|
|
74
|
+
--target "{operation}" --message "{reason}" \
|
|
75
|
+
--action "Wait for the bot/maintainer to backfill, or rerun the workflow step after permissions are available"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Comment create/update failures and exhausted network retries that affect reviewer visibility use `severity=ACTION_REQUIRED` with `code=COMMENT_SYNC_FAILED` or `NETWORK_RETRY_EXHAUSTED`.
|
|
79
|
+
|
|
69
80
|
## External Contributor Locking
|
|
70
81
|
|
|
71
82
|
Maintainers (`has_triage=true`) are never blocked. External contributors (`has_triage=false`) must check whether the current task already has a `task` comment author on the Issue before they start.
|
|
@@ -210,6 +221,8 @@ EOF
|
|
|
210
221
|
|
|
211
222
|
Comment publishing is not gated by `has_triage` or `has_push`.
|
|
212
223
|
|
|
224
|
+
If comment lookup, creation, or update fails and the caller has a task directory, record a Workflow Warning (`step=issue-sync`, `severity=ACTION_REQUIRED`, `code=COMMENT_SYNC_FAILED`, `target={file-stem}`) and surface it in the final Workflow Warnings output block.
|
|
225
|
+
|
|
213
226
|
## task.md Comment Sync
|
|
214
227
|
|
|
215
228
|
Hidden marker:
|
|
@@ -66,6 +66,17 @@ has_push=$(printf '%s' "$repo_perms" | grep -q '"push":true' 2>/dev/null && echo
|
|
|
66
66
|
- 权限不足只影响直接写 Issue 元数据的步骤,不中断整个技能
|
|
67
67
|
- 现有 `2>/dev/null || true` 容错模式保持不变
|
|
68
68
|
|
|
69
|
+
当调用方存在 `{task-id}` / task 目录时,权限降级或关键同步失败必须写入 `## 工作流告警`:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node .agents/scripts/workflow-warnings.js add .agents/workspace/active/{task-id} \
|
|
73
|
+
--step issue-sync --severity IMPORTANT --code PERMISSION_DEGRADED \
|
|
74
|
+
--target "{operation}" --message "{reason}" \
|
|
75
|
+
--action "等待 bot/维护者补位,或在具备权限后重跑对应 workflow 步骤"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
评论创建 / 更新失败、网络重试耗尽等影响后续 reviewer 可见性的失败使用 `severity=ACTION_REQUIRED` 和 `code=COMMENT_SYNC_FAILED` 或 `NETWORK_RETRY_EXHAUSTED`。
|
|
79
|
+
|
|
69
80
|
## 外部开发者锁定机制
|
|
70
81
|
|
|
71
82
|
维护者(`has_triage=true`)不受限制。外部开发者(`has_triage=false`)在开始任务前,必须先检查 Issue 上是否已有当前任务的 `task` 留言作者。
|
|
@@ -210,6 +221,8 @@ EOF
|
|
|
210
221
|
|
|
211
222
|
评论发布不受 `has_triage` / `has_push` 限制,认证用户可正常执行。
|
|
212
223
|
|
|
224
|
+
若评论查询、创建或更新失败且调用方有关联任务目录,记录 Workflow Warning(`step=issue-sync`、`severity=ACTION_REQUIRED`、`code=COMMENT_SYNC_FAILED`、`target={file-stem}`),并在最终输出的 Workflow Warnings 块提示人工处理。
|
|
225
|
+
|
|
213
226
|
## task.md 评论同步
|
|
214
227
|
|
|
215
228
|
隐藏标记:
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Next-Step Output Rule
|
|
2
2
|
|
|
3
|
-
This file defines
|
|
3
|
+
This file defines four **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
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.
|
|
8
|
+
4. **Workflow Warnings output block**: applies when task.md has `status=open` rows in `## Workflow Warnings` — print the warning summary after all normal information and "Next steps" commands, and before `Completed at`.
|
|
8
9
|
|
|
9
10
|
## Placeholder semantics
|
|
10
11
|
|
|
@@ -62,6 +63,19 @@ Completed at: YYYY-MM-DD HH:mm:ss
|
|
|
62
63
|
- 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
64
|
- 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
|
|
|
66
|
+
## Workflow Warnings Output Block
|
|
67
|
+
|
|
68
|
+
If the current task's `## Workflow Warnings` / `## 工作流告警` table has any `status=open` row, the skill's final user output must append a summary block after all normal information and "Next steps" commands, and before `Completed at`. When no open warning exists, do not render this block.
|
|
69
|
+
|
|
70
|
+
Format:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
[ACTION REQUIRED] Workflow warnings are open:
|
|
74
|
+
- WW-N {code} ({target}): {action}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Use `[ACTION REQUIRED]` when any open row has `severity=ACTION_REQUIRED`; otherwise use `[IMPORTANT]`. `{code}`, `{target}`, and `{action}` come directly from the warning table columns.
|
|
78
|
+
|
|
65
79
|
## Pending human-decision pre-block (review-* only, when {h} > 0)
|
|
66
80
|
|
|
67
81
|
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`.
|