@fitlab-ai/agent-infra 0.8.2 → 0.8.4
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.zh-CN.md +10 -0
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +2 -2
- package/dist/lib/cp.js +57 -16
- 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 +10 -8
- package/dist/lib/sandbox/clipboard/bridge.js +59 -21
- package/dist/lib/sandbox/clipboard/inbox.js +82 -0
- 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 +106 -88
- 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 +10 -4
- 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/base.dockerfile +3 -0
- package/dist/lib/sandbox/shell.js +9 -1
- package/dist/lib/sandbox/tools.js +4 -4
- package/dist/lib/server/adapters/feishu/index.js +10 -3
- package/dist/lib/server/adapters/feishu/renderer.js +88 -0
- package/dist/lib/server/adapters/feishu/transport.js +2 -13
- 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 +2 -2
- 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 +4 -4
- package/lib/cp.ts +56 -15
- package/lib/defaults.json +2 -0
- package/lib/sandbox/capture.ts +5 -3
- package/lib/sandbox/clipboard/bridge.ts +62 -18
- package/lib/sandbox/clipboard/inbox.ts +83 -0
- 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 +132 -88
- package/lib/sandbox/commands/rebuild.ts +42 -54
- package/lib/sandbox/config.ts +13 -1
- package/lib/sandbox/constants.ts +4 -0
- package/lib/sandbox/image-build.ts +134 -0
- package/lib/sandbox/runtimes/base.dockerfile +3 -0
- package/lib/sandbox/shell.ts +11 -2
- package/lib/sandbox/tools.ts +5 -5
- package/lib/server/adapters/_contract.ts +3 -0
- package/lib/server/adapters/feishu/index.ts +11 -3
- package/lib/server/adapters/feishu/renderer.ts +99 -0
- package/lib/server/adapters/feishu/transport.ts +5 -18
- package/lib/server/daemon.ts +74 -38
- package/lib/server/display.ts +136 -0
- 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/package.json +2 -2
- package/templates/.agents/README.en.md +3 -0
- package/templates/.agents/README.zh-CN.md +3 -0
- package/templates/.agents/rules/create-issue.github.en.md +4 -4
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -4
- 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/review-handshake.en.md +2 -2
- package/templates/.agents/rules/review-handshake.zh-CN.md +2 -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/rules/testing-discipline.en.md +3 -41
- package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +57 -0
- package/templates/.agents/scripts/validate-artifact.js +213 -0
- package/templates/.agents/scripts/workflow-warnings.js +290 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +6 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +142 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +142 -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/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +6 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +165 -41
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +36 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -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
|
@@ -112,6 +112,7 @@ export function check({ taskDir, config, artifactFile }, shared) {
|
|
|
112
112
|
checkCommentMarker,
|
|
113
113
|
checkPrCommentMarker,
|
|
114
114
|
checkPrCommentLastCommit,
|
|
115
|
+
checkPrCommentRequiredPatterns,
|
|
115
116
|
checkCommentContent,
|
|
116
117
|
checkTaskCommentContent,
|
|
117
118
|
checkInLabelsComputed,
|
|
@@ -158,6 +159,7 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
|
|
|
158
159
|
return { earlyReturn: blockedResult(CHECK_TYPE, upstreamRepo.message, "network_error") };
|
|
159
160
|
}
|
|
160
161
|
const permissions = detectPermissions(upstreamRepo.value, taskDir);
|
|
162
|
+
const repoOwnerType = detectRepoOwnerType(upstreamRepo.value, taskDir);
|
|
161
163
|
const expectedValues = resolveExpectedValues(config);
|
|
162
164
|
if (!expectedValues.ok) {
|
|
163
165
|
return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
|
|
@@ -180,6 +182,7 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
|
|
|
180
182
|
issueNumber,
|
|
181
183
|
prNumber,
|
|
182
184
|
upstreamRepo: upstreamRepo.value,
|
|
185
|
+
repoOwnerType,
|
|
183
186
|
hasTriage: permissions.hasTriage,
|
|
184
187
|
hasPush: permissions.hasPush,
|
|
185
188
|
expectedStatusLabel: expectedValues.statusLabel,
|
|
@@ -553,6 +556,41 @@ function checkPrCommentLastCommit(context, remoteData) {
|
|
|
553
556
|
);
|
|
554
557
|
}
|
|
555
558
|
|
|
559
|
+
function checkPrCommentRequiredPatterns(context, remoteData) {
|
|
560
|
+
const patterns = context.config.expected_pr_comment_required_patterns || [];
|
|
561
|
+
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!context.prMarker) {
|
|
566
|
+
return failResult(CHECK_TYPE,
|
|
567
|
+
"expected_pr_comment_required_patterns requires expected_pr_comment_marker",
|
|
568
|
+
"check_failed"
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const comment = findCommentByMarker(remoteData.prComments, context.prMarker);
|
|
573
|
+
if (!comment) {
|
|
574
|
+
return failResult(CHECK_TYPE,
|
|
575
|
+
`Expected PR comment marker '${context.prMarker}' not found on PR #${context.prNumber}`,
|
|
576
|
+
"check_failed"
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const body = String(comment.body || "");
|
|
581
|
+
for (const pattern of patterns) {
|
|
582
|
+
const regex = new RegExp(pattern, "m");
|
|
583
|
+
if (!regex.test(body)) {
|
|
584
|
+
return failResult(CHECK_TYPE,
|
|
585
|
+
`PR #${context.prNumber} summary comment is missing required pattern: ${pattern}`,
|
|
586
|
+
"check_failed"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
|
|
556
594
|
function checkCommentContent(context, remoteData) {
|
|
557
595
|
if (!context.config.verify_comment_content) {
|
|
558
596
|
return null;
|
|
@@ -722,6 +760,10 @@ function checkIssueType(context, remoteData) {
|
|
|
722
760
|
}
|
|
723
761
|
|
|
724
762
|
if (!remoteData.issueType) {
|
|
763
|
+
if (context.repoOwnerType === "User") {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
|
|
725
767
|
return failResult(CHECK_TYPE,
|
|
726
768
|
`Issue #${context.issueNumber} has no Issue Type set`,
|
|
727
769
|
"check_failed"
|
|
@@ -1228,6 +1270,21 @@ function detectPermissions(upstreamRepo, taskDir) {
|
|
|
1228
1270
|
};
|
|
1229
1271
|
}
|
|
1230
1272
|
|
|
1273
|
+
function detectRepoOwnerType(upstreamRepo, taskDir) {
|
|
1274
|
+
const ownerTypeResult = withRetry(() => ghText([
|
|
1275
|
+
"api",
|
|
1276
|
+
`repos/${upstreamRepo}`,
|
|
1277
|
+
"--jq",
|
|
1278
|
+
".owner.type // empty"
|
|
1279
|
+
], taskDir));
|
|
1280
|
+
|
|
1281
|
+
if (!ownerTypeResult.ok) {
|
|
1282
|
+
return "unknown";
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
return ownerTypeResult.value || "unknown";
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1231
1288
|
function ghJson(args, cwd) {
|
|
1232
1289
|
const result = ghCommand(args, cwd);
|
|
1233
1290
|
if (!result.ok) {
|
|
@@ -6,7 +6,9 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
extractReviewBaseline,
|
|
9
|
+
extractReviewDiffFingerprint,
|
|
9
10
|
findAuthoritativeReviewCodeArtifact,
|
|
11
|
+
parseReviewVerdict,
|
|
10
12
|
resolvePostReviewGlobs
|
|
11
13
|
} from "./lib/post-review-commit.js";
|
|
12
14
|
|
|
@@ -61,6 +63,10 @@ const LEDGER_TERMINAL_OK = new Set(["confirmed", "closed", "human-decided"]);
|
|
|
61
63
|
const DEFAULT_MAX_HANDSHAKE_ROUNDS = 3;
|
|
62
64
|
const POST_REVIEW_COMMIT_STAGE = "post-review-commit";
|
|
63
65
|
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
66
|
+
const WORKFLOW_WARNING_SECTION_NAMES = ["工作流告警", "Workflow Warnings"];
|
|
67
|
+
const WORKFLOW_WARNING_STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
68
|
+
const WORKFLOW_WARNING_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
|
|
69
|
+
const WORKFLOW_WARNING_ID_PATTERN = /^WW-\d+$/;
|
|
64
70
|
|
|
65
71
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
66
72
|
const repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
|
|
@@ -219,6 +225,8 @@ function runCheck(type, context) {
|
|
|
219
225
|
return checkCompletionChecklist(context);
|
|
220
226
|
case "review-ledger":
|
|
221
227
|
return checkReviewLedger(context);
|
|
228
|
+
case "review-fact":
|
|
229
|
+
return checkReviewFact(context);
|
|
222
230
|
case "post-review-commit":
|
|
223
231
|
return checkPostReviewCommit(context);
|
|
224
232
|
default: {
|
|
@@ -284,6 +292,11 @@ function checkTaskMeta({ taskDir, config }) {
|
|
|
284
292
|
return failResult("task-meta", branchValidationError);
|
|
285
293
|
}
|
|
286
294
|
|
|
295
|
+
const warningValidationErrors = validateWorkflowWarnings(task.content);
|
|
296
|
+
if (warningValidationErrors.length > 0) {
|
|
297
|
+
return failResult("task-meta", `Invalid Workflow Warnings: ${warningValidationErrors.join("; ")}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
287
300
|
const expectedStep = config.expected_step;
|
|
288
301
|
if (expectedStep && metadata.current_step !== expectedStep) {
|
|
289
302
|
return failResult(
|
|
@@ -561,6 +574,117 @@ function parseLedgerRows(section) {
|
|
|
561
574
|
return rows;
|
|
562
575
|
}
|
|
563
576
|
|
|
577
|
+
function splitMarkdownTableRow(line) {
|
|
578
|
+
let value = String(line || "").trim();
|
|
579
|
+
if (!value.startsWith("|")) {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
value = value.replace(/^\|/, "").replace(/\|$/, "");
|
|
583
|
+
|
|
584
|
+
const cells = [];
|
|
585
|
+
let cell = "";
|
|
586
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
587
|
+
const char = value[index];
|
|
588
|
+
if (char === "|" && !isEscapedAt(value, index)) {
|
|
589
|
+
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
590
|
+
cell = "";
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
cell += char;
|
|
594
|
+
}
|
|
595
|
+
cells.push(unescapeMarkdownTableCell(cell.trim()));
|
|
596
|
+
return cells;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function unescapeMarkdownTableCell(value) {
|
|
600
|
+
let output = "";
|
|
601
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
602
|
+
const char = value[index];
|
|
603
|
+
const next = value[index + 1];
|
|
604
|
+
if (char === "\\" && (next === "\\" || next === "|")) {
|
|
605
|
+
output += next;
|
|
606
|
+
index += 1;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
output += char;
|
|
610
|
+
}
|
|
611
|
+
return output;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function isEscapedAt(value, index) {
|
|
615
|
+
let backslashes = 0;
|
|
616
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
|
|
617
|
+
backslashes += 1;
|
|
618
|
+
}
|
|
619
|
+
return backslashes % 2 === 1;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function parseWorkflowWarningRows(section) {
|
|
623
|
+
const rows = [];
|
|
624
|
+
for (const rawLine of String(section || "").split(/\r?\n/)) {
|
|
625
|
+
const cells = splitMarkdownTableRow(rawLine);
|
|
626
|
+
if (cells.length === 0) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if ((cells[0] || "").toLowerCase() === "id") {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (cells.every((cell) => /^:?-{3,}:?$/.test(cell))) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
rows.push(cells);
|
|
636
|
+
}
|
|
637
|
+
return rows;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function validateWorkflowWarnings(content) {
|
|
641
|
+
const section = getSectionContent(content, WORKFLOW_WARNING_SECTION_NAMES);
|
|
642
|
+
if (!section.trim()) {
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const rows = parseWorkflowWarningRows(section);
|
|
647
|
+
const errors = [];
|
|
648
|
+
for (const cells of rows) {
|
|
649
|
+
if (cells.length < 11) {
|
|
650
|
+
errors.push(`malformed row (expected 11 columns): ${cells.join(" | ")}`);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const [id, time, step, severity, code, status, target, message, action, resolvedAt, resolution] = cells;
|
|
654
|
+
if (!WORKFLOW_WARNING_ID_PATTERN.test(id)) {
|
|
655
|
+
errors.push(`${id || "(empty id)"}: invalid id`);
|
|
656
|
+
}
|
|
657
|
+
if (!DATE_TIME_PATTERN.test(time)) {
|
|
658
|
+
errors.push(`${id}: invalid time '${time}'`);
|
|
659
|
+
}
|
|
660
|
+
if (isBlank(step)) {
|
|
661
|
+
errors.push(`${id}: step is required`);
|
|
662
|
+
}
|
|
663
|
+
if (!WORKFLOW_WARNING_SEVERITIES.has(severity)) {
|
|
664
|
+
errors.push(`${id}: illegal severity '${severity}'`);
|
|
665
|
+
}
|
|
666
|
+
if (isBlank(code)) {
|
|
667
|
+
errors.push(`${id}: code is required`);
|
|
668
|
+
}
|
|
669
|
+
if (!WORKFLOW_WARNING_STATUSES.has(status)) {
|
|
670
|
+
errors.push(`${id}: illegal status '${status}'`);
|
|
671
|
+
}
|
|
672
|
+
if (isBlank(target)) {
|
|
673
|
+
errors.push(`${id}: target is required`);
|
|
674
|
+
}
|
|
675
|
+
if (isBlank(message)) {
|
|
676
|
+
errors.push(`${id}: message is required`);
|
|
677
|
+
}
|
|
678
|
+
if (status === "open" && isBlank(action)) {
|
|
679
|
+
errors.push(`${id}: open warning requires action`);
|
|
680
|
+
}
|
|
681
|
+
if ((status === "resolved" || status === "ignored") && (isBlank(resolvedAt) || isBlank(resolution))) {
|
|
682
|
+
errors.push(`${id}: ${status} warning requires resolved_at and resolution`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return errors;
|
|
686
|
+
}
|
|
687
|
+
|
|
564
688
|
function resolveReviewSetting(config, key, fallback) {
|
|
565
689
|
if (config && config[key] !== undefined && config[key] !== null) {
|
|
566
690
|
return config[key];
|
|
@@ -698,6 +822,95 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
698
822
|
);
|
|
699
823
|
}
|
|
700
824
|
|
|
825
|
+
function checkReviewFact({ taskDir, artifactFile }) {
|
|
826
|
+
const resolvedArtifact = resolveArtifactPath(
|
|
827
|
+
taskDir,
|
|
828
|
+
"review-code.md|review-code-r{N}.md",
|
|
829
|
+
artifactFile
|
|
830
|
+
);
|
|
831
|
+
if (!resolvedArtifact.ok) {
|
|
832
|
+
return failResult("review-fact", resolvedArtifact.message);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const task = loadTask(taskDir);
|
|
836
|
+
if (!task.ok) {
|
|
837
|
+
return failResult("review-fact", task.message);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const content = fs.readFileSync(resolvedArtifact.path, "utf8");
|
|
841
|
+
const verdict = parseReviewVerdict(content);
|
|
842
|
+
const reviewBaseline = extractReviewBaseline(content);
|
|
843
|
+
const reviewedFingerprint = extractReviewDiffFingerprint(content);
|
|
844
|
+
|
|
845
|
+
if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
|
|
846
|
+
return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
let gitRoot;
|
|
850
|
+
let head;
|
|
851
|
+
let baseline;
|
|
852
|
+
try {
|
|
853
|
+
gitRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
854
|
+
head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
855
|
+
baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewBaseline}^{commit}`], { encoding: "utf8" }).trim();
|
|
856
|
+
} catch {
|
|
857
|
+
return blockedResult(
|
|
858
|
+
"review-fact",
|
|
859
|
+
`Unable to resolve review baseline '${reviewBaseline}' in the task repository; re-run review-code`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (baseline !== head) {
|
|
864
|
+
return failResult(
|
|
865
|
+
"review-fact",
|
|
866
|
+
`Review baseline ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
let actualFingerprint;
|
|
871
|
+
try {
|
|
872
|
+
actualFingerprint = execFileSync(
|
|
873
|
+
process.execPath,
|
|
874
|
+
[path.join(repoRoot, ".agents", "scripts", "review-diff-fingerprint.js"), "worktree", baseline],
|
|
875
|
+
{ cwd: gitRoot, encoding: "utf8" }
|
|
876
|
+
).trim();
|
|
877
|
+
} catch {
|
|
878
|
+
return blockedResult(
|
|
879
|
+
"review-fact",
|
|
880
|
+
`Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (actualFingerprint !== reviewedFingerprint) {
|
|
885
|
+
return failResult(
|
|
886
|
+
"review-fact",
|
|
887
|
+
`Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (["通过", "Approved"].includes(verdict)) {
|
|
892
|
+
const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
|
|
893
|
+
let reviewedCommit = "";
|
|
894
|
+
try {
|
|
895
|
+
reviewedCommit = execFileSync("git", ["-C", gitRoot, "rev-parse", `${lastReviewedCommit}^{commit}`], { encoding: "utf8" }).trim();
|
|
896
|
+
} catch {
|
|
897
|
+
// The failure below names the missing or invalid task review fact.
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (reviewedCommit !== baseline) {
|
|
901
|
+
return failResult(
|
|
902
|
+
"review-fact",
|
|
903
|
+
`Approved review must set task last_reviewed_commit to baseline ${baseline.slice(0, 8)}`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return passResult(
|
|
909
|
+
"review-fact",
|
|
910
|
+
`Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
|
|
701
914
|
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewBaseline, reviewArtifact }) {
|
|
702
915
|
if (lastReviewedCommit) {
|
|
703
916
|
if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
const SECTION_ZH = "工作流告警";
|
|
6
|
+
const SECTION_EN = "Workflow Warnings";
|
|
7
|
+
const ACTIVITY_ZH = "活动日志";
|
|
8
|
+
const ACTIVITY_EN = "Activity Log";
|
|
9
|
+
const HEADER = "| id | time | step | severity | code | status | target | message | action | resolved_at | resolution |";
|
|
10
|
+
const SEPARATOR = "|----|------|------|----------|------|--------|--------|---------|--------|-------------|------------|";
|
|
11
|
+
const VALID_SEVERITIES = new Set(["IMPORTANT", "ACTION_REQUIRED"]);
|
|
12
|
+
const VALID_STATUSES = new Set(["open", "resolved", "ignored"]);
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
process.stderr.write([
|
|
16
|
+
"Usage:",
|
|
17
|
+
" node .agents/scripts/workflow-warnings.js add <task-dir> --step <step> --severity <IMPORTANT|ACTION_REQUIRED> --code <code> --target <target> --message <message> --action <action>",
|
|
18
|
+
" node .agents/scripts/workflow-warnings.js set-status <task-dir> --id <WW-N> --status <resolved|ignored> --resolution <reason>",
|
|
19
|
+
" node .agents/scripts/workflow-warnings.js list <task-dir> [--status <status>] [--format json|text]",
|
|
20
|
+
""
|
|
21
|
+
].join("\n"));
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseOptions(args) {
|
|
26
|
+
const options = {};
|
|
27
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
28
|
+
const key = args[index];
|
|
29
|
+
if (!key?.startsWith("--")) usage();
|
|
30
|
+
const value = args[index + 1];
|
|
31
|
+
if (value === undefined || value.startsWith("--")) usage();
|
|
32
|
+
options[key.slice(2)] = value;
|
|
33
|
+
index += 1;
|
|
34
|
+
}
|
|
35
|
+
return options;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function timestamp() {
|
|
39
|
+
const date = new Date();
|
|
40
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
41
|
+
const offsetMinutes = -date.getTimezoneOffset();
|
|
42
|
+
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
43
|
+
const absolute = Math.abs(offsetMinutes);
|
|
44
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function escapeCell(value) {
|
|
48
|
+
return String(value ?? "").replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function unescapeCell(value) {
|
|
52
|
+
const text = String(value ?? "");
|
|
53
|
+
let output = "";
|
|
54
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
55
|
+
const char = text[index];
|
|
56
|
+
const next = text[index + 1];
|
|
57
|
+
if (char === "\\" && (next === "\\" || next === "|")) {
|
|
58
|
+
output += next;
|
|
59
|
+
index += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
output += char;
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isEscapedAt(value, index) {
|
|
68
|
+
let backslashes = 0;
|
|
69
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) {
|
|
70
|
+
backslashes += 1;
|
|
71
|
+
}
|
|
72
|
+
return backslashes % 2 === 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function splitRow(line) {
|
|
76
|
+
let value = String(line || "").trim();
|
|
77
|
+
if (!value.startsWith("|")) return [];
|
|
78
|
+
value = value.replace(/^\|/, "").replace(/\|$/, "");
|
|
79
|
+
const cells = [];
|
|
80
|
+
let cell = "";
|
|
81
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
82
|
+
const char = value[index];
|
|
83
|
+
if (char === "|" && !isEscapedAt(value, index)) {
|
|
84
|
+
cells.push(unescapeCell(cell.trim()));
|
|
85
|
+
cell = "";
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
cell += char;
|
|
89
|
+
}
|
|
90
|
+
cells.push(unescapeCell(cell.trim()));
|
|
91
|
+
return cells;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function rowToWarning(cells, lineIndex) {
|
|
95
|
+
return {
|
|
96
|
+
lineIndex,
|
|
97
|
+
id: cells[0] || "",
|
|
98
|
+
time: cells[1] || "",
|
|
99
|
+
step: cells[2] || "",
|
|
100
|
+
severity: cells[3] || "",
|
|
101
|
+
code: cells[4] || "",
|
|
102
|
+
status: cells[5] || "",
|
|
103
|
+
target: cells[6] || "",
|
|
104
|
+
message: cells[7] || "",
|
|
105
|
+
action: cells[8] || "",
|
|
106
|
+
resolved_at: cells[9] || "",
|
|
107
|
+
resolution: cells[10] || ""
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function warningToLine(warning) {
|
|
112
|
+
return [
|
|
113
|
+
warning.id,
|
|
114
|
+
warning.time,
|
|
115
|
+
warning.step,
|
|
116
|
+
warning.severity,
|
|
117
|
+
warning.code,
|
|
118
|
+
warning.status,
|
|
119
|
+
warning.target,
|
|
120
|
+
warning.message,
|
|
121
|
+
warning.action,
|
|
122
|
+
warning.resolved_at,
|
|
123
|
+
warning.resolution
|
|
124
|
+
].map(escapeCell).join(" | ").replace(/^/, "| ").replace(/$/, " |");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findSection(lines) {
|
|
128
|
+
let start = -1;
|
|
129
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
130
|
+
if (new RegExp(`^##\\s+(${SECTION_ZH}|${SECTION_EN})\\s*$`).test(lines[index].trim())) {
|
|
131
|
+
start = index;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (start === -1) return null;
|
|
136
|
+
let end = lines.length;
|
|
137
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
138
|
+
if (/^##\s+/.test(lines[index])) {
|
|
139
|
+
end = index;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { start, end };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function insertSection(content) {
|
|
147
|
+
const lines = content.split(/\r?\n/);
|
|
148
|
+
const existing = findSection(lines);
|
|
149
|
+
if (existing) return { content, section: existing };
|
|
150
|
+
|
|
151
|
+
const activityIndex = lines.findIndex((line) => {
|
|
152
|
+
const trimmed = line.trim();
|
|
153
|
+
return trimmed === `## ${ACTIVITY_ZH}` || trimmed === `## ${ACTIVITY_EN}`;
|
|
154
|
+
});
|
|
155
|
+
const heading = content.includes(`## ${ACTIVITY_EN}`) ? SECTION_EN : SECTION_ZH;
|
|
156
|
+
const block = [`## ${heading}`, "", "<!-- Workflow degradation, platform sync failures, permission gaps, and related events. Keep the header when empty. -->", "", HEADER, SEPARATOR, ""];
|
|
157
|
+
const insertAt = activityIndex >= 0 ? activityIndex : lines.length;
|
|
158
|
+
lines.splice(insertAt, 0, ...block);
|
|
159
|
+
const updated = lines.join("\n");
|
|
160
|
+
return { content: updated, section: findSection(updated.split(/\r?\n/)) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readTask(taskDir) {
|
|
164
|
+
const taskPath = path.join(taskDir, "task.md");
|
|
165
|
+
if (!fs.existsSync(taskPath)) {
|
|
166
|
+
throw new Error(`Task file not found: ${taskPath}`);
|
|
167
|
+
}
|
|
168
|
+
return { taskPath, content: fs.readFileSync(taskPath, "utf8") };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseWarnings(content) {
|
|
172
|
+
const lines = content.split(/\r?\n/);
|
|
173
|
+
const section = findSection(lines);
|
|
174
|
+
if (!section) return [];
|
|
175
|
+
const warnings = [];
|
|
176
|
+
for (let index = section.start + 1; index < section.end; index += 1) {
|
|
177
|
+
const cells = splitRow(lines[index]);
|
|
178
|
+
if (cells.length === 0) continue;
|
|
179
|
+
if ((cells[0] || "").toLowerCase() === "id") continue;
|
|
180
|
+
if (cells.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
181
|
+
if (cells.length < 11) continue;
|
|
182
|
+
warnings.push(rowToWarning(cells, index));
|
|
183
|
+
}
|
|
184
|
+
return warnings;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function nextId(warnings) {
|
|
188
|
+
const max = warnings.reduce((current, warning) => {
|
|
189
|
+
const match = /^WW-(\d+)$/.exec(warning.id);
|
|
190
|
+
return match ? Math.max(current, Number.parseInt(match[1], 10)) : current;
|
|
191
|
+
}, 0);
|
|
192
|
+
return `WW-${max + 1}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function requireOption(options, key) {
|
|
196
|
+
const value = options[key];
|
|
197
|
+
if (!value) usage();
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function add(taskDir, options) {
|
|
202
|
+
const severity = requireOption(options, "severity");
|
|
203
|
+
if (!VALID_SEVERITIES.has(severity)) throw new Error(`Invalid severity: ${severity}`);
|
|
204
|
+
const step = requireOption(options, "step");
|
|
205
|
+
const code = requireOption(options, "code");
|
|
206
|
+
const target = requireOption(options, "target");
|
|
207
|
+
const message = requireOption(options, "message");
|
|
208
|
+
const action = requireOption(options, "action");
|
|
209
|
+
|
|
210
|
+
const task = readTask(taskDir);
|
|
211
|
+
const inserted = insertSection(task.content);
|
|
212
|
+
const lines = inserted.content.split(/\r?\n/);
|
|
213
|
+
const warnings = parseWarnings(inserted.content);
|
|
214
|
+
const duplicate = warnings.find((warning) =>
|
|
215
|
+
warning.status === "open" &&
|
|
216
|
+
warning.step === step &&
|
|
217
|
+
warning.code === code &&
|
|
218
|
+
warning.target === target
|
|
219
|
+
);
|
|
220
|
+
if (duplicate) {
|
|
221
|
+
process.stdout.write(`${JSON.stringify({ created: false, warning: duplicate }, null, 2)}\n`);
|
|
222
|
+
if (inserted.content !== task.content) fs.writeFileSync(task.taskPath, inserted.content, "utf8");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const warning = {
|
|
227
|
+
id: nextId(warnings),
|
|
228
|
+
time: timestamp(),
|
|
229
|
+
step,
|
|
230
|
+
severity,
|
|
231
|
+
code,
|
|
232
|
+
status: "open",
|
|
233
|
+
target,
|
|
234
|
+
message,
|
|
235
|
+
action,
|
|
236
|
+
resolved_at: "",
|
|
237
|
+
resolution: ""
|
|
238
|
+
};
|
|
239
|
+
lines.splice(inserted.section.end - 1, 0, warningToLine(warning));
|
|
240
|
+
fs.writeFileSync(task.taskPath, lines.join("\n"), "utf8");
|
|
241
|
+
process.stdout.write(`${JSON.stringify({ created: true, warning }, null, 2)}\n`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function setStatus(taskDir, options) {
|
|
245
|
+
const id = requireOption(options, "id");
|
|
246
|
+
const status = requireOption(options, "status");
|
|
247
|
+
if (!["resolved", "ignored"].includes(status)) throw new Error(`Invalid status for set-status: ${status}`);
|
|
248
|
+
const resolution = requireOption(options, "resolution");
|
|
249
|
+
const task = readTask(taskDir);
|
|
250
|
+
const lines = task.content.split(/\r?\n/);
|
|
251
|
+
const warnings = parseWarnings(task.content);
|
|
252
|
+
const warning = warnings.find((candidate) => candidate.id === id);
|
|
253
|
+
if (!warning) throw new Error(`Warning not found: ${id}`);
|
|
254
|
+
warning.status = status;
|
|
255
|
+
warning.resolved_at = timestamp();
|
|
256
|
+
warning.resolution = resolution;
|
|
257
|
+
lines[warning.lineIndex] = warningToLine(warning);
|
|
258
|
+
fs.writeFileSync(task.taskPath, lines.join("\n"), "utf8");
|
|
259
|
+
process.stdout.write(`${JSON.stringify({ updated: true, warning }, null, 2)}\n`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function list(taskDir, options) {
|
|
263
|
+
const format = options.format || "text";
|
|
264
|
+
const status = options.status || "";
|
|
265
|
+
if (format !== "json" && format !== "text") usage();
|
|
266
|
+
const warnings = parseWarnings(readTask(taskDir).content)
|
|
267
|
+
.filter((warning) => !status || warning.status === status)
|
|
268
|
+
.map(({ lineIndex: _lineIndex, ...warning }) => warning);
|
|
269
|
+
if (format === "json") {
|
|
270
|
+
process.stdout.write(`${JSON.stringify({ warnings }, null, 2)}\n`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
for (const warning of warnings) {
|
|
274
|
+
process.stdout.write(`${warning.id} [${warning.severity}] ${warning.code} ${warning.target} - ${warning.action}\n`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const [command, taskDirArg, ...rest] = process.argv.slice(2);
|
|
280
|
+
if (!command || !taskDirArg) usage();
|
|
281
|
+
const taskDir = path.resolve(taskDirArg);
|
|
282
|
+
const options = parseOptions(rest);
|
|
283
|
+
if (command === "add") add(taskDir, options);
|
|
284
|
+
else if (command === "set-status") setStatus(taskDir, options);
|
|
285
|
+
else if (command === "list") list(taskDir, options);
|
|
286
|
+
else usage();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
@@ -210,7 +210,7 @@ Create `.agents/workspace/active/{task-id}/{analysis-artifact}`.
|
|
|
210
210
|
Get the current time:
|
|
211
211
|
|
|
212
212
|
```bash
|
|
213
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
213
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
214
214
|
```
|
|
215
215
|
|
|
216
216
|
Update `.agents/workspace/active/{task-id}/task.md`:
|
|
@@ -209,7 +209,7 @@ tail .agents/workspace/active/{task-id}/task.md
|
|
|
209
209
|
获取当前时间:
|
|
210
210
|
|
|
211
211
|
```bash
|
|
212
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
212
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
更新 `.agents/workspace/active/{task-id}/task.md`:
|
|
@@ -275,7 +275,7 @@ node .agents/scripts/validate-artifact.js gate analyze-task .agents/workspace/ac
|
|
|
275
275
|
|
|
276
276
|
下一步 - 审查需求分析:
|
|
277
277
|
- Claude Code / OpenCode:/review-analysis {task-ref}
|
|
278
|
-
- Gemini CLI:/
|
|
278
|
+
- Gemini CLI:/{{project}}:review-analysis {task-ref}
|
|
279
279
|
- Codex CLI:$review-analysis {task-ref}
|
|
280
280
|
```
|
|
281
281
|
|
|
@@ -210,7 +210,7 @@ rebuild_manifest() {
|
|
|
210
210
|
entries_file="$tmpdir/manifest.tsv"
|
|
211
211
|
month_keys_file="$tmpdir/manifest-months.tsv"
|
|
212
212
|
year_keys_file="$tmpdir/manifest-years.tsv"
|
|
213
|
-
generated_at=$(date "+%Y-%m-%d %H:%M:%S
|
|
213
|
+
generated_at=$(date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/')
|
|
214
214
|
|
|
215
215
|
mkdir -p "$ARCHIVE_DIR"
|
|
216
216
|
: > "$entries_file"
|