@fitlab-ai/agent-infra 0.8.4 → 0.8.5
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/dist/lib/defaults.json +8 -0
- package/dist/lib/init.js +5 -0
- package/dist/lib/sandbox/commands/create.js +30 -4
- package/dist/lib/sandbox/engine.js +33 -3
- package/dist/lib/sandbox/engines/colima.js +2 -2
- package/dist/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/dist/lib/server/daemon.js +9 -0
- package/dist/lib/server/process-control.js +66 -73
- package/dist/lib/server/process-state.js +168 -0
- package/dist/lib/update.js +2 -0
- package/lib/defaults.json +8 -0
- package/lib/init.ts +9 -0
- package/lib/sandbox/commands/create.ts +32 -3
- package/lib/sandbox/engine.ts +37 -3
- package/lib/sandbox/engines/colima.ts +2 -2
- package/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/lib/server/daemon.ts +9 -0
- package/lib/server/process-control.ts +70 -71
- package/lib/server/process-state.ts +183 -0
- package/lib/update.ts +6 -0
- package/package.json +2 -1
- package/runtime/platform-adapters/platform-sync.github.js +1501 -0
- package/templates/.agents/README.en.md +2 -0
- package/templates/.agents/README.zh-CN.md +2 -0
- package/templates/.agents/rules/review-handshake.en.md +5 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +5 -4
- package/templates/.agents/scripts/lib/agent-infra-package.js +149 -0
- package/templates/.agents/scripts/lib/post-review-commit.js +5 -0
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +25 -1459
- package/templates/.agents/scripts/review-diff-fingerprint.js +95 -24
- package/templates/.agents/scripts/validate-artifact.js +68 -55
- package/templates/.agents/skills/commit/SKILL.en.md +5 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +29 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +29 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/init-milestones/SKILL.en.md +2 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +5 -4
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +174 -136
|
@@ -209,6 +209,8 @@ The `files` field in `.agents/.airc.json` groups project files into three catego
|
|
|
209
209
|
| `merged` | Merge semantically by AI or humans | Do not write from the template | Keep the local project copy |
|
|
210
210
|
| `ejected` | May be created from the template first; skip overwrite once it exists | Do not write from the template | Keep the local project copy |
|
|
211
211
|
|
|
212
|
+
Ordinary `managed` files keep their overwrite semantics. A small set of platform lifecycle workflows can be registered as guarded managed files. The synchronizer stores each rendered source SHA-256 in `files.managedBaselines` and uses a three-way comparison: accept template-only upgrades, preserve user-only changes, and report a conflict without overwriting when both sides changed or the origin is unknown. This mapping is tool-maintained and should not be edited manually.
|
|
213
|
+
|
|
212
214
|
`ejected` has two common uses:
|
|
213
215
|
|
|
214
216
|
1. **Taking over a built-in file**: the project needs full control over a rule, command, or config file that originally came from the template.
|
|
@@ -209,6 +209,8 @@ disable-model-invocation: true # 可选;由支持该能力的 TUI 适配器
|
|
|
209
209
|
| `merged` | 由 AI 或人工语义合并 | 不从模板写入 | 保留项目本地副本 |
|
|
210
210
|
| `ejected` | 首次可从模板创建,已存在时跳过覆盖 | 不从模板写入 | 保留项目本地副本 |
|
|
211
211
|
|
|
212
|
+
普通 `managed` 文件仍采用覆盖语义。少数平台生命周期 workflow 可登记为 guarded managed:同步器在 `files.managedBaselines` 保存渲染后来源的 SHA-256,并用三方比较自动接收官方单边升级、保留用户单边修改,在双方都变化或来源未知时报告冲突而不覆盖。该映射由工具维护,不建议手工编辑。
|
|
213
|
+
|
|
212
214
|
`ejected` 有两种常见用法:
|
|
213
215
|
|
|
214
216
|
1. **接管内置文件**:项目需要完全控制原本来自模板的规则、命令或配置文件,避免后续同步覆盖本地内容。
|
|
@@ -85,10 +85,11 @@ When an executor judges an item to be a key design decision that needs human rul
|
|
|
85
85
|
|
|
86
86
|
## post-review commit gate (code stage only)
|
|
87
87
|
|
|
88
|
-
- `review-code` captures
|
|
89
|
-
- `commit` reads only the highest-round `review-code` artifact.
|
|
90
|
-
-
|
|
91
|
-
-
|
|
88
|
+
- `review-code` captures the review baseline `R` (diff base), full-worktree diff fingerprint `F`, and normalized snapshot tree `T`. An Approved clean snapshot may set `B=R`; an Approved snapshot with uncommitted changes clears or omits `B`.
|
|
89
|
+
- `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.
|
|
90
|
+
- After a successful commit it sets `B=last_reviewed_commit=<new_head>`; B means only a reviewed snapshot anchored to a Git commit.
|
|
91
|
+
- 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.
|
|
92
|
+
- If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`.
|
|
92
93
|
- **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.
|
|
93
94
|
|
|
94
95
|
## Gate behavior cheat sheet
|
|
@@ -85,10 +85,11 @@
|
|
|
85
85
|
|
|
86
86
|
## post-review commit 门禁(仅 code 阶段)
|
|
87
87
|
|
|
88
|
-
- `review-code`
|
|
89
|
-
- `commit` 只读取最高轮 `review-code`
|
|
90
|
-
-
|
|
91
|
-
-
|
|
88
|
+
- `review-code` 一次性捕获审查基线 `R`(diff base)、完整工作区差异指纹 `F` 与规范化快照树 `T`;Approved 且快照干净时可令 `B=R`,Approved 且含未提交差异时清除/不写 `B`。
|
|
89
|
+
- `commit` 只读取最高轮 Approved `review-code` 产物;提交前比较 `pre_head == R`、完整工作区树 `W == T`、规范化暂存树 `S == T`,任一失配都在 `git commit` 前阻断并报告两组 added/missing/different 路径。
|
|
90
|
+
- 成功提交后令 `B=last_reviewed_commit=<new_head>`;`B` 只表示已经落到 Git commit 的审查锚点。
|
|
91
|
+
- `complete-task` 的 `post-review-commit` gate 只使用 B;B 缺失、畸形或对象不存在时报告 `reviewed snapshot was not anchored`,不得回退 R。
|
|
92
|
+
- 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。
|
|
92
93
|
- **豁免**:在账本追加一行 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`,记录人工明确允许该批提交免复审。
|
|
93
94
|
|
|
94
95
|
## gate 行为速查
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const PACKAGE_NAME = "@fitlab-ai/agent-infra";
|
|
7
|
+
const RUNTIME_RELATIVE_PATH = "runtime/platform-adapters/platform-sync.github.js";
|
|
8
|
+
const COMMAND_NAMES = ["ai", "agent-infra"];
|
|
9
|
+
|
|
10
|
+
function canonicalize(candidate) {
|
|
11
|
+
try {
|
|
12
|
+
return fs.realpathSync(candidate);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function inspectPackageRoot(candidate, source) {
|
|
19
|
+
const canonicalRoot = canonicalize(candidate);
|
|
20
|
+
if (!canonicalRoot) {
|
|
21
|
+
return { source, candidate, reason: "path does not exist" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const packagePath = path.join(canonicalRoot, "package.json");
|
|
25
|
+
let metadata;
|
|
26
|
+
try {
|
|
27
|
+
metadata = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return { source, candidate: canonicalRoot, reason: `cannot read ${packagePath}` };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (metadata.name !== PACKAGE_NAME) {
|
|
33
|
+
return {
|
|
34
|
+
source,
|
|
35
|
+
candidate: canonicalRoot,
|
|
36
|
+
reason: `${packagePath} does not belong to ${PACKAGE_NAME}`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const runtimePath = path.join(canonicalRoot, RUNTIME_RELATIVE_PATH);
|
|
41
|
+
if (!fs.existsSync(runtimePath)) {
|
|
42
|
+
return { source, candidate: canonicalRoot, reason: `runtime not found at ${runtimePath}` };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
source,
|
|
47
|
+
candidate: canonicalRoot,
|
|
48
|
+
packageRoot: canonicalRoot,
|
|
49
|
+
runtimePath,
|
|
50
|
+
templateRoot: path.join(canonicalRoot, "templates")
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function packageRootsAbove(startPath) {
|
|
55
|
+
const roots = [];
|
|
56
|
+
let current = path.dirname(startPath);
|
|
57
|
+
while (true) {
|
|
58
|
+
if (fs.existsSync(path.join(current, "package.json"))) roots.push(current);
|
|
59
|
+
const parent = path.dirname(current);
|
|
60
|
+
if (parent === current) break;
|
|
61
|
+
current = parent;
|
|
62
|
+
}
|
|
63
|
+
return roots;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function lookupCommand(command, platform) {
|
|
67
|
+
const lookup = platform === "win32" ? `where ${command}` : `command -v ${command}`;
|
|
68
|
+
try {
|
|
69
|
+
return childProcess.execSync(lookup, {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
72
|
+
}).trim().split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function rootsForCommand(commandPath, platform) {
|
|
79
|
+
const roots = [];
|
|
80
|
+
if (platform === "win32" && /\.cmd$/i.test(commandPath)) {
|
|
81
|
+
roots.push(path.join(path.dirname(commandPath), "node_modules", "@fitlab-ai", "agent-infra"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const canonicalCommand = canonicalize(commandPath);
|
|
85
|
+
if (canonicalCommand) roots.push(...packageRootsAbove(canonicalCommand));
|
|
86
|
+
return roots;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveAgentInfraPackage(options = {}) {
|
|
90
|
+
const env = options.env || process.env;
|
|
91
|
+
const platform = options.platform || process.platform;
|
|
92
|
+
const startPath = options.startPath || fileURLToPath(import.meta.url);
|
|
93
|
+
const attempts = [];
|
|
94
|
+
|
|
95
|
+
const tryRoot = (candidate, source) => {
|
|
96
|
+
const inspected = inspectPackageRoot(candidate, source);
|
|
97
|
+
attempts.push(inspected);
|
|
98
|
+
return inspected.packageRoot ? inspected : null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (env.AGENT_INFRA_PACKAGE_ROOT) {
|
|
102
|
+
const explicit = tryRoot(env.AGENT_INFRA_PACKAGE_ROOT, "AGENT_INFRA_PACKAGE_ROOT");
|
|
103
|
+
if (explicit) return { ...explicit, attempts };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const candidate of packageRootsAbove(startPath)) {
|
|
107
|
+
const self = tryRoot(candidate, "current installation");
|
|
108
|
+
if (self) return { ...self, attempts };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const command of COMMAND_NAMES) {
|
|
112
|
+
const commandPaths = lookupCommand(command, platform);
|
|
113
|
+
if (commandPaths.length === 0) {
|
|
114
|
+
attempts.push({ source: `PATH:${command}`, candidate: null, reason: "command not found" });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const commandPath of commandPaths) {
|
|
118
|
+
const roots = rootsForCommand(commandPath, platform);
|
|
119
|
+
if (roots.length === 0) {
|
|
120
|
+
attempts.push({ source: `PATH:${command}`, candidate: commandPath, reason: "package root not found" });
|
|
121
|
+
}
|
|
122
|
+
for (const candidate of roots) {
|
|
123
|
+
const resolved = tryRoot(candidate, `PATH:${command} (${commandPath})`);
|
|
124
|
+
if (resolved) return { ...resolved, attempts };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { packageRoot: null, runtimePath: null, templateRoot: null, attempts };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatAgentInfraPackageError(result) {
|
|
133
|
+
const details = result.attempts.length > 0
|
|
134
|
+
? result.attempts.map(({ source, candidate, reason }) =>
|
|
135
|
+
` - ${source}${candidate ? `: ${candidate}` : ""}: ${reason}`
|
|
136
|
+
).join("\n")
|
|
137
|
+
: " - no package candidates were found";
|
|
138
|
+
return [
|
|
139
|
+
"Unable to locate a trusted @fitlab-ai/agent-infra installation.",
|
|
140
|
+
"Attempted package locations:",
|
|
141
|
+
details,
|
|
142
|
+
"Install agent-infra persistently and ensure ai or agent-infra is on PATH:",
|
|
143
|
+
" npm install -g @fitlab-ai/agent-infra",
|
|
144
|
+
" brew install fitlab-ai/tap/agent-infra",
|
|
145
|
+
"A one-time npx invocation is only suitable for bootstrap; install before update or validation."
|
|
146
|
+
].join("\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { formatAgentInfraPackageError, resolveAgentInfraPackage };
|
|
@@ -47,6 +47,11 @@ export function extractReviewDiffFingerprint(content) {
|
|
|
47
47
|
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export function extractReviewedSnapshotTree(content) {
|
|
51
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查快照树|Reviewed Snapshot Tree)\*\*[::]\s*(.*?)\s*$/m);
|
|
52
|
+
return match ? match[1].trim().replace(/`/g, "") : "";
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
export function parseReviewVerdict(content) {
|
|
51
56
|
const match = String(content).match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.*?)\s*$/m);
|
|
52
57
|
return match ? match[1].trim() : "";
|