@fitlab-ai/agent-infra 0.5.0 → 0.5.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/README.md +18 -2
- package/README.zh-CN.md +18 -2
- package/bin/cli.js +1 -1
- package/lib/defaults.json +3 -0
- package/lib/init.js +15 -5
- package/lib/merge.js +442 -22
- package/lib/render.js +77 -15
- package/lib/sandbox/commands/create.js +190 -67
- package/lib/sandbox/commands/enter.js +51 -3
- package/lib/sandbox/commands/ls.js +3 -2
- package/lib/sandbox/commands/rm.js +2 -2
- package/lib/sandbox/config.js +1 -1
- package/lib/sandbox/runtimes/base.dockerfile +11 -1
- package/lib/sandbox/tools.js +9 -5
- package/lib/update.js +19 -5
- package/package.json +2 -1
- package/templates/.agents/{README.md → README.en.md} +13 -3
- package/templates/.agents/README.zh-CN.md +13 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +111 -0
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +111 -0
- package/templates/.agents/rules/label-milestone-setup.github.en.md +50 -0
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +50 -0
- package/templates/.agents/rules/pr-sync.github.en.md +110 -0
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +110 -0
- package/templates/.agents/rules/release-commands.github.en.md +30 -0
- package/templates/.agents/rules/release-commands.github.zh-CN.md +30 -0
- package/templates/.agents/rules/security-alerts.github.en.md +43 -0
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +43 -0
- package/templates/.agents/scripts/validate-artifact.js +77 -3
- package/templates/.agents/skills/cancel-task/{SKILL.md → SKILL.en.md} +7 -6
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/close-codescan/{SKILL.md → SKILL.en.md} +3 -11
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -11
- package/templates/.agents/skills/close-dependabot/{SKILL.md → SKILL.en.md} +5 -13
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +5 -13
- package/templates/.agents/skills/commit/{SKILL.md → SKILL.en.md} +9 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +9 -1
- package/templates/.agents/skills/commit/config/verify.json +5 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +21 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +21 -0
- package/templates/.agents/skills/commit/reference/{task-status-update.md → task-status-update.en.md} +2 -0
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -0
- package/templates/.agents/skills/create-issue/{SKILL.md → SKILL.en.md} +2 -5
- package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +2 -5
- package/templates/.agents/skills/create-issue/reference/{label-and-type.md → label-and-type.en.md} +4 -16
- package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +4 -16
- package/templates/.agents/skills/create-pr/{SKILL.md → SKILL.en.md} +5 -5
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +15 -0
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +6 -73
- package/templates/.agents/skills/create-pr/reference/{pr-body-template.md → pr-body-template.en.md} +8 -13
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +8 -13
- package/templates/.agents/skills/create-release-note/{SKILL.md → SKILL.en.md} +6 -18
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +6 -18
- package/templates/.agents/skills/import-codescan/{SKILL.md → SKILL.en.md} +1 -3
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -3
- package/templates/.agents/skills/import-dependabot/{SKILL.md → SKILL.en.md} +1 -3
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -3
- package/templates/.agents/skills/import-issue/{SKILL.md → SKILL.en.md} +2 -10
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +2 -10
- package/templates/.agents/skills/init-labels/{SKILL.md → SKILL.en.md} +9 -13
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -13
- package/templates/.agents/skills/init-milestones/{SKILL.md → SKILL.en.md} +5 -6
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/refine-title/{SKILL.md → SKILL.en.md} +7 -17
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +6 -16
- package/templates/.agents/skills/release/{SKILL.md → SKILL.en.md} +2 -1
- package/templates/.agents/skills/release/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/restore-task/{SKILL.md → SKILL.en.md} +5 -11
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +5 -11
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +219 -59
- package/templates/.agents/skills/create-pr/reference/comment-publish.md +0 -82
- /package/templates/.agents/{QUICKSTART.md → QUICKSTART.en.md} +0 -0
- /package/templates/.agents/rules/{commit-and-pr.md → commit-and-pr.en.md} +0 -0
- /package/templates/.agents/rules/{issue-sync.md → issue-sync.github.en.md} +0 -0
- /package/templates/.agents/rules/{issue-sync.zh-CN.md → issue-sync.github.zh-CN.md} +0 -0
- /package/templates/.agents/rules/{milestone-inference.md → milestone-inference.github.en.md} +0 -0
- /package/templates/.agents/rules/{milestone-inference.zh-CN.md → milestone-inference.github.zh-CN.md} +0 -0
- /package/templates/.agents/rules/{task-management.md → task-management.en.md} +0 -0
- /package/templates/.agents/skills/analyze-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/archive-tasks/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/block-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/check-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/commit/reference/{commit-message.md → commit-message.en.md} +0 -0
- /package/templates/.agents/skills/commit/reference/{copyright-check.md → copyright-check.en.md} +0 -0
- /package/templates/.agents/skills/complete-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/create-issue/reference/{template-matching.md → template-matching.en.md} +0 -0
- /package/templates/.agents/skills/create-pr/reference/{branch-strategy.md → branch-strategy.en.md} +0 -0
- /package/templates/.agents/skills/create-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/implement-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/implement-task/reference/{branch-management.md → branch-management.en.md} +0 -0
- /package/templates/.agents/skills/implement-task/reference/{implementation-rules.md → implementation-rules.en.md} +0 -0
- /package/templates/.agents/skills/implement-task/reference/{output-template.md → output-template.en.md} +0 -0
- /package/templates/.agents/skills/implement-task/reference/{report-template.md → report-template.en.md} +0 -0
- /package/templates/.agents/skills/init-labels/scripts/{init-labels.sh → init-labels.github.sh} +0 -0
- /package/templates/.agents/skills/init-milestones/scripts/{init-milestones.sh → init-milestones.github.sh} +0 -0
- /package/templates/.agents/skills/plan-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/refine-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/refine-task/reference/{fix-workflow.md → fix-workflow.en.md} +0 -0
- /package/templates/.agents/skills/refine-task/reference/{report-template.md → report-template.en.md} +0 -0
- /package/templates/.agents/skills/release/scripts/{manage-milestones.sh → manage-milestones.github.sh} +0 -0
- /package/templates/.agents/skills/review-task/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/review-task/reference/{output-templates.md → output-templates.en.md} +0 -0
- /package/templates/.agents/skills/review-task/reference/{report-template.md → report-template.en.md} +0 -0
- /package/templates/.agents/skills/review-task/reference/{review-criteria.md → review-criteria.en.md} +0 -0
- /package/templates/.agents/skills/test/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/test-integration/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/update-agent-infra/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/skills/upgrade-dependency/{SKILL.md → SKILL.en.md} +0 -0
- /package/templates/.agents/templates/{handoff.md → handoff.en.md} +0 -0
- /package/templates/.agents/templates/{review-report.md → review-report.en.md} +0 -0
- /package/templates/.agents/templates/{task.md → task.en.md} +0 -0
- /package/templates/.agents/workflows/{bug-fix.yaml → bug-fix.en.yaml} +0 -0
- /package/templates/.agents/workflows/{code-review.yaml → code-review.en.yaml} +0 -0
- /package/templates/.agents/workflows/{feature-development.yaml → feature-development.en.yaml} +0 -0
- /package/templates/.agents/workflows/{refactoring.yaml → refactoring.en.yaml} +0 -0
- /package/templates/.agents/workspace/{README.md → README.en.md} +0 -0
- /package/templates/.claude/commands/{analyze-task.md → analyze-task.en.md} +0 -0
- /package/templates/.claude/commands/{archive-tasks.md → archive-tasks.en.md} +0 -0
- /package/templates/.claude/commands/{block-task.md → block-task.en.md} +0 -0
- /package/templates/.claude/commands/{cancel-task.md → cancel-task.en.md} +0 -0
- /package/templates/.claude/commands/{check-task.md → check-task.en.md} +0 -0
- /package/templates/.claude/commands/{close-codescan.md → close-codescan.en.md} +0 -0
- /package/templates/.claude/commands/{close-dependabot.md → close-dependabot.en.md} +0 -0
- /package/templates/.claude/commands/{commit.md → commit.en.md} +0 -0
- /package/templates/.claude/commands/{complete-task.md → complete-task.en.md} +0 -0
- /package/templates/.claude/commands/{create-issue.md → create-issue.en.md} +0 -0
- /package/templates/.claude/commands/{create-pr.md → create-pr.en.md} +0 -0
- /package/templates/.claude/commands/{create-release-note.md → create-release-note.en.md} +0 -0
- /package/templates/.claude/commands/{create-task.md → create-task.en.md} +0 -0
- /package/templates/.claude/commands/{implement-task.md → implement-task.en.md} +0 -0
- /package/templates/.claude/commands/{import-codescan.md → import-codescan.en.md} +0 -0
- /package/templates/.claude/commands/{import-dependabot.md → import-dependabot.en.md} +0 -0
- /package/templates/.claude/commands/{import-issue.md → import-issue.en.md} +0 -0
- /package/templates/.claude/commands/{init-labels.md → init-labels.en.md} +0 -0
- /package/templates/.claude/commands/{init-milestones.md → init-milestones.en.md} +0 -0
- /package/templates/.claude/commands/{plan-task.md → plan-task.en.md} +0 -0
- /package/templates/.claude/commands/{refine-task.md → refine-task.en.md} +0 -0
- /package/templates/.claude/commands/{refine-title.md → refine-title.en.md} +0 -0
- /package/templates/.claude/commands/{release.md → release.en.md} +0 -0
- /package/templates/.claude/commands/{restore-task.md → restore-task.en.md} +0 -0
- /package/templates/.claude/commands/{review-task.md → review-task.en.md} +0 -0
- /package/templates/.claude/commands/{test-integration.md → test-integration.en.md} +0 -0
- /package/templates/.claude/commands/{test.md → test.en.md} +0 -0
- /package/templates/.claude/commands/{update-agent-infra.md → update-agent-infra.en.md} +0 -0
- /package/templates/.claude/commands/{upgrade-dependency.md → upgrade-dependency.en.md} +0 -0
- /package/templates/.gemini/commands/_project_/{analyze-task.toml → analyze-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{archive-tasks.toml → archive-tasks.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{block-task.toml → block-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{cancel-task.toml → cancel-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{check-task.toml → check-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{close-codescan.toml → close-codescan.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{close-dependabot.toml → close-dependabot.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{commit.toml → commit.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{complete-task.toml → complete-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{create-issue.toml → create-issue.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{create-pr.toml → create-pr.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{create-release-note.toml → create-release-note.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{create-task.toml → create-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{implement-task.toml → implement-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{import-codescan.toml → import-codescan.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{import-dependabot.toml → import-dependabot.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{import-issue.toml → import-issue.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{init-labels.toml → init-labels.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{init-milestones.toml → init-milestones.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{plan-task.toml → plan-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{refine-task.toml → refine-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{refine-title.toml → refine-title.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{release.toml → release.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{restore-task.toml → restore-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{review-task.toml → review-task.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{test-integration.toml → test-integration.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{test.toml → test.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{update-agent-infra.toml → update-agent-infra.en.toml} +0 -0
- /package/templates/.gemini/commands/_project_/{upgrade-dependency.toml → upgrade-dependency.en.toml} +0 -0
- /package/templates/.opencode/commands/{analyze-task.md → analyze-task.en.md} +0 -0
- /package/templates/.opencode/commands/{archive-tasks.md → archive-tasks.en.md} +0 -0
- /package/templates/.opencode/commands/{block-task.md → block-task.en.md} +0 -0
- /package/templates/.opencode/commands/{cancel-task.md → cancel-task.en.md} +0 -0
- /package/templates/.opencode/commands/{check-task.md → check-task.en.md} +0 -0
- /package/templates/.opencode/commands/{close-codescan.md → close-codescan.en.md} +0 -0
- /package/templates/.opencode/commands/{close-dependabot.md → close-dependabot.en.md} +0 -0
- /package/templates/.opencode/commands/{commit.md → commit.en.md} +0 -0
- /package/templates/.opencode/commands/{complete-task.md → complete-task.en.md} +0 -0
- /package/templates/.opencode/commands/{create-issue.md → create-issue.en.md} +0 -0
- /package/templates/.opencode/commands/{create-pr.md → create-pr.en.md} +0 -0
- /package/templates/.opencode/commands/{create-release-note.md → create-release-note.en.md} +0 -0
- /package/templates/.opencode/commands/{create-task.md → create-task.en.md} +0 -0
- /package/templates/.opencode/commands/{implement-task.md → implement-task.en.md} +0 -0
- /package/templates/.opencode/commands/{import-codescan.md → import-codescan.en.md} +0 -0
- /package/templates/.opencode/commands/{import-dependabot.md → import-dependabot.en.md} +0 -0
- /package/templates/.opencode/commands/{import-issue.md → import-issue.en.md} +0 -0
- /package/templates/.opencode/commands/{init-labels.md → init-labels.en.md} +0 -0
- /package/templates/.opencode/commands/{init-milestones.md → init-milestones.en.md} +0 -0
- /package/templates/.opencode/commands/{plan-task.md → plan-task.en.md} +0 -0
- /package/templates/.opencode/commands/{refine-task.md → refine-task.en.md} +0 -0
- /package/templates/.opencode/commands/{refine-title.md → refine-title.en.md} +0 -0
- /package/templates/.opencode/commands/{release.md → release.en.md} +0 -0
- /package/templates/.opencode/commands/{restore-task.md → restore-task.en.md} +0 -0
- /package/templates/.opencode/commands/{review-task.md → review-task.en.md} +0 -0
- /package/templates/.opencode/commands/{test-integration.md → test-integration.en.md} +0 -0
- /package/templates/.opencode/commands/{test.md → test.en.md} +0 -0
- /package/templates/.opencode/commands/{update-agent-infra.md → update-agent-infra.en.md} +0 -0
- /package/templates/.opencode/commands/{upgrade-dependency.md → upgrade-dependency.en.md} +0 -0
package/README.md
CHANGED
|
@@ -35,6 +35,20 @@ agent-infra standardizes that collaboration surface. It gives every supported AI
|
|
|
35
35
|
|
|
36
36
|
## See it in Action
|
|
37
37
|
|
|
38
|
+
### Install & Initialize
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="./assets/demo-init.gif" alt="CLI install and initialize demo" width="100%" style="max-width: 720px;">
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
Once initialized, open the project in your AI TUI and install the latest skills:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
/update-agent-infra
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> AI reads `.agents/.airc.json`, auto-locates the installed template root, and syncs the latest skill manifests, managed files, and registry deterministically via `sync-templates.js`.
|
|
51
|
+
|
|
38
52
|
**Scenario**: Issue #42 reports *"Login API returns 500 when email contains a plus sign"*. Here is the full fix lifecycle — AI does the heavy lifting, you stay in control:
|
|
39
53
|
|
|
40
54
|
```bash
|
|
@@ -181,10 +195,12 @@ This detects the packaged template version and renders all managed files. The sa
|
|
|
181
195
|
|
|
182
196
|
### Sandbox aliases and GitHub CLI
|
|
183
197
|
|
|
184
|
-
`ai sandbox create` now bootstraps the host-side aliases file at `~/.
|
|
198
|
+
`ai sandbox create` now bootstraps the host-side aliases file at `~/.agent-infra/aliases/sandbox.sh` on first run. The generated file includes ready-to-edit yolo shortcuts for Claude, Codex, Gemini CLI, and OpenCode, and every sandbox syncs that file into `/home/devuser/.bash_aliases`.
|
|
185
199
|
|
|
186
200
|
The sandbox image also preinstalls `gh`. When `gh auth token` succeeds on the host, `ai sandbox create` injects the token into the container as `GH_TOKEN`, so `gh` commands work inside the sandbox without extra setup.
|
|
187
201
|
|
|
202
|
+
`ai sandbox exec` also forwards a small terminal-detection whitelist (`TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `LC_TERMINAL`, `LC_TERMINAL_VERSION`) into the container. This keeps interactive TUIs aligned with the host terminal for behaviors such as Claude Code's Shift+Enter newline support, without passing through the full host environment.
|
|
203
|
+
|
|
188
204
|
<a id="architecture-overview"></a>
|
|
189
205
|
|
|
190
206
|
## Architecture Overview
|
|
@@ -394,7 +410,7 @@ The generated `.agents/.airc.json` file is the central contract between the boot
|
|
|
394
410
|
"project": "my-project",
|
|
395
411
|
"org": "my-org",
|
|
396
412
|
"language": "en",
|
|
397
|
-
"templateVersion": "v0.5.
|
|
413
|
+
"templateVersion": "v0.5.2",
|
|
398
414
|
"files": {
|
|
399
415
|
"managed": [
|
|
400
416
|
".agents/workspace/README.md",
|
package/README.zh-CN.md
CHANGED
|
@@ -35,6 +35,20 @@ agent-infra 的目标就是把这层协作面标准化。它为所有支持的 A
|
|
|
35
35
|
|
|
36
36
|
## 实战演示
|
|
37
37
|
|
|
38
|
+
### 安装与初始化
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="./assets/demo-init.gif" alt="CLI 安装初始化演示" width="100%" style="max-width: 720px;">
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
完成初始化后,在你的 AI TUI 中打开项目并安装最新 skills:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
/update-agent-infra
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> AI 读取 `.agents/.airc.json`,自动定位已安装的模板根目录,并通过 `sync-templates.js` 确定性地同步最新的 skill 清单、managed 文件和注册表。
|
|
51
|
+
|
|
38
52
|
**场景**:Issue #42 报告 *"登录接口在邮箱包含加号时返回 500"*。以下是完整的修复流程 —— AI 执行主要工作,你掌控方向:
|
|
39
53
|
|
|
40
54
|
```bash
|
|
@@ -181,10 +195,12 @@ CLI 会收集项目元数据,向所有支持的 AI TUI 安装 `update-agent-in
|
|
|
181
195
|
|
|
182
196
|
### 沙箱 aliases 与 GitHub CLI
|
|
183
197
|
|
|
184
|
-
`ai sandbox create` 在首次运行时会自动生成宿主机侧的 `~/.
|
|
198
|
+
`ai sandbox create` 在首次运行时会自动生成宿主机侧的 `~/.agent-infra/aliases/sandbox.sh`。该文件内置了 Claude、Codex、Gemini CLI 和 OpenCode 的 yolo 快捷命令模板,你可以直接修改;每次创建沙箱时,这个文件都会同步到容器内的 `/home/devuser/.bash_aliases`。
|
|
185
199
|
|
|
186
200
|
沙箱镜像也会预装 `gh`。如果宿主机上的 `gh auth token` 能成功返回 token,`ai sandbox create` 会把它以 `GH_TOKEN` 环境变量注入容器,让你在沙箱里直接使用 `gh`,无需额外登录配置。
|
|
187
201
|
|
|
202
|
+
`ai sandbox exec` 也会向容器透传一小组终端检测白名单变量(`TERM_PROGRAM`、`TERM_PROGRAM_VERSION`、`LC_TERMINAL`、`LC_TERMINAL_VERSION`)。这样可以让交互式 TUI 保持与宿主终端一致的行为,例如 Claude Code 的 `Shift+Enter` 换行支持,同时避免把整个宿主环境灌入容器。
|
|
203
|
+
|
|
188
204
|
<a id="architecture-overview"></a>
|
|
189
205
|
|
|
190
206
|
## 架构概览
|
|
@@ -394,7 +410,7 @@ import-issue #42 从 GitHub Issue 导入任务
|
|
|
394
410
|
"project": "my-project",
|
|
395
411
|
"org": "my-org",
|
|
396
412
|
"language": "en",
|
|
397
|
-
"templateVersion": "v0.5.
|
|
413
|
+
"templateVersion": "v0.5.2",
|
|
398
414
|
"files": {
|
|
399
415
|
"managed": [
|
|
400
416
|
".agents/workspace/README.md",
|
package/bin/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ const USAGE = `agent-infra - bootstrap AI collaboration infrastructure
|
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
16
|
agent-infra init Initialize a new project with update-agent-infra seed command
|
|
17
|
-
agent-infra merge Merge
|
|
17
|
+
agent-infra merge Merge tasks from another workspace directory (active/blocked/completed/archive)
|
|
18
18
|
agent-infra update Update seed files and sync file registry for an existing project
|
|
19
19
|
agent-infra sandbox Manage Docker-based AI sandboxes
|
|
20
20
|
agent-infra version Show version
|
package/lib/defaults.json
CHANGED
package/lib/init.js
CHANGED
|
@@ -92,14 +92,22 @@ async function cmdInit() {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
let language = await prompt('Language (en / zh)', 'zh');
|
|
95
|
-
closePrompt();
|
|
96
95
|
if (language === 'zh') language = 'zh-CN';
|
|
97
96
|
if (language !== 'en' && language !== 'zh-CN') {
|
|
97
|
+
closePrompt();
|
|
98
98
|
err(`Language must be 'en' or 'zh'. Got: ${language}`);
|
|
99
99
|
process.exitCode = 1;
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
const platformType = (await prompt('Platform type', 'github')).trim() || 'github';
|
|
104
|
+
closePrompt();
|
|
105
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(platformType)) {
|
|
106
|
+
err(`Platform type must match /^[a-z0-9][a-z0-9-]*$/. Got: ${platformType}`);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
const project = projectName;
|
|
104
112
|
const replacements = { project, org: orgName };
|
|
105
113
|
|
|
@@ -118,9 +126,9 @@ async function cmdInit() {
|
|
|
118
126
|
geminiSrc = 'update-agent-infra.zh-CN.toml';
|
|
119
127
|
opencodeSrc = 'update-agent-infra.zh-CN.md';
|
|
120
128
|
} else {
|
|
121
|
-
claudeSrc = 'update-agent-infra.md';
|
|
122
|
-
geminiSrc = 'update-agent-infra.toml';
|
|
123
|
-
opencodeSrc = 'update-agent-infra.md';
|
|
129
|
+
claudeSrc = 'update-agent-infra.en.md';
|
|
130
|
+
geminiSrc = 'update-agent-infra.en.toml';
|
|
131
|
+
opencodeSrc = 'update-agent-infra.en.md';
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
// install skill
|
|
@@ -128,7 +136,8 @@ async function cmdInit() {
|
|
|
128
136
|
path.join(templateDir, '.agents', 'skills', 'update-agent-infra'),
|
|
129
137
|
path.join('.agents', 'skills', 'update-agent-infra'),
|
|
130
138
|
replacements,
|
|
131
|
-
language
|
|
139
|
+
language,
|
|
140
|
+
platformType
|
|
132
141
|
);
|
|
133
142
|
ok('Installed .agents/skills/update-agent-infra/');
|
|
134
143
|
|
|
@@ -161,6 +170,7 @@ async function cmdInit() {
|
|
|
161
170
|
project: projectName,
|
|
162
171
|
org: orgName,
|
|
163
172
|
language,
|
|
173
|
+
platform: { type: platformType },
|
|
164
174
|
templateVersion: VERSION,
|
|
165
175
|
sandbox: structuredClone(defaults.sandbox),
|
|
166
176
|
labels: structuredClone(defaults.labels),
|
package/lib/merge.js
CHANGED
|
@@ -6,6 +6,15 @@ const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
|
6
6
|
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
7
7
|
const TITLE_RE = /^# (.+)$/m;
|
|
8
8
|
const DATE_FROM_PATH_RE = /(?:^|[/\\])(\d{4})[/\\](\d{2})[/\\](\d{2})(?:[/\\]|$)/;
|
|
9
|
+
const MUTABLE_SECTIONS = ['active', 'blocked', 'completed'];
|
|
10
|
+
const ALL_SECTIONS = [...MUTABLE_SECTIONS, 'archive'];
|
|
11
|
+
const SECTION_LABELS = {
|
|
12
|
+
active: 'Active',
|
|
13
|
+
blocked: 'Blocked',
|
|
14
|
+
completed: 'Completed',
|
|
15
|
+
archive: 'Archive'
|
|
16
|
+
};
|
|
17
|
+
const DIVIDER = '═'.repeat(55);
|
|
9
18
|
|
|
10
19
|
function extractField(content, fieldName) {
|
|
11
20
|
const match = content.match(FRONTMATTER_RE);
|
|
@@ -394,50 +403,305 @@ function removeManifestFiles(rootDir) {
|
|
|
394
403
|
}
|
|
395
404
|
}
|
|
396
405
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
406
|
+
function formatTimestamp(date) {
|
|
407
|
+
return [
|
|
408
|
+
date.getFullYear(),
|
|
409
|
+
String(date.getMonth() + 1).padStart(2, '0'),
|
|
410
|
+
String(date.getDate()).padStart(2, '0')
|
|
411
|
+
].join('-') + ' ' + [
|
|
412
|
+
String(date.getHours()).padStart(2, '0'),
|
|
413
|
+
String(date.getMinutes()).padStart(2, '0'),
|
|
414
|
+
String(date.getSeconds()).padStart(2, '0')
|
|
415
|
+
].join(':');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function formatBackupTimestamp(date) {
|
|
419
|
+
return [
|
|
420
|
+
date.getFullYear(),
|
|
421
|
+
String(date.getMonth() + 1).padStart(2, '0'),
|
|
422
|
+
String(date.getDate()).padStart(2, '0')
|
|
423
|
+
].join('') + `-${String(date.getHours()).padStart(2, '0')}${String(date.getMinutes()).padStart(2, '0')}${String(date.getSeconds()).padStart(2, '0')}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function toPosixPath(relativePath) {
|
|
427
|
+
return relativePath.split(path.sep).join('/');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getLatestFileMtime(taskDir) {
|
|
431
|
+
let latestMs = null;
|
|
432
|
+
const stack = [taskDir];
|
|
433
|
+
|
|
434
|
+
while (stack.length > 0) {
|
|
435
|
+
const currentDir = stack.pop();
|
|
436
|
+
if (!currentDir || !fs.existsSync(currentDir)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
441
|
+
const entryPath = path.join(currentDir, entry.name);
|
|
442
|
+
if (entry.isDirectory()) {
|
|
443
|
+
stack.push(entryPath);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const { mtimeMs } = fs.statSync(entryPath);
|
|
448
|
+
latestMs = latestMs === null ? mtimeMs : Math.max(latestMs, mtimeMs);
|
|
449
|
+
}
|
|
401
450
|
}
|
|
402
451
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
452
|
+
return latestMs;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getTaskTimestamp(taskDir) {
|
|
456
|
+
const taskFile = path.join(taskDir, 'task.md');
|
|
457
|
+
|
|
458
|
+
if (fs.existsSync(taskFile)) {
|
|
459
|
+
const content = fs.readFileSync(taskFile, 'utf8');
|
|
460
|
+
const updatedAt = extractField(content, 'updated_at');
|
|
461
|
+
if (updatedAt) {
|
|
462
|
+
return { value: updatedAt, source: 'frontmatter' };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const taskFileStat = fs.statSync(taskFile);
|
|
466
|
+
return {
|
|
467
|
+
value: formatTimestamp(taskFileStat.mtime),
|
|
468
|
+
source: 'task-mtime'
|
|
469
|
+
};
|
|
406
470
|
}
|
|
407
471
|
|
|
408
|
-
|
|
409
|
-
|
|
472
|
+
const latestMs = getLatestFileMtime(taskDir);
|
|
473
|
+
if (latestMs !== null) {
|
|
474
|
+
return {
|
|
475
|
+
value: formatTimestamp(new Date(latestMs)),
|
|
476
|
+
source: 'dir-mtime'
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const dirStat = fs.statSync(taskDir);
|
|
481
|
+
return {
|
|
482
|
+
value: formatTimestamp(dirStat.mtime),
|
|
483
|
+
source: 'dir-mtime'
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function compareTimestamps(left, right) {
|
|
488
|
+
return left.value.localeCompare(right.value);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function scanWorkspaceSection(rootDir, sectionName) {
|
|
492
|
+
const sectionDir = path.join(rootDir, sectionName);
|
|
493
|
+
if (!fs.existsSync(sectionDir) || !fs.statSync(sectionDir).isDirectory()) {
|
|
494
|
+
return [];
|
|
410
495
|
}
|
|
411
496
|
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
497
|
+
const records = [];
|
|
498
|
+
for (const entry of fs.readdirSync(sectionDir, { withFileTypes: true })) {
|
|
499
|
+
if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name)) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
416
502
|
|
|
417
|
-
|
|
503
|
+
const taskDir = path.join(sectionDir, entry.name);
|
|
504
|
+
const taskFile = path.join(taskDir, 'task.md');
|
|
505
|
+
if (!fs.existsSync(taskFile)) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
records.push({
|
|
510
|
+
taskId: entry.name,
|
|
511
|
+
section: sectionName,
|
|
512
|
+
taskDir,
|
|
513
|
+
timestamp: getTaskTimestamp(taskDir)
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return records.sort((left, right) => left.taskId.localeCompare(right.taskId));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function buildWorkspaceIndex(workspaceDir) {
|
|
521
|
+
const index = new Map();
|
|
522
|
+
|
|
523
|
+
for (const section of MUTABLE_SECTIONS) {
|
|
524
|
+
for (const record of scanWorkspaceSection(workspaceDir, section)) {
|
|
525
|
+
index.set(record.taskId, record);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return index;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function backupTaskDir(backupRoot, section, taskDir, taskId) {
|
|
533
|
+
const backupDir = path.join(backupRoot, section, taskId);
|
|
534
|
+
fs.mkdirSync(path.dirname(backupDir), { recursive: true });
|
|
535
|
+
fs.cpSync(taskDir, backupDir, { recursive: true });
|
|
536
|
+
return backupDir;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function copyTaskToSection(sourceTask, workspaceDir) {
|
|
540
|
+
const destinationDir = path.join(workspaceDir, sourceTask.section, sourceTask.taskId);
|
|
541
|
+
fs.mkdirSync(path.dirname(destinationDir), { recursive: true });
|
|
542
|
+
fs.cpSync(sourceTask.taskDir, destinationDir, { recursive: true });
|
|
543
|
+
return destinationDir;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function detectSourceMode(sourcePath) {
|
|
547
|
+
for (const section of ALL_SECTIONS) {
|
|
548
|
+
const sectionDir = path.join(sourcePath, section);
|
|
549
|
+
if (fs.existsSync(sectionDir) && fs.statSync(sectionDir).isDirectory()) {
|
|
550
|
+
return 'workspace';
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return 'legacy-archive';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function createReport(sourcePath, backupRoot) {
|
|
558
|
+
return {
|
|
559
|
+
sourcePath,
|
|
560
|
+
backupRoot,
|
|
561
|
+
sections: {
|
|
562
|
+
active: { copied: [], updated: [], moved: [], skipped: [] },
|
|
563
|
+
blocked: { copied: [], updated: [], moved: [], skipped: [] },
|
|
564
|
+
completed: { copied: [], updated: [], moved: [], skipped: [] },
|
|
565
|
+
archive: { copied: [], skipped: [] }
|
|
566
|
+
},
|
|
567
|
+
details: [],
|
|
568
|
+
backupCount: 0
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function recordMutable(report, reportSection, action, entry) {
|
|
573
|
+
report.sections[reportSection][action].push(entry);
|
|
574
|
+
report.details.push(entry);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function recordArchive(report, action, entry) {
|
|
578
|
+
report.sections.archive[action].push(entry);
|
|
579
|
+
report.details.push(entry);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function mergeMutableSections({ sourceWorkspace, localWorkspace, backupRoot, report }) {
|
|
583
|
+
const localIndex = buildWorkspaceIndex(localWorkspace);
|
|
584
|
+
|
|
585
|
+
for (const sourceSection of MUTABLE_SECTIONS) {
|
|
586
|
+
const sourceTasks = scanWorkspaceSection(sourceWorkspace, sourceSection);
|
|
587
|
+
|
|
588
|
+
for (const sourceTask of sourceTasks) {
|
|
589
|
+
const localMatch = localIndex.get(sourceTask.taskId) || null;
|
|
590
|
+
|
|
591
|
+
if (!localMatch) {
|
|
592
|
+
const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
|
|
593
|
+
localIndex.set(sourceTask.taskId, {
|
|
594
|
+
taskId: sourceTask.taskId,
|
|
595
|
+
section: sourceTask.section,
|
|
596
|
+
taskDir: destinationDir,
|
|
597
|
+
timestamp: getTaskTimestamp(destinationDir)
|
|
598
|
+
});
|
|
599
|
+
recordMutable(report, sourceTask.section, 'copied', {
|
|
600
|
+
action: 'copied',
|
|
601
|
+
symbol: '✓',
|
|
602
|
+
taskId: sourceTask.taskId,
|
|
603
|
+
section: sourceTask.section,
|
|
604
|
+
detail: 'copied'
|
|
605
|
+
});
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const comparison = compareTimestamps(sourceTask.timestamp, localMatch.timestamp);
|
|
610
|
+
if (comparison > 0) {
|
|
611
|
+
backupTaskDir(backupRoot, localMatch.section, localMatch.taskDir, localMatch.taskId);
|
|
612
|
+
report.backupCount += 1;
|
|
613
|
+
fs.rmSync(localMatch.taskDir, { recursive: true, force: true });
|
|
614
|
+
|
|
615
|
+
const destinationDir = copyTaskToSection(sourceTask, localWorkspace);
|
|
616
|
+
localIndex.set(sourceTask.taskId, {
|
|
617
|
+
taskId: sourceTask.taskId,
|
|
618
|
+
section: sourceTask.section,
|
|
619
|
+
taskDir: destinationDir,
|
|
620
|
+
timestamp: getTaskTimestamp(destinationDir)
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
if (localMatch.section === sourceTask.section) {
|
|
624
|
+
recordMutable(report, sourceTask.section, 'updated', {
|
|
625
|
+
action: 'updated',
|
|
626
|
+
symbol: '↑',
|
|
627
|
+
taskId: sourceTask.taskId,
|
|
628
|
+
section: sourceTask.section,
|
|
629
|
+
detail: `updated (source newer: ${sourceTask.timestamp.value} > ${localMatch.timestamp.value})`
|
|
630
|
+
});
|
|
631
|
+
} else {
|
|
632
|
+
recordMutable(report, localMatch.section, 'moved', {
|
|
633
|
+
action: 'moved',
|
|
634
|
+
symbol: '⇄',
|
|
635
|
+
taskId: sourceTask.taskId,
|
|
636
|
+
fromSection: localMatch.section,
|
|
637
|
+
toSection: sourceTask.section,
|
|
638
|
+
detail: `moved (source newer: ${sourceTask.timestamp.value} > ${localMatch.timestamp.value})`
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (comparison < 0) {
|
|
646
|
+
recordMutable(report, localMatch.section, 'skipped', {
|
|
647
|
+
action: 'skipped',
|
|
648
|
+
symbol: '⊘',
|
|
649
|
+
taskId: sourceTask.taskId,
|
|
650
|
+
section: localMatch.section,
|
|
651
|
+
detail: `skipped (local newer: ${localMatch.timestamp.value} > ${sourceTask.timestamp.value})`
|
|
652
|
+
});
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
recordMutable(report, localMatch.section, 'skipped', {
|
|
657
|
+
action: 'skipped',
|
|
658
|
+
symbol: '⊘',
|
|
659
|
+
taskId: sourceTask.taskId,
|
|
660
|
+
section: localMatch.section,
|
|
661
|
+
detail: `skipped (same timestamp: ${sourceTask.timestamp.value})`
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function mergeArchiveSection(sourceArchive, localArchive, report) {
|
|
668
|
+
const sourceTasks = scanSourceTasks(sourceArchive);
|
|
418
669
|
|
|
419
670
|
for (const task of sourceTasks) {
|
|
420
|
-
const existingTaskDir = taskExistsInArchive(
|
|
671
|
+
const existingTaskDir = taskExistsInArchive(localArchive, task.taskId);
|
|
421
672
|
if (existingTaskDir) {
|
|
422
|
-
skipped
|
|
673
|
+
recordArchive(report, 'skipped', {
|
|
674
|
+
action: 'skipped',
|
|
675
|
+
symbol: '⊘',
|
|
423
676
|
taskId: task.taskId,
|
|
424
|
-
|
|
677
|
+
section: 'archive',
|
|
678
|
+
relativePath: `${toPosixPath(path.relative(localArchive, existingTaskDir))}/`,
|
|
679
|
+
detail: `skipped (already exists at ${toPosixPath(path.relative(localArchive, existingTaskDir))}/)`
|
|
425
680
|
});
|
|
426
681
|
continue;
|
|
427
682
|
}
|
|
428
683
|
|
|
429
|
-
const destinationDir = path.join(
|
|
684
|
+
const destinationDir = path.join(localArchive, task.relativePath);
|
|
430
685
|
fs.mkdirSync(path.dirname(destinationDir), { recursive: true });
|
|
431
686
|
fs.cpSync(task.taskDir, destinationDir, { recursive: true });
|
|
432
|
-
|
|
687
|
+
recordArchive(report, 'copied', {
|
|
688
|
+
action: 'copied',
|
|
689
|
+
symbol: '✓',
|
|
433
690
|
taskId: task.taskId,
|
|
434
|
-
|
|
691
|
+
section: 'archive',
|
|
692
|
+
relativePath: task.relativePath,
|
|
693
|
+
detail: 'copied'
|
|
435
694
|
});
|
|
436
695
|
}
|
|
437
696
|
|
|
438
|
-
|
|
697
|
+
return sourceTasks.length;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function printLegacyArchiveMessages(report, sourcePath) {
|
|
701
|
+
const merged = report.sections.archive.copied;
|
|
702
|
+
const skipped = report.sections.archive.skipped;
|
|
439
703
|
|
|
440
|
-
if (
|
|
704
|
+
if (merged.length === 0 && skipped.length === 0) {
|
|
441
705
|
info(`No archived tasks found in ${sourcePath}`);
|
|
442
706
|
}
|
|
443
707
|
|
|
@@ -453,13 +717,169 @@ async function cmdMerge(args) {
|
|
|
453
717
|
info('Merge summary');
|
|
454
718
|
info(`- Merged: ${merged.length}`);
|
|
455
719
|
info(`- Skipped: ${skipped.length}`);
|
|
720
|
+
process.stdout.write('\n');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function printSection(lines, name, counts) {
|
|
724
|
+
const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (.agents/workspace/${name}/):`;
|
|
725
|
+
lines.push(title);
|
|
726
|
+
|
|
727
|
+
const entries = [
|
|
728
|
+
['copied', '✓ Copied '],
|
|
729
|
+
['updated', '↑ Updated '],
|
|
730
|
+
['moved', '⇄ Moved '],
|
|
731
|
+
['skipped', '⊘ Skipped ']
|
|
732
|
+
].filter(([key]) => Array.isArray(counts[key]));
|
|
733
|
+
|
|
734
|
+
const nonZeroEntries = entries.filter(([key]) => counts[key].length > 0);
|
|
735
|
+
if (nonZeroEntries.length === 0) {
|
|
736
|
+
lines.push(' (no changes)', '');
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
for (const [key, label] of nonZeroEntries) {
|
|
741
|
+
lines.push(` ${label}: ${counts[key].length}`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
lines.push('');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function printArchiveSection(lines, counts) {
|
|
748
|
+
const title = `${SECTION_LABELS.archive.padEnd(9, ' ')} (.agents/workspace/archive/):`;
|
|
749
|
+
lines.push(title);
|
|
750
|
+
|
|
751
|
+
if (counts.copied.length === 0 && counts.skipped.length === 0) {
|
|
752
|
+
lines.push(' (no changes)', '');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (counts.copied.length > 0) {
|
|
757
|
+
lines.push(` ✓ Copied : ${counts.copied.length}`);
|
|
758
|
+
}
|
|
759
|
+
if (counts.skipped.length > 0) {
|
|
760
|
+
lines.push(` ⊘ Skipped : ${counts.skipped.length}`);
|
|
761
|
+
}
|
|
762
|
+
lines.push('');
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function renderDetail(entry) {
|
|
766
|
+
if (entry.action === 'moved') {
|
|
767
|
+
return ` ${entry.symbol} ${entry.taskId} ${entry.fromSection}→${entry.toSection} ${entry.detail}`;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const label = entry.section.padEnd(9, ' ');
|
|
771
|
+
return ` ${entry.symbol} ${entry.taskId} ${label} ${entry.detail}`;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function printReport(report) {
|
|
775
|
+
const mutableTotals = MUTABLE_SECTIONS.reduce((acc, section) => {
|
|
776
|
+
acc.copied += report.sections[section].copied.length;
|
|
777
|
+
acc.updated += report.sections[section].updated.length;
|
|
778
|
+
acc.moved += report.sections[section].moved.length;
|
|
779
|
+
acc.skipped += report.sections[section].skipped.length;
|
|
780
|
+
return acc;
|
|
781
|
+
}, { copied: 0, updated: 0, moved: 0, skipped: 0 });
|
|
782
|
+
|
|
783
|
+
const archiveTotals = {
|
|
784
|
+
copied: report.sections.archive.copied.length,
|
|
785
|
+
skipped: report.sections.archive.skipped.length
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const lines = [
|
|
789
|
+
'Merge summary',
|
|
790
|
+
DIVIDER,
|
|
791
|
+
`Source: ${report.sourcePath}`,
|
|
792
|
+
`Backup: ${report.backupRoot}`,
|
|
793
|
+
''
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
for (const section of MUTABLE_SECTIONS) {
|
|
797
|
+
printSection(lines, section, report.sections[section]);
|
|
798
|
+
}
|
|
799
|
+
printArchiveSection(lines, report.sections.archive);
|
|
800
|
+
|
|
801
|
+
lines.push(
|
|
802
|
+
DIVIDER,
|
|
803
|
+
`Totals: ${mutableTotals.copied + archiveTotals.copied} copied, ${mutableTotals.updated} updated, ${mutableTotals.moved} moved, ${mutableTotals.skipped + archiveTotals.skipped} skipped`,
|
|
804
|
+
`Backup contains ${report.backupCount} task(s); review and remove when verified.`,
|
|
805
|
+
'',
|
|
806
|
+
'Detailed log:'
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
if (report.details.length === 0) {
|
|
810
|
+
lines.push(' (none)');
|
|
811
|
+
} else {
|
|
812
|
+
for (const detail of report.details) {
|
|
813
|
+
lines.push(renderDetail(detail));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function cmdMerge(args) {
|
|
821
|
+
const sourcePath = args[0];
|
|
822
|
+
if (!sourcePath) {
|
|
823
|
+
throw new Error('Usage: agent-infra merge <source-path>');
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const resolvedSource = path.resolve(sourcePath);
|
|
827
|
+
if (!fs.existsSync(resolvedSource)) {
|
|
828
|
+
throw new Error(`Source path does not exist: ${sourcePath}`);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (!fs.statSync(resolvedSource).isDirectory()) {
|
|
832
|
+
throw new Error(`Source path is not a directory: ${sourcePath}`);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const workspaceDir = path.join(process.cwd(), '.agents', 'workspace');
|
|
836
|
+
const archiveDir = path.join(workspaceDir, 'archive');
|
|
837
|
+
const backupStamp = formatBackupTimestamp(new Date());
|
|
838
|
+
const backupRootRelative = `.agents/workspace/.merge-backup/${backupStamp}/`;
|
|
839
|
+
const backupRoot = path.join(workspaceDir, '.merge-backup', backupStamp);
|
|
840
|
+
const report = createReport(resolvedSource, backupRootRelative);
|
|
841
|
+
const mode = detectSourceMode(resolvedSource);
|
|
842
|
+
|
|
843
|
+
for (const section of ALL_SECTIONS) {
|
|
844
|
+
fs.mkdirSync(path.join(workspaceDir, section), { recursive: true });
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (mode === 'legacy-archive') {
|
|
848
|
+
info('Detected legacy archive source; treating the input as archive-only for backward compatibility.');
|
|
849
|
+
mergeArchiveSection(resolvedSource, archiveDir, report);
|
|
850
|
+
} else {
|
|
851
|
+
mergeMutableSections({
|
|
852
|
+
sourceWorkspace: resolvedSource,
|
|
853
|
+
localWorkspace: workspaceDir,
|
|
854
|
+
backupRoot,
|
|
855
|
+
report
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
const sourceArchive = path.join(resolvedSource, 'archive');
|
|
859
|
+
if (fs.existsSync(sourceArchive) && fs.statSync(sourceArchive).isDirectory()) {
|
|
860
|
+
mergeArchiveSection(sourceArchive, archiveDir, report);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
rebuildManifests(archiveDir);
|
|
865
|
+
|
|
866
|
+
if (mode === 'legacy-archive') {
|
|
867
|
+
printLegacyArchiveMessages(report, sourcePath);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
printReport(report);
|
|
456
871
|
}
|
|
457
872
|
|
|
458
873
|
export {
|
|
459
874
|
cmdMerge,
|
|
875
|
+
compareTimestamps,
|
|
876
|
+
detectSourceMode,
|
|
460
877
|
extractField,
|
|
461
878
|
extractTitle,
|
|
879
|
+
formatBackupTimestamp,
|
|
880
|
+
getTaskTimestamp,
|
|
462
881
|
rebuildManifests,
|
|
463
882
|
scanSourceTasks,
|
|
883
|
+
scanWorkspaceSection,
|
|
464
884
|
taskExistsInArchive
|
|
465
885
|
};
|