@fitlab-ai/agent-infra 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +6 -1
  2. package/README.zh-CN.md +5 -1
  3. package/dist/bin/internal-cli.js +5 -0
  4. package/dist/lib/internal/platform-release-notes.js +53 -0
  5. package/dist/lib/internal/release-workflow.js +105 -2
  6. package/dist/lib/platform/github-release-notes.js +96 -0
  7. package/dist/lib/platform/issues.js +4 -2
  8. package/dist/lib/platform/merged-pr-equivalence.js +54 -0
  9. package/dist/lib/platform/pr-checks.js +6 -6
  10. package/dist/lib/platform/pull-requests.js +3 -1
  11. package/dist/lib/platform/release-notes.js +143 -0
  12. package/dist/lib/platform/verification-required.js +29 -5
  13. package/dist/lib/sandbox/build-proxy.js +63 -0
  14. package/dist/lib/sandbox/commands/create.js +57 -9
  15. package/dist/lib/sandbox/commands/rebuild.js +24 -7
  16. package/dist/lib/sandbox/image-build.js +2 -1
  17. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  18. package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
  19. package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
  20. package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
  21. package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
  22. package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
  23. package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
  24. package/dist/lib/sandbox/shell.js +2 -1
  25. package/dist/lib/task/commands/issue-body.js +10 -5
  26. package/dist/lib/task/events.js +1 -1
  27. package/dist/lib/task/issue-form.js +2 -0
  28. package/dist/lib/task/review-fingerprint.js +9 -0
  29. package/dist/lib/task/verification-engine.js +34 -17
  30. package/dist/lib/task/verification-review.js +1 -1
  31. package/dist/lib/task/verification.js +1 -1
  32. package/lib/internal/platform-release-notes.ts +55 -0
  33. package/lib/internal/release-workflow.ts +105 -3
  34. package/lib/platform/github-release-notes.ts +132 -0
  35. package/lib/platform/issues.ts +6 -4
  36. package/lib/platform/merged-pr-equivalence.ts +70 -0
  37. package/lib/platform/pr-checks.ts +8 -7
  38. package/lib/platform/pull-requests.ts +9 -3
  39. package/lib/platform/release-notes.ts +167 -0
  40. package/lib/platform/verification-required.ts +31 -5
  41. package/lib/sandbox/build-proxy.ts +94 -0
  42. package/lib/sandbox/commands/create.ts +77 -11
  43. package/lib/sandbox/commands/rebuild.ts +29 -7
  44. package/lib/sandbox/image-build.ts +4 -1
  45. package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
  46. package/lib/sandbox/runtimes/base.dockerfile +8 -1
  47. package/lib/sandbox/runtimes/java17.dockerfile +8 -1
  48. package/lib/sandbox/runtimes/java21.dockerfile +8 -1
  49. package/lib/sandbox/runtimes/node20.dockerfile +8 -1
  50. package/lib/sandbox/runtimes/node22.dockerfile +8 -1
  51. package/lib/sandbox/runtimes/python3.dockerfile +8 -1
  52. package/lib/sandbox/shell.ts +3 -1
  53. package/lib/task/commands/issue-body.ts +10 -5
  54. package/lib/task/events.ts +1 -1
  55. package/lib/task/issue-form.ts +4 -0
  56. package/lib/task/review-fingerprint.ts +10 -1
  57. package/lib/task/verification-engine.ts +33 -17
  58. package/lib/task/verification-review.ts +1 -0
  59. package/lib/task/verification.ts +1 -1
  60. package/package.json +1 -1
  61. package/templates/.agents/rules/release-commands.en.md +2 -2
  62. package/templates/.agents/rules/release-commands.github.en.md +10 -40
  63. package/templates/.agents/rules/release-commands.github.zh-CN.md +9 -39
  64. package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
  65. package/templates/.agents/rules/review-handshake.en.md +2 -1
  66. package/templates/.agents/rules/review-handshake.zh-CN.md +2 -1
  67. package/templates/.agents/rules/testing-discipline.en.md +20 -2
  68. package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
  69. package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -1
  70. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -1
  71. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  72. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  73. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  74. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  75. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
  76. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
  77. package/templates/.agents/skills/create-task/SKILL.en.md +4 -0
  78. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -0
  79. package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
  80. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
  81. package/templates/.agents/templates/task.en.md +18 -0
  82. package/templates/.agents/templates/task.zh-CN.md +18 -0
@@ -5,20 +5,41 @@ ENV NPM_CONFIG_PREFIX=/home/devuser/.npm-global
5
5
  ENV PATH="/home/devuser/.npm-global/bin:${PATH}"
6
6
 
7
7
  ARG AI_TOOL_PACKAGES=
8
- RUN set -e && \
8
+ RUN --mount=type=secret,id=HTTP_PROXY \
9
+ --mount=type=secret,id=HTTPS_PROXY \
10
+ --mount=type=secret,id=NO_PROXY \
11
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
12
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
13
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
14
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
15
+ set -e && \
9
16
  for pkg in ${AI_TOOL_PACKAGES}; do \
10
17
  npm install -g "$pkg"; \
11
18
  done
12
19
 
13
20
  ARG AI_TOOLS_SHELL_INSTALL_B64=
14
- RUN if [ -n "${AI_TOOLS_SHELL_INSTALL_B64}" ]; then \
21
+ RUN --mount=type=secret,id=HTTP_PROXY \
22
+ --mount=type=secret,id=HTTPS_PROXY \
23
+ --mount=type=secret,id=NO_PROXY \
24
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
25
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
26
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
27
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
28
+ if [ -n "${AI_TOOLS_SHELL_INSTALL_B64}" ]; then \
15
29
  set -e && \
16
30
  echo "${AI_TOOLS_SHELL_INSTALL_B64}" | base64 -d > /tmp/ai-tools-install.sh && \
17
31
  bash /tmp/ai-tools-install.sh && \
18
32
  rm /tmp/ai-tools-install.sh; \
19
33
  fi
20
34
 
21
- RUN npm install -g pyright
35
+ RUN --mount=type=secret,id=HTTP_PROXY \
36
+ --mount=type=secret,id=HTTPS_PROXY \
37
+ --mount=type=secret,id=NO_PROXY \
38
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
39
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
40
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
41
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
42
+ npm install -g pyright
22
43
 
23
44
  RUN mkdir -p /home/devuser/.local/share /home/devuser/.local/state
24
45
 
@@ -19,7 +19,14 @@ RUN if [ "${HOST_UID}" = "0" ]; then \
19
19
  useradd -u ${HOST_UID} -g ${HOST_GID} -m -s /bin/bash devuser; \
20
20
  fi
21
21
 
22
- RUN apt-get update && apt-get install -y \
22
+ RUN --mount=type=secret,id=HTTP_PROXY \
23
+ --mount=type=secret,id=HTTPS_PROXY \
24
+ --mount=type=secret,id=NO_PROXY \
25
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
26
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
27
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
28
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
29
+ apt-get update && apt-get install -y \
23
30
  curl wget git vim file jq \
24
31
  build-essential ca-certificates gnupg lsb-release \
25
32
  libevent-core-2.1-7 libncursesw6 libtinfo6 \
@@ -1,3 +1,10 @@
1
- RUN apt-get update && apt-get install -y \
1
+ RUN --mount=type=secret,id=HTTP_PROXY \
2
+ --mount=type=secret,id=HTTPS_PROXY \
3
+ --mount=type=secret,id=NO_PROXY \
4
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
5
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
6
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
7
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
8
+ apt-get update && apt-get install -y \
2
9
  openjdk-17-jdk maven \
3
10
  && rm -rf /var/lib/apt/lists/*
@@ -1,3 +1,10 @@
1
- RUN apt-get update && apt-get install -y \
1
+ RUN --mount=type=secret,id=HTTP_PROXY \
2
+ --mount=type=secret,id=HTTPS_PROXY \
3
+ --mount=type=secret,id=NO_PROXY \
4
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
5
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
6
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
7
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
8
+ apt-get update && apt-get install -y \
2
9
  openjdk-21-jdk maven \
3
10
  && rm -rf /var/lib/apt/lists/*
@@ -1,3 +1,10 @@
1
- RUN bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_20.x | bash -' && \
1
+ RUN --mount=type=secret,id=HTTP_PROXY \
2
+ --mount=type=secret,id=HTTPS_PROXY \
3
+ --mount=type=secret,id=NO_PROXY \
4
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
5
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
6
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
7
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
8
+ bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_20.x | bash -' && \
2
9
  apt-get install -y nodejs && \
3
10
  rm -rf /var/lib/apt/lists/*
@@ -1,3 +1,10 @@
1
- RUN bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_22.x | bash -' && \
1
+ RUN --mount=type=secret,id=HTTP_PROXY \
2
+ --mount=type=secret,id=HTTPS_PROXY \
3
+ --mount=type=secret,id=NO_PROXY \
4
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
5
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
6
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
7
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
8
+ bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_22.x | bash -' && \
2
9
  apt-get install -y nodejs && \
3
10
  rm -rf /var/lib/apt/lists/*
@@ -1,3 +1,10 @@
1
- RUN apt-get update && apt-get install -y \
1
+ RUN --mount=type=secret,id=HTTP_PROXY \
2
+ --mount=type=secret,id=HTTPS_PROXY \
3
+ --mount=type=secret,id=NO_PROXY \
4
+ export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
5
+ export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
6
+ export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
7
+ export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
8
+ apt-get update && apt-get install -y \
2
9
  python3 python3-pip python3-venv \
3
10
  && rm -rf /var/lib/apt/lists/*
@@ -12,6 +12,7 @@ type CommandOptions = {
12
12
  stdio?: StdioOptions;
13
13
  timeout?: number;
14
14
  shell?: boolean;
15
+ env?: NodeJS.ProcessEnv;
15
16
  };
16
17
 
17
18
  type RunProbeOptions = CommandOptions & {
@@ -23,7 +24,8 @@ function normalizeOptions(opts: CommandOptions = {}, stdio: StdioOptions): Comma
23
24
  cwd: opts.cwd,
24
25
  encoding: opts.encoding,
25
26
  stdio,
26
- timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS
27
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS,
28
+ env: opts.env
27
29
  };
28
30
  }
29
31
 
@@ -12,12 +12,13 @@ Print a deterministic Issue body extracted from a task's task.md.
12
12
  Omit <ref> Resolve the unique active task for the current branch.
13
13
  <ref> Bare numeric short id, or a full TASK-YYYYMMDD-HHMMSS id.
14
14
  --template <path> Render the final body for the given GitHub Issue Form (scenario A);
15
- without it, print the default '描述 + 需求' body (scenario B).
15
+ without it, print the default '任务输入 + 描述 + 需求' body (scenario B).
16
16
 
17
- Only the task title, '## 描述' and '## 需求' sections are ever emitted; the rest of
18
- task.md (scaffolding sections, placeholders) is never written to the body.
17
+ Only the task title, '## 任务输入', '## 描述' and '## 需求' sections are ever
18
+ emitted; the rest of task.md is never written to the body.
19
19
  `;
20
20
 
21
+ const TASK_INPUT_ALIASES = ['任务输入', 'Task Input'];
21
22
  const DESCRIPTION_ALIASES = ['描述', 'Description'];
22
23
  const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
23
24
 
@@ -26,18 +27,22 @@ const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
26
27
  * task.md actually uses, with empty sections falling back to `N/A`.
27
28
  */
28
29
  function buildDefaultBody(content: string): string {
30
+ const taskInputHeading = findSectionHeading(content, TASK_INPUT_ALIASES);
29
31
  const descHeading = findSectionHeading(content, DESCRIPTION_ALIASES);
30
32
  const reqHeading = findSectionHeading(content, REQUIREMENTS_ALIASES);
33
+ const taskInput = extractSection(content, TASK_INPUT_ALIASES) || PLACEHOLDER;
31
34
  const description = extractSection(content, DESCRIPTION_ALIASES) || PLACEHOLDER;
32
35
  const requirements = extractSection(content, REQUIREMENTS_ALIASES) || PLACEHOLDER;
33
- return `## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
36
+ return `## ${taskInputHeading}\n\n${taskInput}\n\n## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
34
37
  }
35
38
 
36
39
  function readTaskFields(content: string): TaskFields {
37
40
  return {
38
41
  title: extractTitle(content),
39
42
  description: extractSection(content, DESCRIPTION_ALIASES),
40
- requirements: extractSection(content, REQUIREMENTS_ALIASES)
43
+ requirements: extractSection(content, REQUIREMENTS_ALIASES),
44
+ taskInput: extractSection(content, TASK_INPUT_ALIASES),
45
+ taskInputHeading: findSectionHeading(content, TASK_INPUT_ALIASES)
41
46
  };
42
47
  }
43
48
 
@@ -109,7 +109,7 @@ const FAMILY = {
109
109
  'review-plan': { artifact: 'review-plan', started: ['technical-design', 'technical-design-review'], completed: ['technical-design', 'technical-design-review'], target: 'technical-design-review', label: 'Review Plan' },
110
110
  code: { artifact: 'code', started: ['technical-design-review', 'code-review'], completed: ['technical-design-review', 'code-review'], target: 'code', label: 'Code Task' },
111
111
  'review-code': { artifact: 'review-code', started: ['code', 'code-review', 'commit'], completed: ['code', 'code-review', 'commit'], target: 'code-review', label: 'Review Code' },
112
- 'manual-validation': { artifact: 'manual-validation', started: ['code-review'], completed: ['code-review'], target: null, label: 'Complete Manual Validation' }
112
+ 'manual-validation': { artifact: 'manual-validation', started: ['code-review', 'commit'], completed: ['code-review', 'commit'], target: null, label: 'Complete Manual Validation' }
113
113
  } as const;
114
114
  type EventFamily = keyof typeof FAMILY;
115
115
 
@@ -4,6 +4,8 @@ type TaskFields = {
4
4
  title: string;
5
5
  description: string;
6
6
  requirements: string;
7
+ taskInput: string;
8
+ taskInputHeading: string;
7
9
  };
8
10
 
9
11
  // Field id -> task value mapping (single source of truth, per HD-1).
@@ -84,6 +86,8 @@ function renderTemplateBody(formText: string, fields: TaskFields): string {
84
86
  const value = mapped.trim() === '' ? PLACEHOLDER : mapped;
85
87
  sections.push(`### ${label}\n\n${value}`);
86
88
  }
89
+ const taskInput = fields.taskInput.trim() === '' ? PLACEHOLDER : fields.taskInput;
90
+ sections.push(`### ${fields.taskInputHeading}\n\n${taskInput}`);
87
91
  return `${sections.join('\n\n')}\n`;
88
92
  }
89
93
 
@@ -12,7 +12,16 @@ export const DEFAULT_POST_REVIEW_EXCLUDES: string[] = [];
12
12
  // The leading ":/" makes coverage fail-closed (everything from the repo root);
13
13
  // each exclude becomes a top-level negative pathspec. Projects extend the
14
14
  // denylist via review.post_review_exclude_globs (union, deduped).
15
- type ReviewGlobConfig = { post_review_exclude_globs?: unknown };
15
+ type ReviewGlobConfig = Record<string, unknown> & { post_review_exclude_globs?: unknown };
16
+
17
+ export function loadPostReviewConfig(repoRoot: string): ReviewGlobConfig {
18
+ try {
19
+ const config = JSON.parse(fs.readFileSync(path.join(repoRoot, ".agents", ".airc.json"), "utf8"));
20
+ return config.review && typeof config.review === "object" ? config.review : {};
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
16
25
 
17
26
  export function resolvePostReviewGlobs(
18
27
  config: ReviewGlobConfig = {},
@@ -10,11 +10,14 @@ import {
10
10
  extractReviewDiffFingerprint,
11
11
  extractReviewedSnapshotTree,
12
12
  findAuthoritativeReviewCodeArtifact,
13
+ loadPostReviewConfig,
13
14
  parseReviewVerdict,
14
15
  resolvePostReviewGlobs
15
16
  } from "./verification-review.ts";
16
17
  import { check as checkPlatformSync } from "../platform/verification-sync.ts";
17
18
  import { check as checkRequiredChecks } from "../platform/verification-required.ts";
19
+ import { inspectPlatformPullRequest } from "../platform/pull-requests.ts";
20
+ import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
18
21
  import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
19
22
  import { parseLedger } from "./ledger.ts";
20
23
  import { loadVerificationConfig } from "./verification-config.ts";
@@ -268,20 +271,6 @@ function loadProjectName(): any {
268
271
  }
269
272
  }
270
273
 
271
- function loadReviewConfig(): any {
272
- const configPath = path.join(repoRoot, ".agents", ".airc.json");
273
- if (!fs.existsSync(configPath)) {
274
- return {};
275
- }
276
-
277
- try {
278
- const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
279
- return config.review && typeof config.review === "object" ? config.review : {};
280
- } catch {
281
- return {};
282
- }
283
- }
284
-
285
274
  function checkArtifact({ taskDir, config, artifactFile }: any): any {
286
275
  const resolvedArtifact = resolveArtifactPath(taskDir, config.file_pattern, artifactFile);
287
276
  if (!resolvedArtifact.ok) {
@@ -598,7 +587,7 @@ function resolveReviewSetting(config: any, key: any, fallback: any): any {
598
587
  if (config && config[key] !== undefined && config[key] !== null) {
599
588
  return config[key];
600
589
  }
601
- const reviewConfig = loadReviewConfig();
590
+ const reviewConfig = loadPostReviewConfig(repoRoot);
602
591
  if (reviewConfig[key] !== undefined && reviewConfig[key] !== null) {
603
592
  return reviewConfig[key];
604
593
  }
@@ -706,7 +695,7 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
706
695
  }
707
696
 
708
697
  const sha = baselineSource.sha;
709
- const globs = resolvePostReviewGlobs(config, loadReviewConfig());
698
+ const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
710
699
  let commits;
711
700
  try {
712
701
  const out = execFileSync("git", ["-C", gitRoot, "rev-list", `${sha}..HEAD`, "--", ...globs], { encoding: "utf8" });
@@ -719,6 +708,33 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
719
708
  return passResult("post-review-commit", `No post-review commits to code/rule paths since ${sha.slice(0, 8)}`);
720
709
  }
721
710
 
711
+ if (task.ok && Number(task.metadata.pr_number) > 0) {
712
+ const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
713
+ if (inspected.pullRequest) {
714
+ let localHead = "";
715
+ try {
716
+ localHead = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
717
+ } catch {
718
+ return blockedResult("post-review-commit", "Unable to resolve the local HEAD");
719
+ }
720
+ const relation = resolveReviewedHeadRelation({
721
+ gitRoot,
722
+ localHead,
723
+ lastReviewedCommit: sha,
724
+ pullRequest: inspected.pullRequest,
725
+ pathspecs: globs
726
+ });
727
+ if (relation.status === "merged-equivalent") {
728
+ return passResult("post-review-commit", `Reviewed PR head is content-equivalent to squash merge ${relation.mergeCommit.slice(0, 8)}`);
729
+ }
730
+ if (relation.status === "blocked") {
731
+ return blockedResult("post-review-commit", relation.message);
732
+ }
733
+ } else if (inspected.status === "blocked") {
734
+ return blockedResult("post-review-commit", inspected.error?.message || "PR merge snapshot is unavailable");
735
+ }
736
+ }
737
+
722
738
  let exempt = false;
723
739
  try {
724
740
  exempt = task.ok && parseLedger(task.content).some(
@@ -788,7 +804,7 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
788
804
 
789
805
  let actualSnapshot;
790
806
  try {
791
- actualSnapshot = snapshotReview({ cwd: gitRoot, mode: "worktree", baseline, globs: resolvePostReviewGlobs({}, loadReviewConfig()) });
807
+ actualSnapshot = snapshotReview({ cwd: gitRoot, mode: "worktree", baseline, globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot)) });
792
808
  } catch {
793
809
  return blockedResult(
794
810
  "review-fact",
@@ -3,6 +3,7 @@ export {
3
3
  extractReviewDiffFingerprint,
4
4
  extractReviewedSnapshotTree,
5
5
  findAuthoritativeReviewCodeArtifact,
6
+ loadPostReviewConfig,
6
7
  parseReviewVerdict,
7
8
  resolvePostReviewGlobs
8
9
  } from './review-fingerprint.ts';
@@ -63,7 +63,7 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
63
63
  'block-task.completed': gate('block-task', 'blocked'),
64
64
  'cancel-task.completed': gate('cancel-task', 'completed'),
65
65
  'commit.completed': gate('commit', 'active'),
66
- 'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'post-review-commit', 'required-checks', 'platform-sync-preflight'] },
66
+ 'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'post-review-commit', 'platform-sync-preflight'] },
67
67
  'complete-task.completed': gate('complete-task', 'completed'),
68
68
  'create-pr.completed': gate('create-pr', 'active'),
69
69
  'create-task.completed': gate('create-task', 'active'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Gemini CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  # Release Commands
2
2
 
3
- This code platform does not provide built-in release commands.
3
+ This code platform does not provide a release-note adapter.
4
4
 
5
- Remote release automation is skipped for custom platforms unless you provide matching `.{platform}.en.md` rule templates and scripts. Continue using local release artifacts or your platform-specific release process.
5
+ Use `agent-infra-internal platform-release-notes context` to receive a structured `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op. Do not probe another platform client. Publishing remains a no-op until an adapter is provided.
@@ -1,52 +1,22 @@
1
1
  # Release Platform Commands
2
2
 
3
- Read this file before loading release history, querying merged PRs, or publishing the Release notes.
3
+ Release-note platform operations use a typed internal intent. Callers consume structured JSON and do not interpret platform commands, raw fields, identity email rules, or authentication errors.
4
4
 
5
- ## Query Releases
5
+ ## Collect Release-note Context
6
6
 
7
7
  ```bash
8
- gh release list --limit {limit} --json tagName,isDraft,isPrerelease
9
- gh release view "{tag}" --json body,url
8
+ agent-infra-internal platform-release-notes context \
9
+ --from-tag "v{prev-version}" --to-tag "v{version}" \
10
+ --branch "{branch}" --history-limit 3
10
11
  ```
11
12
 
12
- ## Query Merged PRs
13
+ The result contains release history, pull requests, closing Issues, and normalized contributor identities. Unsupported platforms return a stable `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op.
13
14
 
14
- ```bash
15
- gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels
16
- ```
17
-
18
- When needed, read the linked Issue:
19
-
20
- ```bash
21
- gh issue view {issue-number} --json number,title,labels,url
22
- ```
23
-
24
- ## Contributor Mapping Helpers
25
-
26
- Merged PR queries used for release notes should include authors when contributors are needed:
27
-
28
- ```bash
29
- gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels,author
30
- ```
31
-
32
- Linked Issue queries used for reporter attribution should include the author:
33
-
34
- ```bash
35
- gh issue view {issue-number} --json number,title,labels,url,author
36
- ```
37
-
38
- Map GitHub no-reply emails with this rule: if `Name <email>` contains an email matching `(\d+\+)?(\S+?)@users\.noreply\.github\.com`, use the second capture group lowercased as the login. This covers both `{id}+{login}@users.noreply.github.com` and `{login}@users.noreply.github.com`.
39
-
40
- ## Publish the Release Notes
41
-
42
- The GitHub Release for `v{version}` is created and published automatically by the release workflow so Homebrew bottles have a stable upload target. This command writes the curated notes onto that existing Release, falling back to creating it if it does not exist yet.
15
+ ## Publish Release Notes
43
16
 
44
17
  ```bash
45
- if gh release view "v{version}" >/dev/null 2>&1; then
46
- gh release edit "v{version}" --notes-file "{notes-file}"
47
- else
48
- gh release create "v{version}" --title "v{version}" --notes-file "{notes-file}"
49
- fi
18
+ agent-infra-internal platform-release-notes publish \
19
+ --tag "v{version}" --title "v{version}" --notes-file "{notes-file}"
50
20
  ```
51
21
 
52
- If commands fail, stop or escalate according to the calling skill.
22
+ The command updates an existing Release or creates a missing one. `--dry-run` only plans the operation. Exit codes `0/1/2` mean success, failure, and blocked.
@@ -1,52 +1,22 @@
1
1
  # Release 平台命令
2
2
 
3
- 在读取历史 release、查询已合并 PR,或发布 Release notes 前先读取本文件。
3
+ 发布说明平台操作统一使用 typed internal intent。调用方只解释结构化 JSON,不解析平台命令、原始字段、身份邮箱规则或认证错误。
4
4
 
5
- ## Release 查询
5
+ ## 收集发布说明上下文
6
6
 
7
7
  ```bash
8
- gh release list --limit {limit} --json tagName,isDraft,isPrerelease
9
- gh release view "{tag}" --json body,url
8
+ agent-infra-internal platform-release-notes context \
9
+ --from-tag "v{prev-version}" --to-tag "v{version}" \
10
+ --branch "{branch}" --history-limit 3
10
11
  ```
11
12
 
12
- ## 已合并 PR 查询
13
-
14
- ```bash
15
- gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels
16
- ```
17
-
18
- 必要时读取关联 Issue:
19
-
20
- ```bash
21
- gh issue view {issue-number} --json number,title,labels,url
22
- ```
23
-
24
- ## Contributor 映射辅助规则
25
-
26
- release notes 需要 contributors 时,已合并 PR 查询应包含 author:
27
-
28
- ```bash
29
- gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels,author
30
- ```
31
-
32
- 关联 Issue 用于 reporter 归因时,查询应包含 author:
33
-
34
- ```bash
35
- gh issue view {issue-number} --json number,title,labels,url,author
36
- ```
37
-
38
- GitHub no-reply 邮箱映射规则:如果 `Name <email>` 中的 email 匹配 `(\d+\+)?(\S+?)@users\.noreply\.github\.com`,使用第二个捕获组的小写形式作为 login。该规则同时覆盖 `{id}+{login}@users.noreply.github.com` 和 `{login}@users.noreply.github.com`。
13
+ 结果包含历史发布、PR、关联 Issue 和规范化贡献者身份。不支持的平台返回稳定的 `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op。
39
14
 
40
15
  ## 发布 Release notes
41
16
 
42
- `v{version}` 的 GitHub Release 由 release 工作流自动创建并发布,为 Homebrew bottle 提供稳定的上传落点。本命令把精修后的 notes 写到这个已存在的 Release 上;若 Release 尚不存在则兜底创建。
43
-
44
17
  ```bash
45
- if gh release view "v{version}" >/dev/null 2>&1; then
46
- gh release edit "v{version}" --notes-file "{notes-file}"
47
- else
48
- gh release create "v{version}" --title "v{version}" --notes-file "{notes-file}"
49
- fi
18
+ agent-infra-internal platform-release-notes publish \
19
+ --tag "v{version}" --title "v{version}" --notes-file "{notes-file}"
50
20
  ```
51
21
 
52
- 失败时按调用方规则停止或提示人工介入。
22
+ 命令更新既有 Release,缺失时创建;`--dry-run` 只规划操作。退出码 `0/1/2` 分别表示成功、失败和阻塞。
@@ -1,5 +1,5 @@
1
1
  # Release 命令
2
2
 
3
- 当前代码平台未内置发布命令支持。
3
+ 当前代码平台未提供发布说明 adapter。
4
4
 
5
- 自定义平台会跳过远端发布自动化,除非你提供匹配的 `.{platform}.zh-CN.md` 规则模板和脚本。请继续使用本地发布产物或你的平台专属发布流程。
5
+ 调用 `agent-infra-internal platform-release-notes context` 会返回结构化的 `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op,不得探测其他平台 client。在提供 adapter 前,发布操作同样保持 no-op。
@@ -92,7 +92,8 @@ When an executor judges an item to be a key design decision that needs human rul
92
92
  - `commit` reads only the highest-round Approved `review-code` artifact. Before committing it requires `pre_head == R`, complete worktree tree `W == T`, and normalized staged tree `S == T`; any mismatch blocks before `git commit` and reports added, missing, and different paths for both comparisons.
93
93
  - After a successful commit it sets `B=last_reviewed_commit=<new_head>`; B means only a reviewed snapshot anchored to a Git commit.
94
94
  - The `complete-task` `post-review-commit` gate uses only B. When B is absent, malformed, or missing as a Git object, it reports `reviewed snapshot was not anchored` and never falls back to R.
95
- - If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`.
95
+ - If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`. The only automatic exception is a bound merged GitHub PR whose platform snapshot proves B is the original PR head, whose merge commit is in local HEAD history, whose reviewed changes exactly match the normalized patch of a single-parent squash commit, and whose protected paths have no later commits. Missing platform or Git evidence fails closed.
96
+ - Required checks remain a pre-merge responsibility of branch protection / rulesets and the `review-code` / `watch-pr` routes. The post-review-commit gate still applies the strict / squash-equivalent relation above independently.
96
97
  - **Exemption**: append a ledger row `| PRC-1 | post-review-commit | - | - | human-decided | <ruling note> |` recording that a human explicitly allowed those commits without re-review.
97
98
 
98
99
  ## Gate behavior cheat sheet
@@ -92,7 +92,8 @@
92
92
  - `commit` 只读取最高轮 Approved `review-code` 产物;提交前比较 `pre_head == R`、完整工作区树 `W == T`、规范化暂存树 `S == T`,任一失配都在 `git commit` 前阻断并报告两组 added/missing/different 路径。
93
93
  - 成功提交后令 `B=last_reviewed_commit=<new_head>`;`B` 只表示已经落到 Git commit 的审查锚点。
94
94
  - `complete-task` 的 `post-review-commit` gate 只使用 B;B 缺失、畸形或对象不存在时报告 `reviewed snapshot was not anchored`,不得回退 R。
95
- - 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。
95
+ - 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。唯一自动例外是绑定 GitHub PR 已合并,且平台快照证明 B 是原 PR head、merge commit 位于本地 HEAD 历史中、受审查变更与单父 squash commit 的规范化补丁完全一致,并且 merge 后受保护路径没有新提交;缺少任一平台或 Git 证据时 fail closed。
96
+ - required checks 由合并前的分支保护 / ruleset 与 `review-code` / `watch-pr` 路由负责。post-review-commit 仍独立执行上述严格 / squash 等价判定。
96
97
  - **豁免**:在账本追加一行 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`,记录人工明确允许该批提交免复审。
97
98
 
98
99
  ## gate 行为速查
@@ -41,6 +41,24 @@ This mirrors "Goal-Driven Execution" in AGENTS.md: define a verifiable success c
41
41
 
42
42
  ## Project-specific test policy
43
43
 
44
- Keep repository-specific commands, directory conventions, coverage thresholds, CI integration, and reporting services in the project's own test documentation. Before adding a test, inspect that local policy and place the test in the layer that matches its observable scope and runtime cost.
44
+ Complete each item below for this repository. Record the chosen behavior and its boundaries so contributors can select and validate tests consistently.
45
45
 
46
- If the project does not define a layered test suite, use its complete test command for RED and GREEN verification. Do not invent a test hierarchy or coverage gate as part of an unrelated change.
46
+ ### 1. Test commands
47
+
48
+ > TODO: Document the complete test command and any faster commands used during development.
49
+
50
+ ### 2. Test directories or tiers
51
+
52
+ > TODO: Document the test directories or tiers and the rule for choosing among them.
53
+
54
+ ### 3. Coverage
55
+
56
+ > TODO: Document how coverage is collected, any thresholds, and whether coverage blocks changes.
57
+
58
+ ### 4. Continuous integration
59
+
60
+ > TODO: Document which CI checks run tests and which checks are blocking or informational.
61
+
62
+ ### 5. Reporting services
63
+
64
+ > TODO: Document any external reporting services, badges, or artifacts and whether they affect acceptance.
@@ -41,6 +41,24 @@ assert.match(content, /^name: code-task$/m); // 正向断言已足够
41
41
 
42
42
  ## 项目级测试策略
43
43
 
44
- 仓库特定的命令、目录约定、覆盖率阈值、CI 集成和报告服务应记录在项目自己的测试文档中。新增测试前先读取该策略,并按测试的可观察范围与运行成本选择对应层级。
44
+ 请为本仓库填写以下各项,记录选定行为及其边界,以便贡献者一致地选择并验证测试。
45
45
 
46
- 如果项目没有定义分层测试套件,RED 与 GREEN 验证都使用项目的完整测试命令。不要在无关改动中自行引入测试层级或覆盖率门禁。
46
+ ### 1. 测试命令
47
+
48
+ > TODO: 记录完整测试命令,以及开发期间使用的可选快速命令。
49
+
50
+ ### 2. 测试目录或层级
51
+
52
+ > TODO: 记录测试目录或层级,以及选择对应目录或层级的规则。
53
+
54
+ ### 3. 覆盖率
55
+
56
+ > TODO: 记录覆盖率采集方式、阈值,以及覆盖率是否阻塞变更。
57
+
58
+ ### 4. 持续集成
59
+
60
+ > TODO: 记录运行测试的 CI 检查,以及哪些检查属于阻塞项或信息项。
61
+
62
+ ### 5. 报告服务
63
+
64
+ > TODO: 记录外部报告服务、徽章或构件,以及它们是否影响验收。
@@ -11,7 +11,7 @@ description: >
11
11
  ## Boundary / Critical Rules
12
12
 
13
13
  - This skill only outputs a requirements analysis document (`analysis.md` or `analysis-r{N}.md`) and does not modify any business code
14
- - Base the analysis strictly on the existing requirements, context, and source information in `task.md`
14
+ - Base the analysis strictly on the existing task input, requirements, context, and source information in `task.md`
15
15
  - After executing this skill, you **must** immediately update task status in task.md
16
16
 
17
17
  Version stamp rule: when creating or updating `task.md` frontmatter, read `.agents/rules/version-stamp.md` first and write or refresh `agent_infra_version`.
@@ -56,6 +56,7 @@ Run `agent-infra-internal task-artifact {task-id} inspect --family analysis`. Co
56
56
  ### 3. Read Task Context
57
57
 
58
58
  Read `task.md` carefully to understand:
59
+ - sources, facts and evidence, constraints, decision states, acceptance criteria, and open questions captured under `## Task Input` (fall back compatibly when the section is absent)
59
60
  - task title, description, and requirement list
60
61
  - context information (Issue, PR, branch, alert numbers, etc.)
61
62
  - currently known affected files and constraints
@@ -89,6 +90,8 @@ Runs after Step 0 state check and Step 3 (questioning is an external-state actio
89
90
  - no answer carried → restate `pending_question` and take Scenario B early-exit below (do not increase `question_count`).
90
91
 
91
92
  **4.3 Sufficiency judgment** (objective checklist; any gap hit means insufficient):
93
+ - first aggregate `## Task Input`, description, context, requirements, and remote sources; an analysis-owned `## Requirements` section being unpopulated is not by itself insufficient;
94
+ - goals, scope, constraints, or acceptance criteria already recorded in task input count as provided and must not be asked again;
92
95
  - description/requirements empty, or a single sentence with no verifiable acceptance criteria;
93
96
  - missing goal or impact scope (unclear what to change / who is affected);
94
97
  - requirement items contradict each other, or key terms are undefined and block analysis.