@fitlab-ai/agent-infra 0.8.3 → 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/README.zh-CN.md +10 -0
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/lib/cp.js +56 -15
- package/dist/lib/defaults.json +9 -0
- package/dist/lib/init.js +5 -0
- package/dist/lib/sandbox/capture.js +5 -3
- package/dist/lib/sandbox/clipboard/bridge.js +12 -9
- package/dist/lib/sandbox/clipboard/inbox.js +82 -0
- package/dist/lib/sandbox/commands/create.js +68 -26
- package/dist/lib/sandbox/engine.js +33 -3
- package/dist/lib/sandbox/engines/colima.js +2 -2
- package/dist/lib/sandbox/runtimes/base.dockerfile +7 -0
- package/dist/lib/sandbox/shell.js +9 -1
- package/dist/lib/sandbox/tools.js +2 -2
- 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/cp.ts +56 -15
- package/lib/defaults.json +9 -0
- package/lib/init.ts +9 -0
- package/lib/sandbox/capture.ts +5 -3
- package/lib/sandbox/clipboard/bridge.ts +14 -12
- package/lib/sandbox/clipboard/inbox.ts +83 -0
- package/lib/sandbox/commands/create.ts +80 -27
- package/lib/sandbox/engine.ts +37 -3
- package/lib/sandbox/engines/colima.ts +2 -2
- package/lib/sandbox/runtimes/base.dockerfile +7 -0
- package/lib/sandbox/shell.ts +11 -2
- package/lib/sandbox/tools.ts +5 -5
- 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 +5 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +5 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +5 -4
- package/templates/.agents/rules/testing-discipline.en.md +3 -41
- package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
- 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 +144 -38
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +6 -4
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +35 -8
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +35 -8
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +27 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +27 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/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/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +5 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +6 -5
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +170 -45
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +203 -142
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
|
@@ -14,11 +14,17 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import childProcess from 'node:child_process';
|
|
17
|
+
import crypto from 'node:crypto';
|
|
17
18
|
import fs from 'node:fs';
|
|
18
19
|
import os from 'node:os';
|
|
19
20
|
import path from 'node:path';
|
|
20
21
|
import { fileURLToPath } from 'node:url';
|
|
21
22
|
|
|
23
|
+
import {
|
|
24
|
+
formatAgentInfraPackageError,
|
|
25
|
+
resolveAgentInfraPackage
|
|
26
|
+
} from '../../../scripts/lib/agent-infra-package.js';
|
|
27
|
+
|
|
22
28
|
const DEFAULTS = {
|
|
23
29
|
"platform": {
|
|
24
30
|
"type": "github"
|
|
@@ -65,14 +71,23 @@ const DEFAULTS = {
|
|
|
65
71
|
".gemini/commands/",
|
|
66
72
|
".git-hooks/check-version-format.sh",
|
|
67
73
|
".github/scripts/",
|
|
74
|
+
".github/workflows/metadata-sync.yml",
|
|
75
|
+
".github/workflows/pr-label.yml",
|
|
76
|
+
".github/workflows/status-label.yml",
|
|
68
77
|
".opencode/commands/"
|
|
69
78
|
],
|
|
79
|
+
"guardedManaged": [
|
|
80
|
+
".github/workflows/metadata-sync.yml",
|
|
81
|
+
".github/workflows/pr-label.yml",
|
|
82
|
+
".github/workflows/status-label.yml"
|
|
83
|
+
],
|
|
70
84
|
"merged": [
|
|
71
85
|
"**/post-release.*",
|
|
72
86
|
"**/release.*",
|
|
73
87
|
"**/test-integration.*",
|
|
74
88
|
"**/test.*",
|
|
75
89
|
"**/upgrade-dependency.*",
|
|
90
|
+
".agents/rules/testing-discipline.*",
|
|
76
91
|
".agents/skills/post-release/SKILL.*",
|
|
77
92
|
".agents/skills/release/SKILL.*",
|
|
78
93
|
".agents/skills/test-integration/SKILL.*",
|
|
@@ -87,7 +102,6 @@ const DEFAULTS = {
|
|
|
87
102
|
}
|
|
88
103
|
};
|
|
89
104
|
|
|
90
|
-
const PACKAGE_NAME = '@fitlab-ai/agent-infra';
|
|
91
105
|
const AGENT_INFRA_SANDBOX_TOOL = 'agent-infra';
|
|
92
106
|
const LEGACY_DEFAULT_SANDBOX_TOOLS = ['claude-code', 'codex', 'gemini-cli', 'opencode'];
|
|
93
107
|
const DEFAULT_SANDBOX_TOOLS = [AGENT_INFRA_SANDBOX_TOOL, ...LEGACY_DEFAULT_SANDBOX_TOOLS];
|
|
@@ -150,6 +164,14 @@ function migrateSandboxTools(cfg) {
|
|
|
150
164
|
|
|
151
165
|
function norm(p) { return p.replace(/\\/g, '/'); }
|
|
152
166
|
|
|
167
|
+
function sha256(content) {
|
|
168
|
+
return `sha256:${crypto.createHash('sha256').update(content).digest('hex')}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function trustedBaseline(value) {
|
|
172
|
+
return typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value) ? value : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
153
175
|
function normDir(p) {
|
|
154
176
|
return norm(p).replace(/^\.\//, '').replace(/\/+$/, '');
|
|
155
177
|
}
|
|
@@ -235,7 +257,7 @@ function parseSkillFrontmatter(filePath) {
|
|
|
235
257
|
if (!pair) continue;
|
|
236
258
|
|
|
237
259
|
const [, key, rawValue] = pair;
|
|
238
|
-
if (rawValue === '>') {
|
|
260
|
+
if (rawValue === '>' || rawValue === '|') {
|
|
239
261
|
const block = [];
|
|
240
262
|
for (let offset = index + 1; offset < lines.length; offset += 1) {
|
|
241
263
|
const nextLine = lines[offset];
|
|
@@ -244,7 +266,7 @@ function parseSkillFrontmatter(filePath) {
|
|
|
244
266
|
block.push(nextLine.trim());
|
|
245
267
|
index = offset;
|
|
246
268
|
}
|
|
247
|
-
result[key] = block.join(' ').trim();
|
|
269
|
+
result[key] = block.join(rawValue === '|' ? '\n' : ' ').trim();
|
|
248
270
|
continue;
|
|
249
271
|
}
|
|
250
272
|
|
|
@@ -286,7 +308,8 @@ function detectCustomSkills(projectRoot, templateSkillNames) {
|
|
|
286
308
|
dirName: entry.name,
|
|
287
309
|
name: meta.name || entry.name,
|
|
288
310
|
description: meta.description || '',
|
|
289
|
-
args: meta.args || null
|
|
311
|
+
args: meta.args || null,
|
|
312
|
+
disableModelInvocation: meta['disable-model-invocation'] === 'true'
|
|
290
313
|
};
|
|
291
314
|
})
|
|
292
315
|
.filter(Boolean)
|
|
@@ -501,14 +524,35 @@ function cleanStaleSyncedFiles(projectRoot, syncedSkills, report) {
|
|
|
501
524
|
}
|
|
502
525
|
}
|
|
503
526
|
|
|
527
|
+
function formatYamlMetadata(key, value) {
|
|
528
|
+
if (!value.includes('\n')) {
|
|
529
|
+
return [`${key}: ${JSON.stringify(value)}`];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return [`${key}: |-`, ...value.split('\n').map((line) => ` ${line}`)];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function formatTomlMetadata(key, value) {
|
|
536
|
+
if (!value.includes('\n')) {
|
|
537
|
+
return `${key} = ${JSON.stringify(value)}`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const lines = value.split('\n').map((line) => JSON.stringify(line).slice(1, -1));
|
|
541
|
+
return `${key} = """${lines.join('\n')}"""`;
|
|
542
|
+
}
|
|
543
|
+
|
|
504
544
|
function generateClaudeCommand(skill, lang) {
|
|
505
545
|
const isZhCN = lang === 'zh-CN';
|
|
506
|
-
const lines = ['---',
|
|
546
|
+
const lines = ['---', ...formatYamlMetadata('description', skill.description)];
|
|
507
547
|
|
|
508
548
|
if (skill.args) {
|
|
509
549
|
lines.push(`usage: ${JSON.stringify(`/${skill.dirName} ${skill.args}`)}`);
|
|
510
550
|
}
|
|
511
551
|
|
|
552
|
+
if (skill.disableModelInvocation) {
|
|
553
|
+
lines.push('disable-model-invocation: true');
|
|
554
|
+
}
|
|
555
|
+
|
|
512
556
|
lines.push('---', '');
|
|
513
557
|
lines.push(
|
|
514
558
|
isZhCN
|
|
@@ -539,7 +583,7 @@ function generateGeminiCommand(skill, lang) {
|
|
|
539
583
|
promptLines.push(isZhCN ? '严格按照技能中定义的所有步骤执行。' : 'Follow all steps defined in the skill exactly.');
|
|
540
584
|
|
|
541
585
|
return [
|
|
542
|
-
|
|
586
|
+
formatTomlMetadata('description', skill.description),
|
|
543
587
|
'prompt = """',
|
|
544
588
|
...promptLines,
|
|
545
589
|
'"""'
|
|
@@ -550,7 +594,7 @@ function generateOpenCodeCommand(skill, lang) {
|
|
|
550
594
|
const isZhCN = lang === 'zh-CN';
|
|
551
595
|
const lines = [
|
|
552
596
|
'---',
|
|
553
|
-
|
|
597
|
+
...formatYamlMetadata('description', skill.description),
|
|
554
598
|
'agent: general',
|
|
555
599
|
'subtask: false',
|
|
556
600
|
'---',
|
|
@@ -816,117 +860,6 @@ function stripLangVariant(relativePath) {
|
|
|
816
860
|
return relativePath;
|
|
817
861
|
}
|
|
818
862
|
|
|
819
|
-
function isTemplateDir(dir) {
|
|
820
|
-
try {
|
|
821
|
-
return fs.statSync(dir).isDirectory();
|
|
822
|
-
} catch {
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function verifyPackageDir(dir) {
|
|
828
|
-
const pkgPath = path.join(dir, 'package.json');
|
|
829
|
-
if (!fs.existsSync(pkgPath)) {
|
|
830
|
-
return { templateRoot: null, reason: `package.json not found at ${pkgPath}` };
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
let pkg;
|
|
834
|
-
try {
|
|
835
|
-
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
836
|
-
} catch {
|
|
837
|
-
return { templateRoot: null, reason: `invalid package.json at ${pkgPath}` };
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
if (pkg.name !== PACKAGE_NAME) {
|
|
841
|
-
const packageName = typeof pkg.name === 'string' && pkg.name ? pkg.name : 'an unknown package';
|
|
842
|
-
return { templateRoot: null, reason: `${pkgPath} belongs to ${packageName}` };
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
const templateRoot = path.join(dir, 'templates');
|
|
846
|
-
if (!isTemplateDir(templateRoot)) {
|
|
847
|
-
return { templateRoot: null, reason: `templates/ not found at ${templateRoot}` };
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return { templateRoot, reason: null };
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function resolveUnixTemplateRoot(name) {
|
|
854
|
-
let linkPath;
|
|
855
|
-
try {
|
|
856
|
-
linkPath = childProcess.execSync(`command -v ${name}`, {
|
|
857
|
-
encoding: 'utf8',
|
|
858
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
859
|
-
}).trim();
|
|
860
|
-
} catch {
|
|
861
|
-
return { templateRoot: null, reason: 'not found in PATH' };
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
if (!linkPath) {
|
|
865
|
-
return { templateRoot: null, reason: 'not found in PATH' };
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
let realPath;
|
|
869
|
-
try {
|
|
870
|
-
realPath = fs.realpathSync(linkPath);
|
|
871
|
-
} catch {
|
|
872
|
-
return { templateRoot: null, reason: `cannot resolve symlink target for ${linkPath}` };
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
let dir = path.dirname(realPath);
|
|
876
|
-
while (true) {
|
|
877
|
-
const pkgPath = path.join(dir, 'package.json');
|
|
878
|
-
if (fs.existsSync(pkgPath)) {
|
|
879
|
-
return verifyPackageDir(dir);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
const parentDir = path.dirname(dir);
|
|
883
|
-
if (parentDir === dir) {
|
|
884
|
-
break;
|
|
885
|
-
}
|
|
886
|
-
dir = parentDir;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
return { templateRoot: null, reason: `no package.json found above ${realPath}` };
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function resolveWindowsTemplateRoot(name) {
|
|
893
|
-
let output;
|
|
894
|
-
try {
|
|
895
|
-
output = childProcess.execSync(`where ${name}`, {
|
|
896
|
-
encoding: 'utf8',
|
|
897
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
898
|
-
}).trim();
|
|
899
|
-
} catch {
|
|
900
|
-
return { templateRoot: null, reason: 'not found in PATH' };
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const wrapperPaths = output.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
904
|
-
if (wrapperPaths.length === 0) {
|
|
905
|
-
return { templateRoot: null, reason: 'not found in PATH' };
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
const wrapperPath = wrapperPaths.find(line => /\.cmd$/i.test(line)) || wrapperPaths[0];
|
|
909
|
-
const packageDir = path.join(path.dirname(wrapperPath), 'node_modules', '@fitlab-ai', 'agent-infra');
|
|
910
|
-
return verifyPackageDir(packageDir);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
function resolveTemplateRoot() {
|
|
914
|
-
const resolver = process.platform === 'win32'
|
|
915
|
-
? resolveWindowsTemplateRoot
|
|
916
|
-
: resolveUnixTemplateRoot;
|
|
917
|
-
const errors = [];
|
|
918
|
-
|
|
919
|
-
for (const name of ['ai', 'agent-infra']) {
|
|
920
|
-
const result = resolver(name);
|
|
921
|
-
if (result.templateRoot) {
|
|
922
|
-
return result.templateRoot;
|
|
923
|
-
}
|
|
924
|
-
errors.push({ name, reason: result.reason });
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
return { templateRoot: null, errors };
|
|
928
|
-
}
|
|
929
|
-
|
|
930
863
|
function isBinary(fp) {
|
|
931
864
|
const fd = fs.openSync(fp, 'r');
|
|
932
865
|
const buf = Buffer.alloc(8192);
|
|
@@ -1014,25 +947,14 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1014
947
|
const configPathRel = norm(path.relative(projectRoot, cfgPath));
|
|
1015
948
|
let templateRoot = templateRootOverride;
|
|
1016
949
|
if (!templateRoot) {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
950
|
+
const packageResolution = resolveAgentInfraPackage({
|
|
951
|
+
startPath: path.join(projectRoot, '.agents', 'scripts', 'lib', 'agent-infra-package.js')
|
|
952
|
+
});
|
|
953
|
+
if (packageResolution.templateRoot && fs.existsSync(packageResolution.templateRoot)) {
|
|
954
|
+
templateRoot = packageResolution.templateRoot;
|
|
1020
955
|
} else {
|
|
1021
|
-
const details = resolvedTemplateRoot.errors
|
|
1022
|
-
.map(({ name, reason }) => ` - ${name}: ${reason}`)
|
|
1023
|
-
.join('\n');
|
|
1024
956
|
return {
|
|
1025
|
-
error:
|
|
1026
|
-
'Template source not found.',
|
|
1027
|
-
'',
|
|
1028
|
-
'Attempted binary lookups:',
|
|
1029
|
-
details,
|
|
1030
|
-
'',
|
|
1031
|
-
'Please ensure agent-infra is installed and available on PATH.',
|
|
1032
|
-
'If already installed, upgrade to the latest version or reinstall:',
|
|
1033
|
-
' npm install -g @fitlab-ai/agent-infra',
|
|
1034
|
-
' brew upgrade fitlab-ai/agent-infra/agent-infra || brew install fitlab-ai/agent-infra/agent-infra'
|
|
1035
|
-
].join('\n')
|
|
957
|
+
error: formatAgentInfraPackageError(packageResolution)
|
|
1036
958
|
};
|
|
1037
959
|
}
|
|
1038
960
|
}
|
|
@@ -1050,6 +972,19 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1050
972
|
const managed = [...(cfg.files.managed || [])];
|
|
1051
973
|
const merged = [...(cfg.files.merged || [])];
|
|
1052
974
|
const ejected = [...(cfg.files.ejected || [])];
|
|
975
|
+
const guardedManaged = new Set((DEFAULTS.files.guardedManaged || []).map(norm));
|
|
976
|
+
const managedBaselines = cfg.files.managedBaselines && typeof cfg.files.managedBaselines === 'object'
|
|
977
|
+
&& !Array.isArray(cfg.files.managedBaselines)
|
|
978
|
+
? { ...cfg.files.managedBaselines }
|
|
979
|
+
: {};
|
|
980
|
+
let baselinesChanged = false;
|
|
981
|
+
|
|
982
|
+
for (const target of Object.keys(managedBaselines)) {
|
|
983
|
+
if (!guardedManaged.has(norm(target))) {
|
|
984
|
+
delete managedBaselines[target];
|
|
985
|
+
baselinesChanged = true;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
1053
988
|
|
|
1054
989
|
const report = {
|
|
1055
990
|
templateVersion: version,
|
|
@@ -1062,7 +997,17 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1062
997
|
errors: [],
|
|
1063
998
|
conflicts: []
|
|
1064
999
|
},
|
|
1065
|
-
managed: {
|
|
1000
|
+
managed: {
|
|
1001
|
+
written: [],
|
|
1002
|
+
created: [],
|
|
1003
|
+
unchanged: [],
|
|
1004
|
+
protected: [],
|
|
1005
|
+
conflicts: [],
|
|
1006
|
+
skippedMerged: [],
|
|
1007
|
+
skippedPlatform: [],
|
|
1008
|
+
skippedTUI: [],
|
|
1009
|
+
removed: []
|
|
1010
|
+
},
|
|
1066
1011
|
custom: {
|
|
1067
1012
|
detected: [],
|
|
1068
1013
|
generated: [],
|
|
@@ -1104,6 +1049,22 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1104
1049
|
const allRels = mergedRels;
|
|
1105
1050
|
const allSet = new Set(allRels);
|
|
1106
1051
|
|
|
1052
|
+
function renderedTemplate(entry) {
|
|
1053
|
+
const target = norm(renderPathname(entry, project));
|
|
1054
|
+
const selected = platformSelect(
|
|
1055
|
+
langSelect(entryVariantRels(entry, allSet, platformType), lang, allSet, project),
|
|
1056
|
+
platformType,
|
|
1057
|
+
project
|
|
1058
|
+
);
|
|
1059
|
+
const src = selected.get(target);
|
|
1060
|
+
if (!src) return null;
|
|
1061
|
+
const srcRoot = sourceMap.get(src) || templateRoot;
|
|
1062
|
+
const srcFull = path.join(srcRoot, src);
|
|
1063
|
+
return isBinary(srcFull)
|
|
1064
|
+
? fs.readFileSync(srcFull)
|
|
1065
|
+
: renderContent(fs.readFileSync(srcFull, 'utf8'), vars);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1107
1068
|
for (const entry of [...managed, ...merged, ...ejected]) {
|
|
1108
1069
|
if (!isPathOwnedByOtherPlatform(entry, platformType)) continue;
|
|
1109
1070
|
|
|
@@ -1112,17 +1073,49 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1112
1073
|
if (!fs.existsSync(dir)) continue;
|
|
1113
1074
|
|
|
1114
1075
|
for (const filePath of walkDir(dir)) {
|
|
1076
|
+
const relativeFile = norm(path.relative(projectRoot, filePath));
|
|
1077
|
+
if (guardedManaged.has(relativeFile)) continue;
|
|
1115
1078
|
fs.unlinkSync(filePath);
|
|
1116
|
-
report.managed.removed.push(
|
|
1079
|
+
report.managed.removed.push(relativeFile);
|
|
1117
1080
|
}
|
|
1118
1081
|
removeEmptyDirs(dir);
|
|
1119
1082
|
continue;
|
|
1120
1083
|
}
|
|
1121
1084
|
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1085
|
+
const renderedTarget = norm(renderPathname(entry, project));
|
|
1086
|
+
const target = path.join(projectRoot, renderedTarget);
|
|
1087
|
+
if (!guardedManaged.has(renderedTarget)) {
|
|
1088
|
+
if (!fs.existsSync(target)) continue;
|
|
1089
|
+
fs.unlinkSync(target);
|
|
1090
|
+
report.managed.removed.push(renderedTarget);
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const baseline = trustedBaseline(managedBaselines[renderedTarget]);
|
|
1095
|
+
const templateContent = renderedTemplate(entry);
|
|
1096
|
+
const templateHash = templateContent === null ? null : sha256(templateContent);
|
|
1097
|
+
const localHash = fs.existsSync(target) ? sha256(fs.readFileSync(target)) : null;
|
|
1098
|
+
const safeToRemove = localHash !== null && (localHash === baseline || (!baseline && localHash === templateHash));
|
|
1099
|
+
|
|
1100
|
+
if (safeToRemove) {
|
|
1101
|
+
fs.unlinkSync(target);
|
|
1102
|
+
report.managed.removed.push(renderedTarget);
|
|
1103
|
+
if (Object.prototype.hasOwnProperty.call(managedBaselines, renderedTarget)) {
|
|
1104
|
+
delete managedBaselines[renderedTarget];
|
|
1105
|
+
baselinesChanged = true;
|
|
1106
|
+
}
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (localHash !== null || baseline !== null) {
|
|
1111
|
+
report.managed.conflicts.push({
|
|
1112
|
+
target: renderedTarget,
|
|
1113
|
+
reason: localHash === null ? 'platform-switch-deleted' : 'platform-switch-modified',
|
|
1114
|
+
baseline,
|
|
1115
|
+
local: localHash,
|
|
1116
|
+
template: templateHash
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1126
1119
|
}
|
|
1127
1120
|
|
|
1128
1121
|
// Cleanup files owned by disabled built-in TUIs. Iterates managed + merged
|
|
@@ -1207,6 +1200,68 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1207
1200
|
: renderContent(fs.readFileSync(srcFull, 'utf8'), vars);
|
|
1208
1201
|
|
|
1209
1202
|
const exists = fs.existsSync(dstFull);
|
|
1203
|
+
if (guardedManaged.has(tgt)) {
|
|
1204
|
+
const rawBaseline = managedBaselines[tgt];
|
|
1205
|
+
const baseline = trustedBaseline(rawBaseline);
|
|
1206
|
+
const templateHash = sha256(content);
|
|
1207
|
+
const localHash = exists ? sha256(fs.readFileSync(dstFull)) : null;
|
|
1208
|
+
|
|
1209
|
+
if (rawBaseline !== undefined && baseline === null) {
|
|
1210
|
+
delete managedBaselines[tgt];
|
|
1211
|
+
baselinesChanged = true;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (baseline === null && localHash !== null && localHash !== templateHash) {
|
|
1215
|
+
report.managed.conflicts.push({
|
|
1216
|
+
target: tgt,
|
|
1217
|
+
reason: 'unknown-origin',
|
|
1218
|
+
baseline: null,
|
|
1219
|
+
local: localHash,
|
|
1220
|
+
template: templateHash
|
|
1221
|
+
});
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (baseline !== null && templateHash === baseline && localHash !== baseline) {
|
|
1226
|
+
report.managed.protected.push({
|
|
1227
|
+
target: tgt,
|
|
1228
|
+
reason: localHash === null ? 'user-deleted' : 'user-modified',
|
|
1229
|
+
baseline,
|
|
1230
|
+
local: localHash,
|
|
1231
|
+
template: templateHash
|
|
1232
|
+
});
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
if (baseline !== null && localHash !== baseline && templateHash !== baseline && localHash !== templateHash) {
|
|
1237
|
+
report.managed.conflicts.push({
|
|
1238
|
+
target: tgt,
|
|
1239
|
+
reason: 'both-modified',
|
|
1240
|
+
baseline,
|
|
1241
|
+
local: localHash,
|
|
1242
|
+
template: templateHash
|
|
1243
|
+
});
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (localHash === templateHash) {
|
|
1248
|
+
report.managed.unchanged.push(tgt);
|
|
1249
|
+
if (managedBaselines[tgt] !== templateHash) {
|
|
1250
|
+
managedBaselines[tgt] = templateHash;
|
|
1251
|
+
baselinesChanged = true;
|
|
1252
|
+
}
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const dir = path.dirname(dstFull);
|
|
1257
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1258
|
+
fs.writeFileSync(dstFull, content);
|
|
1259
|
+
managedBaselines[tgt] = templateHash;
|
|
1260
|
+
baselinesChanged = true;
|
|
1261
|
+
(exists ? report.managed.written : report.managed.created).push(tgt);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1210
1265
|
if (exists) {
|
|
1211
1266
|
const cur = bin ? fs.readFileSync(dstFull) : fs.readFileSync(dstFull, 'utf8');
|
|
1212
1267
|
if (bin ? content.equals(cur) : content === cur) {
|
|
@@ -1332,10 +1387,16 @@ function syncTemplates(projectRoot, templateRootOverride) {
|
|
|
1332
1387
|
cfg.files.managed = managed;
|
|
1333
1388
|
cfg.files.merged = merged;
|
|
1334
1389
|
cfg.files.ejected = ejected;
|
|
1390
|
+
if (Object.keys(managedBaselines).length > 0) {
|
|
1391
|
+
cfg.files.managedBaselines = managedBaselines;
|
|
1392
|
+
} else {
|
|
1393
|
+
if (Object.prototype.hasOwnProperty.call(cfg.files, 'managedBaselines')) baselinesChanged = true;
|
|
1394
|
+
delete cfg.files.managedBaselines;
|
|
1395
|
+
}
|
|
1335
1396
|
cfg.templateVersion = version;
|
|
1336
1397
|
delete cfg.templateSource;
|
|
1337
1398
|
|
|
1338
|
-
report.configUpdated = hasChanges || prevVersion !== version || hadTemplateSource || sandboxToolsMigrated;
|
|
1399
|
+
report.configUpdated = hasChanges || baselinesChanged || prevVersion !== version || hadTemplateSource || sandboxToolsMigrated;
|
|
1339
1400
|
|
|
1340
1401
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
1341
1402
|
|
|
@@ -67,7 +67,7 @@ When self-heal hits the cap, the failure is non-code, the run id is unlocatable,
|
|
|
67
67
|
Get the current time:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
70
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Update `.agents/workspace/active/{task-id}/task.md`:
|
|
@@ -109,7 +109,7 @@ Output per scenario:
|
|
|
109
109
|
```
|
|
110
110
|
Next step - Complete and archive the task:
|
|
111
111
|
- Claude Code / OpenCode: /complete-task {task-ref}
|
|
112
|
-
- Gemini CLI: /
|
|
112
|
+
- Gemini CLI: /{{project}}:complete-task {task-ref}
|
|
113
113
|
- Codex CLI: $complete-task {task-ref}
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -67,7 +67,7 @@ description: >
|
|
|
67
67
|
获取当前时间:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
date "+%Y-%m-%d %H:%M:%S
|
|
70
|
+
date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
更新 `.agents/workspace/active/{task-id}/task.md`:
|
|
@@ -109,7 +109,7 @@ node .agents/scripts/validate-artifact.js gate watch-pr .agents/workspace/active
|
|
|
109
109
|
```
|
|
110
110
|
下一步 - 完成并归档任务:
|
|
111
111
|
- Claude Code / OpenCode:/complete-task {task-ref}
|
|
112
|
-
- Gemini CLI:/
|
|
112
|
+
- Gemini CLI:/{{project}}:complete-task {task-ref}
|
|
113
113
|
- Codex CLI:$complete-task {task-ref}
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -12,6 +12,13 @@ After running the watch command from `.agents/rules/pr-checks-commands.md`, clas
|
|
|
12
12
|
|
|
13
13
|
## Self-Heal Decision Tree
|
|
14
14
|
|
|
15
|
+
```text
|
|
16
|
+
# self-heal-test-command-contract
|
|
17
|
+
primary: failing-job-command
|
|
18
|
+
fallback-source: project-test-skill
|
|
19
|
+
unknown: help
|
|
20
|
+
```
|
|
21
|
+
|
|
15
22
|
For each failing check, decide "self-heal" vs "ask for help" in this order:
|
|
16
23
|
|
|
17
24
|
1. **Can the corresponding CI run be located** (per the rule's "Resolve a failing run id")? No → ask for help.
|
|
@@ -22,7 +29,7 @@ For each failing check, decide "self-heal" vs "ask for help" in this order:
|
|
|
22
29
|
4. When "locatable + code layer + under cap" holds, perform one self-heal:
|
|
23
30
|
- Before fixing, run `git status -s` to record the working tree and ensure only changes related to this failure are included.
|
|
24
31
|
- Locate and make a minimal fix per the logs (touch only code / tests / config related to that failure).
|
|
25
|
-
- Run the relevant tests: prefer the local command for the failing job;
|
|
32
|
+
- Run the relevant tests: prefer the local command for the failing job; otherwise read the project `test` skill and select its declared core or full validation command. If neither command is known, ask for help. **Do not commit or push before tests pass.**
|
|
26
33
|
- After tests pass, publish the fix: per `.agents/rules/commit-and-pr.md` stage only the related files (`git add <paths>`, avoid `git add -A` pulling in unrelated changes) → create the fix commit (`git commit`) → `git push` to the current PR branch.
|
|
27
34
|
- Record the fix commit SHA, increment the fix count, and return to SKILL step 2 to re-watch.
|
|
28
35
|
- Never make unrelated "drive-by" optimizations; never loosen / skip the failing assertion to "make it green".
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
## 自愈决策树
|
|
14
14
|
|
|
15
|
+
```text
|
|
16
|
+
# self-heal-test-command-contract
|
|
17
|
+
primary: failing-job-command
|
|
18
|
+
fallback-source: project-test-skill
|
|
19
|
+
unknown: help
|
|
20
|
+
```
|
|
21
|
+
|
|
15
22
|
对每个失败 check,按下列顺序判定「自愈」还是「求助」:
|
|
16
23
|
|
|
17
24
|
1. **能否定位到对应的 CI run**(按规则的「解析失败 run id」)?否 → 求助。
|
|
@@ -22,7 +29,7 @@
|
|
|
22
29
|
4. 满足「可定位 + 代码层 + 未达上限」时执行一次自愈:
|
|
23
30
|
- 自愈前先 `git status -s` 记录当前工作树,确保后续只纳入与本次失败相关的改动。
|
|
24
31
|
- 在本地按日志定位并最小化修复(只动与该失败相关的代码 / 测试 / 配置)。
|
|
25
|
-
- 运行对应测试:优先失败 job
|
|
32
|
+
- 运行对应测试:优先失败 job 对应的本地命令;否则读取项目 `test` skill,选择其中声明的 core 或 full 验证命令。两者均未知时进入求助出口。**测试通过前不得提交或推送。**
|
|
26
33
|
- 测试通过后发布修复:按 `.agents/rules/commit-and-pr.md` 仅暂存本次相关文件(`git add <相关路径>`,避免 `git add -A` 卷入无关改动)→ 创建修复 commit(`git commit`)→ `git push` 到当前 PR 分支。
|
|
27
34
|
- 记录本次修复的 commit SHA,修复计数 +1,回到 SKILL 步骤 2 重新监控。
|
|
28
35
|
- 绝不执行与失败无关的「顺手优化」;不放宽 / 跳过失败的断言来「修绿」。
|