@fitlab-ai/agent-infra 0.9.10 → 0.9.11
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 +13 -3
- package/README.zh-CN.md +13 -3
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +50 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +7 -8
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +5 -15
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/internal/task-validate.js +2 -1
- package/dist/lib/merge.js +205 -103
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +3 -12
- package/dist/lib/sandbox/control/executor.js +131 -39
- package/dist/lib/sandbox/control/lifecycle.js +29 -20
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +4 -2
- package/dist/lib/task/codex-orchestration.js +1 -1
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-operation.js +21 -25
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +6 -1
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +263 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/index.js +19 -20
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +16 -0
- package/dist/lib/task/orchestration.js +50 -13
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +19 -6
- package/dist/lib/task/verification-engine.js +38 -15
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +6 -37
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +7 -8
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +5 -18
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +4 -0
- package/lib/init.ts +2 -2
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/internal/task-validate.ts +3 -2
- package/lib/merge.ts +236 -120
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +2 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/executor.ts +148 -51
- package/lib/sandbox/control/lifecycle.ts +34 -28
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +5 -3
- package/lib/task/codex-orchestration.ts +2 -1
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-operation.ts +25 -30
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +6 -1
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +340 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/events.ts +10 -5
- package/lib/task/index.ts +19 -20
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +20 -0
- package/lib/task/orchestration.ts +40 -15
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +33 -7
- package/lib/task/verification-engine.ts +33 -18
- package/lib/task/verification-review.ts +1 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +6 -40
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/compatibility-policy.en.md +5 -4
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- 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/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +1 -1
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
- package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/release-commands.github.en.md +6 -0
- package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +1 -2
- package/templates/.agents/rules/task-management.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +27 -7
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +5 -7
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -7
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- 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 +5 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -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/migration-risks.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +140 -109
- 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/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const DECISION_ID = /^(AN|PL|CD|HD)-[1-9]\d*(?=$|[\s::])/i;
|
|
2
|
+
const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
|
|
3
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
4
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
|
|
5
|
+
const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
|
|
6
|
+
const FIELD = /^\s*[-*]\s+\*\*([^*]+)\*\*\s*[::]\s*/;
|
|
7
|
+
const OPTION_HEADING = /^(?:方案|Option)\s+[A-Z](?:\b|\s*[::])/i;
|
|
8
|
+
function sourceLines(content) {
|
|
9
|
+
const lines = [];
|
|
10
|
+
let start = 0;
|
|
11
|
+
while (start < content.length) {
|
|
12
|
+
const newline = content.indexOf('\n', start);
|
|
13
|
+
const end = newline === -1 ? content.length : newline;
|
|
14
|
+
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
15
|
+
start = newline === -1 ? content.length : newline + 1;
|
|
16
|
+
}
|
|
17
|
+
return lines;
|
|
18
|
+
}
|
|
19
|
+
function scanVisibleMarkdown(content) {
|
|
20
|
+
const lines = sourceLines(content);
|
|
21
|
+
const visible = [];
|
|
22
|
+
const headings = [];
|
|
23
|
+
const anchors = [];
|
|
24
|
+
let fence = null;
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (fence) {
|
|
27
|
+
const close = line.text.match(FENCE_CLOSE);
|
|
28
|
+
if (close && close[1][0] === fence.character && close[1].length >= fence.length)
|
|
29
|
+
fence = null;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const open = line.text.match(FENCE_OPEN);
|
|
33
|
+
const marker = open?.[1];
|
|
34
|
+
const info = open?.[2] ?? '';
|
|
35
|
+
if (marker && !(marker[0] === '`' && info.includes('`'))) {
|
|
36
|
+
fence = { character: marker[0], length: marker.length };
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
visible.push(line);
|
|
40
|
+
const heading = line.text.match(HEADING);
|
|
41
|
+
if (heading)
|
|
42
|
+
headings.push({ ...line, level: heading[1].length, text: heading[2].trim() });
|
|
43
|
+
const anchor = line.text.match(ANCHOR);
|
|
44
|
+
if (anchor)
|
|
45
|
+
anchors.push({ ...line, id: anchor[2] });
|
|
46
|
+
}
|
|
47
|
+
return { lines: visible, headings, anchors };
|
|
48
|
+
}
|
|
49
|
+
function parseDecisionDetailBlocks(content) {
|
|
50
|
+
const scanned = scanVisibleMarkdown(content);
|
|
51
|
+
const details = scanned.headings.filter((heading) => heading.level === 3 && DECISION_ID.test(heading.text));
|
|
52
|
+
return details.map((heading) => {
|
|
53
|
+
const next = scanned.headings.find((candidate) => (candidate.start > heading.start && candidate.level <= 3));
|
|
54
|
+
const end = next?.start ?? content.length;
|
|
55
|
+
return {
|
|
56
|
+
id: heading.text.match(DECISION_ID)[0],
|
|
57
|
+
heading: heading.text,
|
|
58
|
+
canonical: /\[needs-human-decision\]/i.test(heading.text),
|
|
59
|
+
start: heading.start,
|
|
60
|
+
end,
|
|
61
|
+
content: content.slice(heading.start, end).trim()
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function parseDecisionEvidence(evidence) {
|
|
66
|
+
const separator = evidence.indexOf('#');
|
|
67
|
+
if (separator <= 0 || evidence.indexOf('#', separator + 1) !== -1)
|
|
68
|
+
return { ok: false };
|
|
69
|
+
const artifact = evidence.slice(0, separator);
|
|
70
|
+
const anchor = evidence.slice(separator + 1);
|
|
71
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(artifact))
|
|
72
|
+
return { ok: false };
|
|
73
|
+
if (!anchor || /[\s\u0000-\u001f#]/.test(anchor))
|
|
74
|
+
return { ok: false };
|
|
75
|
+
return { ok: true, artifact, anchor };
|
|
76
|
+
}
|
|
77
|
+
function sameId(left, right) {
|
|
78
|
+
return left.toUpperCase() === right.toUpperCase();
|
|
79
|
+
}
|
|
80
|
+
function resolveDecisionDetail(content, rowId, anchor) {
|
|
81
|
+
const scanned = scanVisibleMarkdown(content);
|
|
82
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
83
|
+
const explicit = scanned.anchors.filter((candidate) => candidate.id === anchor);
|
|
84
|
+
if (explicit.length > 1) {
|
|
85
|
+
return { status: 'ambiguous', reason: `evidence anchor '${anchor}' occurs more than once`, candidates: [] };
|
|
86
|
+
}
|
|
87
|
+
if (explicit.length === 1) {
|
|
88
|
+
const marker = explicit[0];
|
|
89
|
+
const followingLine = scanned.lines.find((line) => line.start > marker.end && line.text.trim());
|
|
90
|
+
const followingHeading = followingLine
|
|
91
|
+
? scanned.headings.find((heading) => heading.start === followingLine.start)
|
|
92
|
+
: undefined;
|
|
93
|
+
const candidate = followingHeading?.level === 3
|
|
94
|
+
? blocks.find((block) => block.start === followingHeading.start)
|
|
95
|
+
: undefined;
|
|
96
|
+
if (!candidate || !sameId(candidate.id, rowId) || !candidate.canonical) {
|
|
97
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' does not identify a canonical ${rowId} detail` };
|
|
98
|
+
}
|
|
99
|
+
return { status: 'found', block: candidate };
|
|
100
|
+
}
|
|
101
|
+
if (!sameId(anchor, rowId)) {
|
|
102
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' is not the ledger id '${rowId}'` };
|
|
103
|
+
}
|
|
104
|
+
const matches = blocks.filter((block) => sameId(block.id, rowId) && block.canonical);
|
|
105
|
+
if (matches.length === 0)
|
|
106
|
+
return { status: 'missing', reason: `no canonical ${rowId} detail was found` };
|
|
107
|
+
if (matches.length > 1) {
|
|
108
|
+
return { status: 'ambiguous', reason: `more than one canonical ${rowId} detail was found`, candidates: matches };
|
|
109
|
+
}
|
|
110
|
+
return { status: 'found', block: matches[0] };
|
|
111
|
+
}
|
|
112
|
+
const INFORMAL_SHORT_HEADING = /^(?:AN|PL|CD|HD)-[1-9]\d*[::]\s*(?:上一轮)?简短复核$/i;
|
|
113
|
+
const INFORMAL_SHORT_FRAGMENT = /^简短(?:结论|说明)$/;
|
|
114
|
+
function isInformalShortBlock(block) {
|
|
115
|
+
if (block.canonical || block.content.length > 600)
|
|
116
|
+
return false;
|
|
117
|
+
if (!INFORMAL_SHORT_HEADING.test(block.heading))
|
|
118
|
+
return false;
|
|
119
|
+
const scanned = scanVisibleMarkdown(block.content);
|
|
120
|
+
const bodyLines = scanned.lines
|
|
121
|
+
.filter((line) => !scanned.headings.some((heading) => heading.start === line.start) && line.text.trim())
|
|
122
|
+
.map((line) => line.text);
|
|
123
|
+
if (bodyLines.length === 0 || bodyLines.length > 2)
|
|
124
|
+
return false;
|
|
125
|
+
const fragments = bodyLines.map((line) => line.match(/^\s*[-*]\s+([^\s].*?)\s*$/)?.[1] ?? null);
|
|
126
|
+
if (fragments.some((fragment) => fragment === null))
|
|
127
|
+
return false;
|
|
128
|
+
return fragments.every((fragment) => INFORMAL_SHORT_FRAGMENT.test(fragment));
|
|
129
|
+
}
|
|
130
|
+
function hasDirectPrecedingAnchor(block, scanned) {
|
|
131
|
+
return scanned.anchors.some((anchor) => (anchor.end < block.start
|
|
132
|
+
&& !scanned.lines.some((line) => line.start > anchor.end && line.start < block.start && line.text.trim())));
|
|
133
|
+
}
|
|
134
|
+
function inspectDecisionDetailDuplicates(content) {
|
|
135
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
136
|
+
const scanned = scanVisibleMarkdown(content);
|
|
137
|
+
const grouped = new Map();
|
|
138
|
+
for (const block of blocks) {
|
|
139
|
+
const key = block.id.toUpperCase();
|
|
140
|
+
const group = grouped.get(key) ?? [];
|
|
141
|
+
group.push(block);
|
|
142
|
+
grouped.set(key, group);
|
|
143
|
+
}
|
|
144
|
+
const duplicates = [];
|
|
145
|
+
for (const group of grouped.values()) {
|
|
146
|
+
if (group.length < 2)
|
|
147
|
+
continue;
|
|
148
|
+
const canonical = group.filter((block) => block.canonical);
|
|
149
|
+
const repairable = canonical.length === 1 && group.every((block) => (block.canonical || (!hasDirectPrecedingAnchor(block, scanned) && isInformalShortBlock(block))));
|
|
150
|
+
duplicates.push({ id: canonical[0]?.id ?? group[0].id, blocks: group, repairable });
|
|
151
|
+
}
|
|
152
|
+
if (duplicates.length === 0)
|
|
153
|
+
return { ok: true, blocks };
|
|
154
|
+
const summary = duplicates.map((duplicate) => {
|
|
155
|
+
const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
|
|
156
|
+
return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types}${duplicate.repairable ? '; safe repair available' : ''})`;
|
|
157
|
+
}).join(', ');
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
code: 'DECISION_DETAIL_DUPLICATE',
|
|
161
|
+
message: `Duplicate decision detail ids: ${summary}`,
|
|
162
|
+
blocks,
|
|
163
|
+
duplicates
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function repairDecisionDetailDuplicates(content) {
|
|
167
|
+
const inspection = inspectDecisionDetailDuplicates(content);
|
|
168
|
+
if (inspection.ok)
|
|
169
|
+
return { ok: true, changed: false, content };
|
|
170
|
+
const unsafe = inspection.duplicates.filter((duplicate) => !duplicate.repairable);
|
|
171
|
+
if (unsafe.length > 0) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
code: 'DECISION_DETAIL_AMBIGUOUS',
|
|
175
|
+
message: `Cannot safely repair duplicate decision detail ids: ${unsafe.map((duplicate) => duplicate.id).join(', ')}`,
|
|
176
|
+
content
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const removals = inspection.duplicates.flatMap((duplicate) => duplicate.blocks.filter((block) => !block.canonical));
|
|
180
|
+
let repaired = content;
|
|
181
|
+
for (const block of removals.sort((left, right) => right.start - left.start)) {
|
|
182
|
+
repaired = repaired.slice(0, block.start) + repaired.slice(block.end);
|
|
183
|
+
}
|
|
184
|
+
return { ok: true, changed: repaired !== content, content: repaired };
|
|
185
|
+
}
|
|
186
|
+
const FIELD_KEYS = {
|
|
187
|
+
'为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
|
|
188
|
+
'要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
|
|
189
|
+
'会影响什么': 'impact', 'what it affects': 'impact', 'what this affects': 'impact',
|
|
190
|
+
'如果选错或暂不处理': 'risk', 'if wrong or postponed': 'risk',
|
|
191
|
+
'what could go wrong': 'risk',
|
|
192
|
+
'打个比方': 'analogy', '例子': 'analogy', 'example': 'analogy',
|
|
193
|
+
'建议选哪个': 'recommend', 'recommended option': 'recommend', 'recommended choice': 'recommend',
|
|
194
|
+
'为什么这样建议': 'reason', 'why this recommendation': 'reason', 'why this choice': 'reason',
|
|
195
|
+
'是否需要实现': 'implementation', 'implementation required': 'implementation'
|
|
196
|
+
};
|
|
197
|
+
const TERMINAL_FIELD_KEYS = new Set(['recommend', 'reason', 'implementation']);
|
|
198
|
+
function normalizeDecisionDetailForDisplay(block) {
|
|
199
|
+
const body = block.content;
|
|
200
|
+
const scanned = scanVisibleMarkdown(body);
|
|
201
|
+
const heading = scanned.headings.find((candidate) => candidate.level === 3);
|
|
202
|
+
if (!heading)
|
|
203
|
+
return body.trim();
|
|
204
|
+
const options = scanned.headings.filter((candidate) => candidate.level === 4 && OPTION_HEADING.test(candidate.text));
|
|
205
|
+
const optionRanges = options.map((option, index) => ({
|
|
206
|
+
start: option.start,
|
|
207
|
+
end: options[index + 1]?.start
|
|
208
|
+
?? scanned.headings.find((candidate) => candidate.start > option.start && candidate.level <= 3)?.start
|
|
209
|
+
?? scanned.lines.find((line) => (line.start > option.start
|
|
210
|
+
&& FIELD.test(line.text)
|
|
211
|
+
&& TERMINAL_FIELD_KEYS.has(FIELD_KEYS[line.text.match(FIELD)[1].trim().toLowerCase()])))?.start
|
|
212
|
+
?? body.length
|
|
213
|
+
}));
|
|
214
|
+
const topLines = scanned.lines.filter((line) => (line.start > heading.end && !optionRanges.some((range) => line.start >= range.start && line.start < range.end)));
|
|
215
|
+
const fields = [];
|
|
216
|
+
for (const line of topLines) {
|
|
217
|
+
const match = line.text.match(FIELD);
|
|
218
|
+
if (!match)
|
|
219
|
+
continue;
|
|
220
|
+
const key = FIELD_KEYS[match[1].trim().toLowerCase()];
|
|
221
|
+
fields.push({ key: key ?? null, line });
|
|
222
|
+
}
|
|
223
|
+
if (fields.some((field) => field.key === null))
|
|
224
|
+
return body.trim();
|
|
225
|
+
const byKey = new Map();
|
|
226
|
+
const selectedRanges = [];
|
|
227
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
228
|
+
const field = fields[index];
|
|
229
|
+
if (!field.key)
|
|
230
|
+
continue;
|
|
231
|
+
const nextOption = optionRanges.find((range) => range.start > field.line.start)?.start ?? body.length;
|
|
232
|
+
const end = Math.min(fields[index + 1]?.line.start ?? body.length, nextOption);
|
|
233
|
+
const value = body.slice(field.line.start, end).trim();
|
|
234
|
+
byKey.set(field.key, value);
|
|
235
|
+
selectedRanges.push({ start: field.line.start, end });
|
|
236
|
+
}
|
|
237
|
+
selectedRanges.push(...optionRanges);
|
|
238
|
+
const required = ['decision', 'why', 'impact', 'recommend', 'reason'];
|
|
239
|
+
if (required.some((key) => !byKey.has(key)) || options.length < 2)
|
|
240
|
+
return body.trim();
|
|
241
|
+
const sortedRanges = selectedRanges.sort((left, right) => left.start - right.start);
|
|
242
|
+
let cursor = heading.end;
|
|
243
|
+
for (const range of sortedRanges) {
|
|
244
|
+
if (/\S/.test(body.slice(cursor, range.start)))
|
|
245
|
+
return body.trim();
|
|
246
|
+
cursor = Math.max(cursor, range.end);
|
|
247
|
+
}
|
|
248
|
+
if (/\S/.test(body.slice(cursor)))
|
|
249
|
+
return body.trim();
|
|
250
|
+
const ordered = [
|
|
251
|
+
byKey.get('decision'), byKey.get('why'), byKey.get('impact')
|
|
252
|
+
];
|
|
253
|
+
for (const optional of ['risk', 'analogy'])
|
|
254
|
+
if (byKey.has(optional))
|
|
255
|
+
ordered.push(byKey.get(optional));
|
|
256
|
+
ordered.push(...optionRanges.map((range) => body.slice(range.start, range.end).trim()));
|
|
257
|
+
ordered.push(byKey.get('recommend'), byKey.get('reason'));
|
|
258
|
+
if (byKey.has('implementation'))
|
|
259
|
+
ordered.push(byKey.get('implementation'));
|
|
260
|
+
return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
|
|
261
|
+
}
|
|
262
|
+
export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence, repairDecisionDetailDuplicates, resolveDecisionDetail };
|
|
263
|
+
//# sourceMappingURL=decision-details.js.map
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
3
3
|
import { isDecisionItem, listDecisionItems, selectDecisionItem } from './decision-items.js';
|
|
4
4
|
import { createImplementationInput, finalizeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs, selectDeclaredImplementationInput } from './implementation-inputs.js';
|
|
5
|
-
import { LEDGER_COLUMNS, LEDGER_HEADINGS,
|
|
5
|
+
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument } from './ledger.js';
|
|
6
6
|
import { resolveTaskContext } from './resolve-ref.js';
|
|
7
7
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
|
|
8
8
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
@@ -64,7 +64,10 @@ function applyHumanDecision(request, options = {}) {
|
|
|
64
64
|
let rows;
|
|
65
65
|
try {
|
|
66
66
|
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
67
|
-
|
|
67
|
+
const ledger = parseLedgerDocument(content);
|
|
68
|
+
if (!ledger.present)
|
|
69
|
+
return failed(LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
|
|
70
|
+
rows = ledger.rows;
|
|
68
71
|
}
|
|
69
72
|
catch (error) {
|
|
70
73
|
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
package/dist/lib/task/events.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-log.js';
|
|
2
|
+
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
3
3
|
import { buildArtifactLinkSection, artifactName, inspectArtifactDirectory, parseReviewedInputReference, parseCodePlanInputReference, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
4
4
|
import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
|
|
5
5
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
6
|
import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
7
|
-
import {
|
|
8
|
-
import { parseReviewSummary } from './review-artifacts.js';
|
|
7
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
8
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
10
|
import { findSectionHeading } from './sections.js';
|
|
11
11
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
@@ -101,7 +101,10 @@ function validateReviewFindingCounts(request, content, family, artifactPath) {
|
|
|
101
101
|
const stage = REVIEW_LEDGER_STAGES[family];
|
|
102
102
|
if (!stage || !artifactPath)
|
|
103
103
|
return null;
|
|
104
|
-
const
|
|
104
|
+
const ledger = parseLedgerDocument(content);
|
|
105
|
+
if (!ledger.present)
|
|
106
|
+
return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
|
|
107
|
+
const rows = ledger.rows;
|
|
105
108
|
const invalid = validateLedgerRows(rows);
|
|
106
109
|
if (invalid)
|
|
107
110
|
return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
@@ -125,6 +128,9 @@ function validateReviewFindingCounts(request, content, family, artifactPath) {
|
|
|
125
128
|
message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
|
|
126
129
|
};
|
|
127
130
|
}
|
|
131
|
+
const canonical = resolveCanonicalVerdict(parsed.summary);
|
|
132
|
+
if (!canonical.ok)
|
|
133
|
+
return { code: 'EVENT_VERDICT_INVALID', message: `${canonical.code}: ${canonical.message}` };
|
|
128
134
|
const report = parsed.summary.counts;
|
|
129
135
|
const reportVerdict = parsed.summary.verdict === 'Approved'
|
|
130
136
|
? 'approved'
|
|
@@ -352,7 +358,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
352
358
|
const section = locateActivityLog(content);
|
|
353
359
|
if (!section)
|
|
354
360
|
return failed(request, { code: 'EVENT_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
355
|
-
const rows = pairEntries(section.entries);
|
|
361
|
+
const rows = startedBackedRows(pairEntries(section.entries));
|
|
356
362
|
let normalized = request;
|
|
357
363
|
let artifactContext = null;
|
|
358
364
|
const initialParts = eventParts(request.event);
|
package/dist/lib/task/index.js
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
const USAGE = `Usage: ai task <command> [options]
|
|
2
2
|
|
|
3
3
|
Commands:
|
|
4
|
-
cat [--task <ref>] <artifact | N>
|
|
5
|
-
decisions, d [--task <ref>] [-i
|
|
6
|
-
files [--task <ref>]
|
|
7
|
-
grep
|
|
8
|
-
issue-body [--task <ref>] [--template] Print a deterministic Issue body from task.md
|
|
9
|
-
log [--task <ref>]
|
|
4
|
+
cat [--task <ref> | -t <ref>] <artifact | N> Print a task artifact (by name or number)
|
|
5
|
+
decisions, d [--task <ref> | -t <ref>] [--item <selector> | -i <selector>] List review decisions, or show one item's detail
|
|
6
|
+
files [--task <ref> | -t <ref>] List artifacts in a task dir (numbered)
|
|
7
|
+
grep [--current | --task <ref> | -t <ref>] <pattern> Literal search in the current or selected task
|
|
8
|
+
issue-body [--task <ref> | -t <ref>] [--template <path>] Print a deterministic Issue body from task.md
|
|
9
|
+
log [--task <ref> | -t <ref>] Render a task's activity log as a timeline
|
|
10
10
|
ls [--all | --blocked | --completed] List tasks (default: active)
|
|
11
|
-
show [--task <ref>]
|
|
12
|
-
status [--task <ref>]
|
|
11
|
+
show [--task <ref> | -t <ref>] Print a task.md
|
|
12
|
+
status [--task <ref> | -t <ref>] Aggregated status view (metadata / artifacts / git / platform)
|
|
13
13
|
|
|
14
14
|
Examples:
|
|
15
|
-
ai task cat 11 analysis
|
|
16
15
|
ai task cat analysis
|
|
17
|
-
ai task cat 11
|
|
18
|
-
ai task decisions 11
|
|
16
|
+
ai task cat --task 11 analysis
|
|
17
|
+
ai task decisions --task 11
|
|
19
18
|
ai task decisions --item 1
|
|
20
|
-
ai task d 11 PL-3 --format markdown
|
|
21
|
-
ai task files 11
|
|
19
|
+
ai task d --task 11 --item PL-3 --format markdown
|
|
20
|
+
ai task files --task 11
|
|
22
21
|
ai task grep resolveArtifact
|
|
23
22
|
ai task grep resolveArtifact --current
|
|
24
|
-
ai task grep resolveArtifact 11
|
|
25
|
-
ai task issue-body 11
|
|
26
|
-
ai task issue-body 11 --template .github/ISSUE_TEMPLATE/05_other.yml
|
|
27
|
-
ai task log 11
|
|
23
|
+
ai task grep resolveArtifact --task 11
|
|
24
|
+
ai task issue-body --task 11
|
|
25
|
+
ai task issue-body --task 11 --template .github/ISSUE_TEMPLATE/05_other.yml
|
|
26
|
+
ai task log --task 11
|
|
28
27
|
ai task ls
|
|
29
|
-
ai task show 11
|
|
30
|
-
ai task show TASK-20260612-162737
|
|
31
|
-
ai task status 11
|
|
28
|
+
ai task show --task 11
|
|
29
|
+
ai task show --task TASK-20260612-162737
|
|
30
|
+
ai task status --task 11
|
|
32
31
|
|
|
33
32
|
Run 'ai task <command> --help' for details.`;
|
|
34
33
|
export async function runTask(args) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { declareImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
4
|
-
import { LEDGER_COLUMNS, LEDGER_HEADINGS, nextHdId,
|
|
4
|
+
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.js';
|
|
5
5
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
6
6
|
import { writeTask } from './write.js';
|
|
7
7
|
import { allowsManualOverride } from './guard-override.js';
|
|
@@ -26,6 +26,8 @@ function normalizedEvidence(value) {
|
|
|
26
26
|
}
|
|
27
27
|
function ledgerReadErrorCode(error) {
|
|
28
28
|
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
29
|
+
if (code === LEDGER_SECTION_MISSING_CODE)
|
|
30
|
+
return code;
|
|
29
31
|
return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
|
|
30
32
|
}
|
|
31
33
|
function maxHandshakeRounds(repoRoot) {
|
|
@@ -60,13 +62,7 @@ function rowMutation(row) {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
function ledgerSectionMutation(content) {
|
|
63
|
-
|
|
64
|
-
return [];
|
|
65
|
-
const english = /^##\s+Activity Log\s*$/m.test(content);
|
|
66
|
-
return [{
|
|
67
|
-
kind: 'section', aliases: LEDGER_HEADINGS, heading: english ? 'Review Disagreement Ledger' : '审查分歧账本',
|
|
68
|
-
body: `| ${LEDGER_COLUMNS.join(' | ')} |\n|----|-------|-------|----------|--------|----------|`
|
|
69
|
-
}];
|
|
65
|
+
return [];
|
|
70
66
|
}
|
|
71
67
|
function implementationInputMutation(content, rows) {
|
|
72
68
|
const english = /^##\s+Activity Log\s*$/m.test(content);
|
|
@@ -92,7 +88,10 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
92
88
|
let rows;
|
|
93
89
|
try {
|
|
94
90
|
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
95
|
-
|
|
91
|
+
const ledger = parseLedgerDocument(content);
|
|
92
|
+
if (!ledger.present)
|
|
93
|
+
return failed(intent, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
|
|
94
|
+
rows = ledger.rows;
|
|
96
95
|
}
|
|
97
96
|
catch (error) {
|
|
98
97
|
return failed(intent, ledgerReadErrorCode(error), error instanceof Error ? error.message : String(error), resolved.taskId);
|
package/dist/lib/task/ledger.js
CHANGED
|
@@ -12,18 +12,25 @@ const LEDGER_TERMINAL = new Set(['confirmed', 'closed', 'human-decided']);
|
|
|
12
12
|
// Statuses that represent a decision row awaiting or carrying a human ruling.
|
|
13
13
|
const HUMAN_DECISION_STATUSES = new Set(['needs-human-decision', 'human-decided']);
|
|
14
14
|
const FINDING_STATUSES = new Set(['open', 'accepted', 'adjusted', 'refuted', 'cannot-judge', 'confirmed', 'needs-human-decision', 'closed', 'human-decided']);
|
|
15
|
+
const LEDGER_SECTION_MISSING_CODE = 'LEDGER_SECTION_MISSING';
|
|
16
|
+
const LEDGER_SECTION_MISSING_MESSAGE = 'review disagreement ledger section is missing';
|
|
15
17
|
function isReviewStage(stage) {
|
|
16
18
|
return stage === 'analysis' || stage === 'plan' || stage === 'code';
|
|
17
19
|
}
|
|
18
20
|
// Parse all rows of the disagreement ledger table. Skips the heading, the
|
|
19
21
|
// header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
|
|
20
22
|
// Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
|
|
21
|
-
function
|
|
23
|
+
function parseLedgerDocument(content) {
|
|
22
24
|
const table = parseTable(content, { sectionAliases: LEDGER_HEADINGS, columns: LEDGER_COLUMNS });
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (!table)
|
|
26
|
+
return { present: false, rows: [] };
|
|
27
|
+
return {
|
|
28
|
+
present: true,
|
|
29
|
+
rows: table.rows.map(({ values, sourceLine }) => ({
|
|
30
|
+
id: values.id, stage: values.stage, round: values.round, severity: values.severity,
|
|
31
|
+
status: values.status, evidence: values.evidence, sourceLine
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
27
34
|
}
|
|
28
35
|
function validateLedgerRows(rows) {
|
|
29
36
|
const seen = new Set();
|
|
@@ -104,5 +111,5 @@ function nextHdId(rows) {
|
|
|
104
111
|
}
|
|
105
112
|
return `HD-${max + 1}`;
|
|
106
113
|
}
|
|
107
|
-
export { LEDGER_HEADINGS, LEDGER_COLUMNS,
|
|
114
|
+
export { LEDGER_HEADINGS, LEDGER_COLUMNS, parseLedgerDocument, nextHdId, isReviewStage, validateLedgerRows, summarizeLedgerStage, LEDGER_TERMINAL, HUMAN_DECISION_STATUSES, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE };
|
|
108
115
|
//# sourceMappingURL=ledger.js.map
|
|
@@ -7,6 +7,8 @@ import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
|
7
7
|
import { locateHotTaskDirs, resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
8
8
|
import { configuredShortIdLength, executeShortIdCommand, loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
|
|
9
9
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
10
|
+
import { validateCurrentTaskContract } from './current-contract.js';
|
|
11
|
+
import { isValidAgentInfraVersion } from '../version.js';
|
|
10
12
|
const lifecycleIntentCatalog = [
|
|
11
13
|
'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
|
|
12
14
|
];
|
|
@@ -57,6 +59,9 @@ function failed(request, error, extra = {}) {
|
|
|
57
59
|
error, ...extra
|
|
58
60
|
};
|
|
59
61
|
}
|
|
62
|
+
export function taskLifecycleFailure(request, error, taskId = null) {
|
|
63
|
+
return failed(request, error, { taskId });
|
|
64
|
+
}
|
|
60
65
|
function normalizedRequest(request) {
|
|
61
66
|
if (!request || typeof request !== 'object' || !lifecycleIntentCatalog.includes(request.intent)) {
|
|
62
67
|
return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'intent is not in the lifecycle catalog' };
|
|
@@ -366,6 +371,11 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
366
371
|
return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
367
372
|
if (!locateActivityLog(content) && !allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'))
|
|
368
373
|
return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
374
|
+
if (spec.target === 'active') {
|
|
375
|
+
const contract = validateCurrentTaskContract(content);
|
|
376
|
+
if (!contract.ok)
|
|
377
|
+
return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
378
|
+
}
|
|
369
379
|
const registryRequest = executeShortIdCommand({
|
|
370
380
|
operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
|
|
371
381
|
shortIdLength: configuredShortIdLength(repoRoot)
|
|
@@ -394,6 +404,9 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
394
404
|
}
|
|
395
405
|
if (journal.taskId !== taskId || journal.intent !== request.intent)
|
|
396
406
|
return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
407
|
+
if (spec.target === 'active' && !isValidAgentInfraVersion(journal.metadata.agentInfraVersion)) {
|
|
408
|
+
return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `journal metadata agentInfraVersion must be a valid v-prefixed semver (received ${journal.metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
409
|
+
}
|
|
397
410
|
if (request.dryRun) {
|
|
398
411
|
if (!journal.failure)
|
|
399
412
|
return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
@@ -411,6 +424,9 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
411
424
|
catch (error) {
|
|
412
425
|
return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
413
426
|
}
|
|
427
|
+
if (spec.target === 'active' && !isValidAgentInfraVersion(metadata.agentInfraVersion)) {
|
|
428
|
+
return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `metadata agentInfraVersion must be a valid v-prefixed semver (received ${metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
429
|
+
}
|
|
414
430
|
journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
|
|
415
431
|
if (request.dryRun) {
|
|
416
432
|
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
|
|
@@ -2,9 +2,10 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
|
-
import {
|
|
5
|
+
import { validateCurrentTaskContract } from './current-contract.js';
|
|
6
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
|
|
6
7
|
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
7
|
-
import {
|
|
8
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
8
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
9
10
|
import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
10
11
|
import { normalizeAgentClients } from '../agent-clients/config.js';
|
|
@@ -350,9 +351,21 @@ function hasReviewAfterArtifact(taskDir, family, reviewFamily) {
|
|
|
350
351
|
function latestReviewApproved(taskDir, family) {
|
|
351
352
|
const round = highestRound(taskDir, family);
|
|
352
353
|
if (round === 0)
|
|
353
|
-
return false;
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
return { approved: false, error: null };
|
|
355
|
+
let content;
|
|
356
|
+
try {
|
|
357
|
+
content = fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8');
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return { approved: false, error: String(error) };
|
|
361
|
+
}
|
|
362
|
+
const parsed = parseReviewSummary(content);
|
|
363
|
+
if (!parsed.ok)
|
|
364
|
+
return { approved: false, error: `${parsed.code}: ${parsed.message}` };
|
|
365
|
+
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
366
|
+
if (!verdict.ok)
|
|
367
|
+
return { approved: false, error: `${verdict.code}: ${verdict.message}` };
|
|
368
|
+
return { approved: verdict.verdict === 'Approved', error: null };
|
|
356
369
|
}
|
|
357
370
|
function routeFromFacts(taskDir) {
|
|
358
371
|
const analysisRound = highestRound(taskDir, 'analysis');
|
|
@@ -369,7 +382,10 @@ function routeFromFacts(taskDir) {
|
|
|
369
382
|
const round = analysisReviewRound + 1;
|
|
370
383
|
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
371
384
|
}
|
|
372
|
-
|
|
385
|
+
const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
|
|
386
|
+
if (analysisVerdict.error)
|
|
387
|
+
return { error: analysisVerdict.error };
|
|
388
|
+
if (!analysisVerdict.approved) {
|
|
373
389
|
const round = analysisRound + 1;
|
|
374
390
|
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
375
391
|
}
|
|
@@ -381,7 +397,10 @@ function routeFromFacts(taskDir) {
|
|
|
381
397
|
const round = planReviewRound + 1;
|
|
382
398
|
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
383
399
|
}
|
|
384
|
-
|
|
400
|
+
const planVerdict = latestReviewApproved(taskDir, 'review-plan');
|
|
401
|
+
if (planVerdict.error)
|
|
402
|
+
return { error: planVerdict.error };
|
|
403
|
+
if (!planVerdict.approved) {
|
|
385
404
|
const round = planRound + 1;
|
|
386
405
|
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
387
406
|
}
|
|
@@ -393,7 +412,10 @@ function routeFromFacts(taskDir) {
|
|
|
393
412
|
const round = codeReviewRound + 1;
|
|
394
413
|
return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
|
|
395
414
|
}
|
|
396
|
-
|
|
415
|
+
const codeVerdict = latestReviewApproved(taskDir, 'review-code');
|
|
416
|
+
if (codeVerdict.error)
|
|
417
|
+
return { error: codeVerdict.error };
|
|
418
|
+
if (!codeVerdict.approved) {
|
|
397
419
|
const round = codeRound + 1;
|
|
398
420
|
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
399
421
|
}
|
|
@@ -418,13 +440,25 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
418
440
|
return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
|
|
419
441
|
}
|
|
420
442
|
let metadata;
|
|
421
|
-
|
|
422
|
-
|
|
443
|
+
let currentLedger = null;
|
|
444
|
+
if (resolved.state === 'active') {
|
|
445
|
+
const contract = validateCurrentTaskContract(content);
|
|
446
|
+
if (!contract.ok)
|
|
447
|
+
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
448
|
+
metadata = contract.metadata;
|
|
449
|
+
currentLedger = contract.ledger;
|
|
423
450
|
}
|
|
424
|
-
|
|
425
|
-
|
|
451
|
+
else {
|
|
452
|
+
try {
|
|
453
|
+
metadata = parseTypedTaskFrontmatter(content);
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
457
|
+
}
|
|
426
458
|
}
|
|
427
459
|
const routed = routeFromFacts(resolved.taskDir);
|
|
460
|
+
if (routed && 'error' in routed)
|
|
461
|
+
return failed('ORCHESTRATION_REVIEW_INVALID', routed.error, resolved.taskId);
|
|
428
462
|
if (!routed)
|
|
429
463
|
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
430
464
|
const run = readRun(resolved.taskDir);
|
|
@@ -440,7 +474,10 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
440
474
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
441
475
|
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
442
476
|
}
|
|
443
|
-
const
|
|
477
|
+
const ledger = currentLedger ?? parseLedgerDocument(content);
|
|
478
|
+
if (!ledger.present)
|
|
479
|
+
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
|
|
480
|
+
const rows = ledger.rows;
|
|
444
481
|
const ledgerError = validateLedgerRows(rows);
|
|
445
482
|
if (ledgerError)
|
|
446
483
|
return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
|