@fitlab-ai/agent-infra 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +12 -1
- package/dist/bin/cli.js +13 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +3 -0
- package/dist/lib/init.js +62 -23
- package/dist/lib/prompt.js +49 -1
- package/dist/lib/sandbox/commands/create.js +10 -2
- package/dist/lib/sandbox/commands/enter.js +8 -7
- package/dist/lib/sandbox/commands/list-running.js +62 -28
- package/dist/lib/sandbox/commands/ls.js +20 -22
- package/dist/lib/sandbox/commands/rebuild.js +3 -11
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/index.js +7 -3
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/sandbox/tools.js +1 -1
- package/dist/lib/table.js +29 -0
- package/dist/lib/task/commands/ls.js +122 -0
- package/dist/lib/task/commands/show.js +135 -0
- package/dist/lib/task/frontmatter.js +32 -0
- package/dist/lib/task/index.js +41 -0
- package/dist/lib/task/short-id.js +80 -0
- package/dist/lib/update.js +59 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +3 -0
- package/lib/init.ts +87 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/create.ts +11 -2
- package/lib/sandbox/commands/enter.ts +8 -7
- package/lib/sandbox/commands/list-running.ts +70 -31
- package/lib/sandbox/commands/ls.ts +25 -25
- package/lib/sandbox/commands/rebuild.ts +3 -12
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/index.ts +7 -3
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/sandbox/tools.ts +1 -1
- package/lib/table.ts +32 -0
- package/lib/task/commands/ls.ts +138 -0
- package/lib/task/commands/show.ts +139 -0
- package/lib/task/frontmatter.ts +30 -0
- package/lib/task/index.ts +44 -0
- package/lib/task/short-id.ts +97 -0
- package/lib/update.ts +71 -30
- package/package.json +1 -1
- package/templates/.agents/README.en.md +32 -0
- package/templates/.agents/README.zh-CN.md +32 -0
- package/templates/.agents/hooks/auto-resume.sh +87 -0
- 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/milestone-inference.github.en.md +4 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -1
- package/templates/.agents/rules/next-step-output.en.md +59 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +59 -0
- package/templates/.agents/rules/task-short-id.en.md +133 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +105 -0
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +17 -0
- package/templates/.agents/scripts/task-short-id.js +556 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -11
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +13 -12
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +17 -5
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +17 -6
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +17 -5
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +17 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +15 -9
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +15 -10
- package/templates/.agents/skills/close-codescan/SKILL.en.md +16 -5
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +16 -5
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +16 -5
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +16 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +13 -5
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -6
- package/templates/.agents/skills/code-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +10 -5
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +10 -5
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/reference/report-template.en.md +8 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +8 -0
- package/templates/.agents/skills/commit/SKILL.en.md +5 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +9 -9
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +9 -9
- package/templates/.agents/skills/complete-task/SKILL.en.md +17 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +9 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +9 -5
- package/templates/.agents/skills/create-pr/config/verify.json +2 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +29 -15
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +29 -16
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +20 -6
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +20 -6
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +20 -6
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +20 -6
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +19 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +19 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -11
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -12
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +8 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +9 -9
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +9 -9
- package/templates/.agents/skills/review-code/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +7 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +3 -2
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +3 -2
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +15 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +15 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -1
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -1
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +112 -21
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +1 -1
- package/templates/.claude/settings.json +11 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
|
+
const SHORT_ID_RE = /^#\d+$/;
|
|
7
|
+
const REGISTRY_NAME = ".short-ids.json";
|
|
8
|
+
const LOCK_NAME = ".short-ids.json.lock";
|
|
9
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 5000;
|
|
10
|
+
// Kept in sync with lib/defaults.json's task.shortIdLength. Used when there is
|
|
11
|
+
// no `--short-id-length` flag and no readable `task.shortIdLength` in
|
|
12
|
+
// .agents/.airc.json (e.g. the project upgraded but hasn't re-run
|
|
13
|
+
// ai update-agent-infra to backfill the field).
|
|
14
|
+
const DEFAULT_SHORT_ID_LENGTH = 2;
|
|
15
|
+
|
|
16
|
+
// process.stdout.write / process.stderr.write are non-blocking when the
|
|
17
|
+
// destination is a pipe (e.g. when spawned via child_process.spawnSync). On
|
|
18
|
+
// some platforms (notably macOS) the Node process can exit before the buffer
|
|
19
|
+
// flushes, leaving the parent with empty stdout. Use fs.writeSync to guarantee
|
|
20
|
+
// synchronous, fully-flushed writes — this is critical because the parent
|
|
21
|
+
// CLI/test code relies on stdout to carry the resolved task id / short id.
|
|
22
|
+
function writeStdout(text) {
|
|
23
|
+
fs.writeSync(1, text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeStderr(text) {
|
|
27
|
+
fs.writeSync(2, text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function usage() {
|
|
31
|
+
return [
|
|
32
|
+
"Usage: task-short-id.js <subcommand> [args]",
|
|
33
|
+
"",
|
|
34
|
+
"Subcommands:",
|
|
35
|
+
" alloc <task-id> Allocate short id for a task in the registry",
|
|
36
|
+
" release <task-id> Release short id (idempotent; exit 0 if not present)",
|
|
37
|
+
" resolve <#N> Resolve short id to full task id",
|
|
38
|
+
" list Print registry JSON",
|
|
39
|
+
" list --verify Read-only check; exit 1 if active dir / registry disagree",
|
|
40
|
+
"",
|
|
41
|
+
"Options:",
|
|
42
|
+
" --active-dir <path> Override active dir (default: <repo>/.agents/workspace/active)",
|
|
43
|
+
" --short-id-length N Override configured width (default: from .airc.json or 2)"
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
const args = { positional: [], activeDir: null, shortIdLength: null, verify: false, help: false };
|
|
49
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
50
|
+
const a = argv[i];
|
|
51
|
+
if (a === "--active-dir") {
|
|
52
|
+
args.activeDir = argv[++i];
|
|
53
|
+
} else if (a === "--short-id-length") {
|
|
54
|
+
args.shortIdLength = Number(argv[++i]);
|
|
55
|
+
} else if (a === "--verify") {
|
|
56
|
+
args.verify = true;
|
|
57
|
+
} else if (a === "-h" || a === "--help") {
|
|
58
|
+
args.help = true;
|
|
59
|
+
} else if (a.startsWith("--")) {
|
|
60
|
+
throw new Error(`Unknown option: ${a}`);
|
|
61
|
+
} else {
|
|
62
|
+
args.positional.push(a);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return args;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findRepoRoot(start) {
|
|
69
|
+
let dir = path.resolve(start || process.cwd());
|
|
70
|
+
for (;;) {
|
|
71
|
+
if (fs.existsSync(path.join(dir, ".agents", ".airc.json"))) return dir;
|
|
72
|
+
const parent = path.dirname(dir);
|
|
73
|
+
if (parent === dir) return null;
|
|
74
|
+
dir = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readShortIdLength(repoRoot, override) {
|
|
79
|
+
if (typeof override === "number" && Number.isFinite(override) && override >= 1) {
|
|
80
|
+
return override;
|
|
81
|
+
}
|
|
82
|
+
if (!repoRoot) return DEFAULT_SHORT_ID_LENGTH;
|
|
83
|
+
try {
|
|
84
|
+
const cfgPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
85
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
86
|
+
const v = cfg && cfg.task && cfg.task.shortIdLength;
|
|
87
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 1) return v;
|
|
88
|
+
} catch {
|
|
89
|
+
// ignore
|
|
90
|
+
}
|
|
91
|
+
return DEFAULT_SHORT_ID_LENGTH;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readRegistry(registryPath) {
|
|
95
|
+
if (!fs.existsSync(registryPath)) {
|
|
96
|
+
return { version: 1, ids: {} };
|
|
97
|
+
}
|
|
98
|
+
let raw;
|
|
99
|
+
try {
|
|
100
|
+
raw = fs.readFileSync(registryPath, "utf8");
|
|
101
|
+
} catch (e) {
|
|
102
|
+
writeStderr(`Error: cannot read registry ${registryPath}: ${e.message}\n`);
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(raw);
|
|
107
|
+
if (!data || typeof data !== "object" || !data.ids || typeof data.ids !== "object") {
|
|
108
|
+
writeStderr(`Error: registry ${registryPath} has invalid schema\n`);
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
if (data.version !== 1) data.version = 1;
|
|
112
|
+
return data;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
writeStderr(`Error: registry ${registryPath} is not valid JSON: ${e.message}\n`);
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function writeRegistryAtomic(data, registryPath) {
|
|
120
|
+
const tmpPath = `${registryPath}.tmp.${process.pid}`;
|
|
121
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
122
|
+
fs.renameSync(tmpPath, registryPath);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function withRegistryLock(activeDir, fn, timeoutMs = DEFAULT_LOCK_TIMEOUT_MS) {
|
|
126
|
+
fs.mkdirSync(activeDir, { recursive: true });
|
|
127
|
+
const lockDir = path.join(activeDir, LOCK_NAME);
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
for (;;) {
|
|
130
|
+
try {
|
|
131
|
+
fs.mkdirSync(lockDir, { recursive: false });
|
|
132
|
+
break;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (e.code !== "EEXIST") throw e;
|
|
135
|
+
if (Date.now() - start > timeoutMs) {
|
|
136
|
+
writeStderr(`Error: registry lock timeout after ${timeoutMs}ms\n`);
|
|
137
|
+
process.exit(3);
|
|
138
|
+
}
|
|
139
|
+
const elapsed = Date.now() - start;
|
|
140
|
+
const wait = Math.min(500, 50 * Math.pow(2, Math.floor(elapsed / 200)));
|
|
141
|
+
const deadline = Date.now() + wait;
|
|
142
|
+
while (Date.now() < deadline) {
|
|
143
|
+
/* busy wait, ms-scale */
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Register cleanup that runs even on process.exit (which skips try/finally).
|
|
148
|
+
const cleanup = () => {
|
|
149
|
+
try {
|
|
150
|
+
fs.rmdirSync(lockDir);
|
|
151
|
+
} catch {
|
|
152
|
+
/* lock-dir already removed */
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
process.once("exit", cleanup);
|
|
156
|
+
try {
|
|
157
|
+
return fn();
|
|
158
|
+
} finally {
|
|
159
|
+
process.removeListener("exit", cleanup);
|
|
160
|
+
cleanup();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function padShortId(n, shortIdLength) {
|
|
165
|
+
return String(n).padStart(shortIdLength, "0");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function allocateMinFreeInt(registry, shortIdLength) {
|
|
169
|
+
const maxN = Math.pow(10, shortIdLength) - 1;
|
|
170
|
+
for (let n = 1; n <= maxN; n += 1) {
|
|
171
|
+
if (!registry.ids[padShortId(n, shortIdLength)]) return n;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseShortIdArg(arg, shortIdLength) {
|
|
177
|
+
const L = shortIdLength;
|
|
178
|
+
const max = Math.pow(10, L) - 1;
|
|
179
|
+
// Accept bare numeric or '#'-prefixed; canonicalize to zero-padded key.
|
|
180
|
+
// Bare numeric is the recommended form (no shell quoting needed).
|
|
181
|
+
const m = /^#?(\d+)$/.exec(arg);
|
|
182
|
+
if (!m) {
|
|
183
|
+
writeStderr(
|
|
184
|
+
`Error: invalid short id format '${arg}', ` +
|
|
185
|
+
`expected bare digits (recommended) or '#'-prefixed digits; ` +
|
|
186
|
+
`e.g. '11' or '#11' (shortIdLength=${L}, max=${max})\n`
|
|
187
|
+
);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
const n = Number(m[1]);
|
|
191
|
+
if (n === 0) {
|
|
192
|
+
writeStderr(
|
|
193
|
+
`Error: short id '${arg}' is invalid (#${"0".repeat(L)} is reserved)\n`
|
|
194
|
+
);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
if (n > max) {
|
|
198
|
+
writeStderr(
|
|
199
|
+
`Error: short id ${n} exceeds shortIdLength=${L} capacity (max=${max}); ` +
|
|
200
|
+
`archive tasks or raise task.shortIdLength in .agents/.airc.json\n`
|
|
201
|
+
);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
return String(n).padStart(L, "0");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function planTransaction(registry, activeDir, shortIdLength) {
|
|
208
|
+
const maxN = Math.pow(10, shortIdLength) - 1;
|
|
209
|
+
|
|
210
|
+
// A1: active task id set
|
|
211
|
+
const activeTaskIds = new Set(
|
|
212
|
+
fs
|
|
213
|
+
.readdirSync(activeDir)
|
|
214
|
+
.filter((d) => TASK_ID_RE.test(d))
|
|
215
|
+
.filter((d) => fs.existsSync(path.join(activeDir, d, "task.md")))
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// A2: stale entries (registry points at a task no longer active)
|
|
219
|
+
const pendingRegistryDeletes = [];
|
|
220
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
221
|
+
if (!activeTaskIds.has(taskId)) pendingRegistryDeletes.push(key);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const projectedIds = { ...registry.ids };
|
|
225
|
+
for (const key of pendingRegistryDeletes) delete projectedIds[key];
|
|
226
|
+
|
|
227
|
+
// A3: duplicate key detection (after stale cleanup)
|
|
228
|
+
const taskIdToKey = new Map();
|
|
229
|
+
for (const [key, taskId] of Object.entries(projectedIds)) {
|
|
230
|
+
if (taskIdToKey.has(taskId)) {
|
|
231
|
+
const existingKey = taskIdToKey.get(taskId);
|
|
232
|
+
writeStderr(
|
|
233
|
+
`Error: duplicate registry entries for taskId ${taskId} at keys [#${existingKey}, #${key}]; manual resolution required\n`
|
|
234
|
+
);
|
|
235
|
+
process.exit(2);
|
|
236
|
+
}
|
|
237
|
+
taskIdToKey.set(taskId, key);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The registry is the sole source of truth: short ids are allocated only by
|
|
241
|
+
// explicit `alloc` (planAlloc), never inferred from task.md or auto-allocated
|
|
242
|
+
// for active tasks. Read paths (resolve/list) only run stale cleanup (A2).
|
|
243
|
+
const plannedRegistryWrites = [];
|
|
244
|
+
|
|
245
|
+
const tx = {
|
|
246
|
+
_registry: registry,
|
|
247
|
+
_activeDir: activeDir,
|
|
248
|
+
_registrySnapshot: { ...registry.ids },
|
|
249
|
+
_pendingRegistryDeletes: pendingRegistryDeletes,
|
|
250
|
+
_plannedRegistryWrites: plannedRegistryWrites,
|
|
251
|
+
_projectedIds: projectedIds,
|
|
252
|
+
_taskIdToKey: taskIdToKey,
|
|
253
|
+
_shortIdLength: shortIdLength,
|
|
254
|
+
_maxN: maxN,
|
|
255
|
+
|
|
256
|
+
planAlloc(taskId) {
|
|
257
|
+
const taskMdPath = path.join(activeDir, taskId, "task.md");
|
|
258
|
+
if (!fs.existsSync(taskMdPath)) {
|
|
259
|
+
throw new Error(`planAlloc: task.md not found for ${taskId}`);
|
|
260
|
+
}
|
|
261
|
+
if (this._taskIdToKey.has(taskId)) {
|
|
262
|
+
return this._taskIdToKey.get(taskId);
|
|
263
|
+
}
|
|
264
|
+
const inUse = Object.keys(this._projectedIds).length;
|
|
265
|
+
if (inUse >= this._maxN) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Error: short id width exhausted (current shortIdLength=${this._shortIdLength}, ` +
|
|
268
|
+
`${inUse}/${this._maxN} slots in use). Archive some active tasks or raise task.shortIdLength.`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
const n = allocateMinFreeInt({ ids: this._projectedIds }, this._shortIdLength);
|
|
272
|
+
const key = padShortId(n, this._shortIdLength);
|
|
273
|
+
this._projectedIds[key] = taskId;
|
|
274
|
+
this._taskIdToKey.set(taskId, key);
|
|
275
|
+
this._plannedRegistryWrites.push({ key, taskId });
|
|
276
|
+
return key; // zero-padded; matches registry key
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
planRelease(taskId) {
|
|
280
|
+
const key = this._taskIdToKey.get(taskId);
|
|
281
|
+
if (!key) return; // idempotent
|
|
282
|
+
this._plannedRegistryWrites = this._plannedRegistryWrites.filter(
|
|
283
|
+
(w) => w.taskId !== taskId
|
|
284
|
+
);
|
|
285
|
+
this._pendingRegistryDeletes.push(key);
|
|
286
|
+
delete this._projectedIds[key];
|
|
287
|
+
this._taskIdToKey.delete(taskId);
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
commit(registryPath) {
|
|
291
|
+
// Apply registry mutation in memory, then persist atomically.
|
|
292
|
+
for (const key of this._pendingRegistryDeletes) delete this._registry.ids[key];
|
|
293
|
+
for (const { key, taskId } of this._plannedRegistryWrites) {
|
|
294
|
+
this._registry.ids[key] = taskId;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
writeRegistryAtomic(this._registry, registryPath);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
this._registry.ids = this._registrySnapshot;
|
|
300
|
+
throw new Error(`Failed to persist registry to ${registryPath}: ${e.message}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return tx;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function verifyRegistry(registry, activeDir) {
|
|
309
|
+
const activeTaskIds = new Set(
|
|
310
|
+
fs
|
|
311
|
+
.readdirSync(activeDir)
|
|
312
|
+
.filter((d) => TASK_ID_RE.test(d))
|
|
313
|
+
.filter((d) => fs.existsSync(path.join(activeDir, d, "task.md")))
|
|
314
|
+
);
|
|
315
|
+
const registryTaskIds = new Set(Object.values(registry.ids));
|
|
316
|
+
const missing_in_registry = [];
|
|
317
|
+
for (const taskId of activeTaskIds) {
|
|
318
|
+
if (!registryTaskIds.has(taskId)) {
|
|
319
|
+
missing_in_registry.push({ taskId });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const orphans_in_registry = [];
|
|
323
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
324
|
+
if (!activeTaskIds.has(taskId)) {
|
|
325
|
+
orphans_in_registry.push({ key: `#${key}`, taskId });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const taskIdToKeys = new Map();
|
|
329
|
+
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
330
|
+
if (!taskIdToKeys.has(taskId)) taskIdToKeys.set(taskId, []);
|
|
331
|
+
taskIdToKeys.get(taskId).push(key);
|
|
332
|
+
}
|
|
333
|
+
const duplicate_registry_keys = [];
|
|
334
|
+
for (const [taskId, keys] of taskIdToKeys) {
|
|
335
|
+
if (keys.length > 1) {
|
|
336
|
+
duplicate_registry_keys.push({ taskId, keys: keys.map((k) => `#${k}`) });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
missing_in_registry,
|
|
341
|
+
orphans_in_registry,
|
|
342
|
+
duplicate_registry_keys
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function cmdAlloc(taskId, activeDir, registryPath, shortIdLength) {
|
|
347
|
+
if (!TASK_ID_RE.test(taskId)) {
|
|
348
|
+
writeStderr(`Error: invalid task id format '${taskId}'\n`);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
return withRegistryLock(activeDir, () => {
|
|
352
|
+
const taskMdPath = path.join(activeDir, taskId, "task.md");
|
|
353
|
+
if (!fs.existsSync(taskMdPath)) {
|
|
354
|
+
writeStderr(`Error: task ${taskId} not found in ${activeDir} (no task.md)\n`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
const registry = readRegistry(registryPath);
|
|
358
|
+
const tx = planTransaction(registry, activeDir, shortIdLength);
|
|
359
|
+
let shortId;
|
|
360
|
+
try {
|
|
361
|
+
shortId = tx.planAlloc(taskId);
|
|
362
|
+
} catch (e) {
|
|
363
|
+
writeStderr(`${e.message}\n`);
|
|
364
|
+
process.exit(2);
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
tx.commit(registryPath);
|
|
368
|
+
} catch (e) {
|
|
369
|
+
writeStderr(`${e.message}\n`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
// shortId is already zero-padded (returned by tx.planAlloc; matches registry key)
|
|
373
|
+
writeStdout(`#${shortId}\n`);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function cmdRelease(taskId, activeDir, registryPath, shortIdLength) {
|
|
378
|
+
if (!TASK_ID_RE.test(taskId)) {
|
|
379
|
+
writeStderr(`Error: invalid task id format '${taskId}'\n`);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
return withRegistryLock(activeDir, () => {
|
|
383
|
+
const registry = readRegistry(registryPath);
|
|
384
|
+
const tx = planTransaction(registry, activeDir, shortIdLength);
|
|
385
|
+
tx.planRelease(taskId);
|
|
386
|
+
try {
|
|
387
|
+
tx.commit(registryPath);
|
|
388
|
+
} catch (e) {
|
|
389
|
+
writeStderr(`${e.message}\n`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
// idempotent exit 0
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function cmdResolve(shortIdArg, activeDir, registryPath, shortIdLength) {
|
|
397
|
+
// Accepts bare digits ('11') or '#'-prefixed form ('#11', '#11', '#005'); normalized by
|
|
398
|
+
// numeric value with capacity check (n > 10^L-1) and reserved-zero rejection.
|
|
399
|
+
const key = parseShortIdArg(shortIdArg, shortIdLength);
|
|
400
|
+
return withRegistryLock(activeDir, () => {
|
|
401
|
+
const registry = readRegistry(registryPath);
|
|
402
|
+
const tx = planTransaction(registry, activeDir, shortIdLength);
|
|
403
|
+
const taskId = tx._projectedIds[key];
|
|
404
|
+
if (!taskId) {
|
|
405
|
+
const hasPendingMutations =
|
|
406
|
+
tx._plannedRegistryWrites.length > 0 ||
|
|
407
|
+
tx._pendingRegistryDeletes.length > 0;
|
|
408
|
+
if (hasPendingMutations) {
|
|
409
|
+
try {
|
|
410
|
+
tx.commit(registryPath);
|
|
411
|
+
} catch (e) {
|
|
412
|
+
writeStderr(`${e.message}\n`);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (Object.keys(tx._projectedIds).length === 0) {
|
|
417
|
+
writeStderr(
|
|
418
|
+
`Error: short id '#${key}' not found; active task registry is empty.\n`
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
writeStderr(
|
|
422
|
+
`Error: short id '#${key}' not found in active task registry ` +
|
|
423
|
+
`(it may have been cleaned up after archival; check 'task-short-id.js list').\n`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
tx.commit(registryPath);
|
|
430
|
+
} catch (e) {
|
|
431
|
+
writeStderr(`${e.message}\n`);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
writeStdout(`${taskId}\n`);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function cmdList(activeDir, registryPath, verify) {
|
|
439
|
+
if (!verify) {
|
|
440
|
+
const registry = readRegistry(registryPath);
|
|
441
|
+
writeStdout(`${JSON.stringify(registry, null, 2)}\n`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const registry = readRegistry(registryPath);
|
|
445
|
+
if (!fs.existsSync(activeDir)) {
|
|
446
|
+
writeStdout("");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const diff = verifyRegistry(registry, activeDir);
|
|
450
|
+
const hasIssues =
|
|
451
|
+
diff.missing_in_registry.length > 0 ||
|
|
452
|
+
diff.orphans_in_registry.length > 0 ||
|
|
453
|
+
diff.duplicate_registry_keys.length > 0;
|
|
454
|
+
if (hasIssues) {
|
|
455
|
+
writeStdout(`${JSON.stringify(diff, null, 2)}\n`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
// consistent: empty stdout, exit 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function main(argv) {
|
|
462
|
+
let args;
|
|
463
|
+
try {
|
|
464
|
+
args = parseArgs(argv);
|
|
465
|
+
} catch (e) {
|
|
466
|
+
writeStderr(`${e.message}\n${usage()}\n`);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
if (args.help || args.positional.length === 0) {
|
|
470
|
+
writeStdout(`${usage()}\n`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const subcommand = args.positional[0];
|
|
474
|
+
const repoRoot = findRepoRoot(process.cwd());
|
|
475
|
+
const activeDir = args.activeDir
|
|
476
|
+
? path.resolve(args.activeDir)
|
|
477
|
+
: repoRoot
|
|
478
|
+
? path.join(repoRoot, ".agents", "workspace", "active")
|
|
479
|
+
: null;
|
|
480
|
+
if (!activeDir) {
|
|
481
|
+
writeStderr(
|
|
482
|
+
`Error: cannot locate active dir (no .agents/.airc.json found above ${process.cwd()})\n`
|
|
483
|
+
);
|
|
484
|
+
process.exit(2);
|
|
485
|
+
}
|
|
486
|
+
const shortIdLength = readShortIdLength(repoRoot, args.shortIdLength);
|
|
487
|
+
const registryPath = path.join(activeDir, REGISTRY_NAME);
|
|
488
|
+
|
|
489
|
+
switch (subcommand) {
|
|
490
|
+
case "alloc":
|
|
491
|
+
if (!args.positional[1]) {
|
|
492
|
+
writeStderr(`Usage: alloc <task-id>\n`);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
return cmdAlloc(args.positional[1], activeDir, registryPath, shortIdLength);
|
|
496
|
+
case "release":
|
|
497
|
+
if (!args.positional[1]) {
|
|
498
|
+
writeStderr(`Usage: release <task-id>\n`);
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
return cmdRelease(args.positional[1], activeDir, registryPath, shortIdLength);
|
|
502
|
+
case "resolve":
|
|
503
|
+
if (!args.positional[1]) {
|
|
504
|
+
writeStderr(`Usage: resolve <#N>\n`);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
return cmdResolve(args.positional[1], activeDir, registryPath, shortIdLength);
|
|
508
|
+
case "list":
|
|
509
|
+
return cmdList(activeDir, registryPath, args.verify);
|
|
510
|
+
default:
|
|
511
|
+
writeStderr(`Unknown subcommand: ${subcommand}\n${usage()}\n`);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Compare canonicalized (symlink-resolved) paths so this script still runs as a
|
|
517
|
+
// CLI when invoked through a temp-dir symlink (notably /var/folders on macOS,
|
|
518
|
+
// which is a symlink to /private/var/folders; process.argv[1] keeps the
|
|
519
|
+
// symlinked path while import.meta.url is auto-resolved to the realpath).
|
|
520
|
+
const isCli = (() => {
|
|
521
|
+
const entry = process.argv[1];
|
|
522
|
+
if (!entry) return false;
|
|
523
|
+
try {
|
|
524
|
+
const realEntry = fs.realpathSync(entry);
|
|
525
|
+
const realModule = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
526
|
+
return realEntry === realModule;
|
|
527
|
+
} catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
})();
|
|
531
|
+
|
|
532
|
+
if (isCli) {
|
|
533
|
+
main(process.argv.slice(2));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export {
|
|
537
|
+
TASK_ID_RE,
|
|
538
|
+
SHORT_ID_RE,
|
|
539
|
+
REGISTRY_NAME,
|
|
540
|
+
parseArgs,
|
|
541
|
+
findRepoRoot,
|
|
542
|
+
readShortIdLength,
|
|
543
|
+
readRegistry,
|
|
544
|
+
writeRegistryAtomic,
|
|
545
|
+
withRegistryLock,
|
|
546
|
+
padShortId,
|
|
547
|
+
parseShortIdArg,
|
|
548
|
+
allocateMinFreeInt,
|
|
549
|
+
planTransaction,
|
|
550
|
+
verifyRegistry,
|
|
551
|
+
cmdAlloc,
|
|
552
|
+
cmdRelease,
|
|
553
|
+
cmdResolve,
|
|
554
|
+
cmdList,
|
|
555
|
+
main
|
|
556
|
+
};
|
|
@@ -27,6 +27,10 @@ tail .agents/workspace/active/{task-id}/task.md
|
|
|
27
27
|
|
|
28
28
|
Before the state check is complete, do not make external-state assertions such as "the code is unchanged", "tests passed", or "there are no other references", including in reasoning. This gate is only a structural floor; evidence pairing and authenticity still require the report template and review discipline.
|
|
29
29
|
|
|
30
|
+
## Task id short ref
|
|
31
|
+
|
|
32
|
+
> If `{task-id}` matches `^[#]?[0-9]+$` (bare numeric or `#`-prefixed), follow the "SKILL parameter resolver" section of `.agents/rules/task-short-id.md`; treat `{task-id}` as the resolved full `TASK-YYYYMMDD-HHMMSS` form for every downstream command.
|
|
33
|
+
|
|
30
34
|
## Steps
|
|
31
35
|
|
|
32
36
|
### 1. Verify Prerequisites
|
|
@@ -61,6 +65,8 @@ If `task.md` contains these source fields, also read the corresponding source in
|
|
|
61
65
|
- `codescan_alert_number` - Code Scanning alert
|
|
62
66
|
- `security_alert_number` - Dependabot alert
|
|
63
67
|
|
|
68
|
+
**Round ≥ 2: respond to the prior review (only when a review artifact exists)**: if the task directory contains `review-analysis.md` / `review-analysis-r{N}.md`, read the highest-round review report; add a `## Response to Prior Review` section to this round's analysis artifact, and for each finding verify it via Read/Grep before acting (holds → accept and fix; judged hallucinated/unfounded → rebut with counter-evidence rather than defaulting to compliance); record any open disagreement under `## Open Questions`. Round 1 has no review, so skip this section.
|
|
69
|
+
|
|
64
70
|
### 4. Perform Requirements Analysis
|
|
65
71
|
|
|
66
72
|
Before analysis begins: if `start_date` in the frontmatter is empty, write today's date immediately (command: `date +%F`, format `YYYY-MM-DD`); keep any existing value. Before writing, read `.agents/rules/version-stamp.md` and refresh `updated_at` / `agent_infra_version` at the same time.
|
|
@@ -150,15 +156,11 @@ Update `.agents/workspace/active/{task-id}/task.md`:
|
|
|
150
156
|
- Mark requirement-analysis as complete in workflow progress and include the actual round when the task template supports it
|
|
151
157
|
- Before appending the workflow Activity Log entry, re-estimate `priority` based on the analysis findings (business impact, risks, dependencies, blockers). If the re-estimated value differs from the current value in `task.md`:
|
|
152
158
|
- Overwrite the `priority` field in frontmatter with the new value
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analysis Re-estimate** by {agent} — priority {old} → {new} (rationale: {short basis grounded in this analysis})
|
|
156
|
-
```
|
|
157
|
-
Both entries may share the same timestamp; ordering is conveyed by list position only.
|
|
158
|
-
If the re-estimated value matches the current value, skip the Re-estimate entry. The Flow A sync that follows reads the possibly updated frontmatter and propagates the new value to the Issue automatically.
|
|
159
|
+
- Append a `## Priority Re-estimate` section to this round's analysis artifact `{analysis-artifact}`, recording: `priority {old} → {new} (rationale: {short basis grounded in this analysis})`
|
|
160
|
+
If the re-estimated value matches the current value, skip it: do not write the `## Priority Re-estimate` section. The Flow A sync that follows reads the possibly updated frontmatter and propagates the new value to the Issue automatically.
|
|
159
161
|
- **Append** to `## Activity Log` (do NOT overwrite previous entries):
|
|
160
162
|
```
|
|
161
|
-
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **
|
|
163
|
+
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analyze Task (Round {N})** by {agent} — Analysis completed → {analysis-artifact}
|
|
162
164
|
```
|
|
163
165
|
|
|
164
166
|
If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
|
|
@@ -187,7 +189,7 @@ Keep the gate output in your reply as fresh evidence. Do not claim completion wi
|
|
|
187
189
|
|
|
188
190
|
> Execute this step only after the verification gate passes.
|
|
189
191
|
|
|
190
|
-
> **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent. If `.agents/.airc.json` configures custom TUIs (via `customTUIs`), read each tool's `name` and `invoke`, then add the matching command line in the same format (`${skillName}` becomes the skill name and `${projectName}` becomes the project name).
|
|
192
|
+
> **IMPORTANT**: All TUI command formats listed below must be output in full. Do not show only the format for the current AI agent. If `.agents/.airc.json` configures custom TUIs (via `customTUIs`), read each tool's `name` and `invoke`, then add the matching command line in the same format (`${skillName}` becomes the skill name and `${projectName}` becomes the project name). Before rendering the "Next steps" commands, read `.agents/rules/next-step-output.md` and use its short-id snippet to render `{task-ref}` in the commands as the short id `#NN` (falling back to the full TASK-id when unallocated or released).
|
|
191
193
|
|
|
192
194
|
Output format:
|
|
193
195
|
```
|
|
@@ -202,9 +204,9 @@ Output file:
|
|
|
202
204
|
- Analysis report: .agents/workspace/active/{task-id}/{analysis-artifact}
|
|
203
205
|
|
|
204
206
|
Next step - review the analysis:
|
|
205
|
-
- Claude Code / OpenCode: /review-analysis {task-
|
|
206
|
-
- Gemini CLI: /{{project}}:review-analysis {task-
|
|
207
|
-
- Codex CLI: $review-analysis {task-
|
|
207
|
+
- Claude Code / OpenCode: /review-analysis {task-ref}
|
|
208
|
+
- Gemini CLI: /{{project}}:review-analysis {task-ref}
|
|
209
|
+
- Codex CLI: $review-analysis {task-ref}
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
## Completion Checklist
|
|
@@ -27,8 +27,11 @@ tail .agents/workspace/active/{task-id}/task.md
|
|
|
27
27
|
|
|
28
28
|
状态核对完成前,禁止任何关于外部状态的断言(例如“代码没变”“测试已通过”“没有其他引用”),包括思考阶段。本门禁只提供结构下限;逐条证据配对和真实性仍需按报告模板与审查要求核对。
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## 任务入参短号别名
|
|
31
|
+
|
|
32
|
+
> 如果 `{task-id}` 入参匹配 `^[#]?[0-9]+$`(裸数字或带 `#` 前缀),先读取 `.agents/rules/task-short-id.md` 的「SKILL 入参解析」段执行解析;后续命令视 `{task-id}` 为解析后的全长 `TASK-YYYYMMDD-HHMMSS` 形式。
|
|
31
33
|
|
|
34
|
+
## 执行步骤
|
|
32
35
|
### 1. 验证前置条件
|
|
33
36
|
|
|
34
37
|
检查必要文件:
|
|
@@ -61,6 +64,8 @@ tail .agents/workspace/active/{task-id}/task.md
|
|
|
61
64
|
- `codescan_alert_number` - Code Scanning 告警
|
|
62
65
|
- `security_alert_number` - Dependabot 告警
|
|
63
66
|
|
|
67
|
+
**Round ≥ 2:响应上一轮审查(仅当存在审查产物时)**:若任务目录存在 `review-analysis.md` / `review-analysis-r{N}.md`,读取最高轮次的审查报告;在本轮分析产物中新增 `## 对上一轮审查的响应` 段,对每条发现先 Read/Grep 核实再处置(成立 → 接受并修正;判定为幻觉/不成立 → 附反证反驳,不默认顺从),未决分歧写入 `## 未决问题`。Round 1 无审查,跳过本段。
|
|
68
|
+
|
|
64
69
|
### 4. 执行需求分析
|
|
65
70
|
|
|
66
71
|
开始分析前:若 frontmatter 的 `start_date` 为空,立即写入当日日期(命令 `date +%F`,格式 `YYYY-MM-DD`);已有值则保留。写入前先读取 `.agents/rules/version-stamp.md`,并同步刷新 `updated_at` / `agent_infra_version`。
|
|
@@ -150,15 +155,11 @@ date "+%Y-%m-%d %H:%M:%S%:z"
|
|
|
150
155
|
- 在工作流进度中标记 requirement-analysis 为已完成,并注明实际轮次(如果任务模板支持)
|
|
151
156
|
- 在追加工作流 Activity Log 条目之前,基于分析结果(业务影响、风险、依赖、阻塞条件)重估 `priority`。若重估值与 `task.md` 当前值不一致:
|
|
152
157
|
- 用新值覆盖 frontmatter 的 `priority` 字段
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analysis Re-estimate** by {agent} — priority {old} → {new} (rationale: {基于本轮分析的简短依据})
|
|
156
|
-
```
|
|
157
|
-
两条条目可共用同一时间戳,顺序仅通过列表位置表达。
|
|
158
|
-
若重估值与当前值一致,跳过 Re-estimate 条目。后续 Flow A 同步会读取可能更新过的 frontmatter,并自动把新值同步到 Issue。
|
|
158
|
+
- 在本轮分析产物 `{analysis-artifact}` 中追加 `## 优先级重估` 段,记录一条:`priority {old} → {new} (rationale: {基于本轮分析的简短依据})`
|
|
159
|
+
若重估值与当前值一致,跳过:不写入 `## 优先级重估` 段。后续 Flow A 同步会读取可能更新过的 frontmatter,并自动把新值同步到 Issue。
|
|
159
160
|
- **追加**到 `## Activity Log`(不要覆盖之前的记录):
|
|
160
161
|
```
|
|
161
|
-
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **
|
|
162
|
+
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Analyze Task (Round {N})** by {agent} — Analysis completed → {analysis-artifact}
|
|
162
163
|
```
|
|
163
164
|
|
|
164
165
|
如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续):
|
|
@@ -187,7 +188,7 @@ node .agents/scripts/validate-artifact.js gate analyze-task .agents/workspace/ac
|
|
|
187
188
|
|
|
188
189
|
> 仅在校验通过后执行本步骤。
|
|
189
190
|
|
|
190
|
-
> **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。如果 `.agents/.airc.json` 中配置了自定义 TUI(`customTUIs`),读取每个工具的 `name` 和 `invoke`,按同样格式补充对应命令行(`${skillName}` 替换为技能名,`${projectName}` 替换为项目名)。
|
|
191
|
+
> **重要**:以下「下一步」中列出的所有 TUI 命令格式必须完整输出,不要只展示当前 AI 代理对应的格式。如果 `.agents/.airc.json` 中配置了自定义 TUI(`customTUIs`),读取每个工具的 `name` 和 `invoke`,按同样格式补充对应命令行(`${skillName}` 替换为技能名,`${projectName}` 替换为项目名)。 渲染「下一步」命令前,先读取 `.agents/rules/next-step-output.md`,按其取短号片段把命令中的 `{task-ref}` 渲染为短号 `#NN`(未分配/已释放时回退完整 TASK-id)。
|
|
191
192
|
|
|
192
193
|
输出格式:
|
|
193
194
|
```
|
|
@@ -202,9 +203,9 @@ node .agents/scripts/validate-artifact.js gate analyze-task .agents/workspace/ac
|
|
|
202
203
|
- 分析报告:.agents/workspace/active/{task-id}/{analysis-artifact}
|
|
203
204
|
|
|
204
205
|
下一步 - 审查需求分析:
|
|
205
|
-
- Claude Code / OpenCode:/review-analysis {task-
|
|
206
|
-
- Gemini CLI:/agent-infra:review-analysis {task-
|
|
207
|
-
- Codex CLI:$review-analysis {task-
|
|
206
|
+
- Claude Code / OpenCode:/review-analysis {task-ref}
|
|
207
|
+
- Gemini CLI:/agent-infra:review-analysis {task-ref}
|
|
208
|
+
- Codex CLI:$review-analysis {task-ref}
|
|
208
209
|
```
|
|
209
210
|
|
|
210
211
|
## 完成检查清单
|