ai-dev-harness 0.1.0 → 0.1.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.
- package/COLLEAGUE_TRIAL_GUIDE.md +147 -0
- package/README.md +116 -52
- package/dist/agent.js +44 -31
- package/dist/cli.js +65 -42
- package/dist/config.js +79 -8
- package/dist/context.js +53 -21
- package/dist/init.js +8 -20
- package/dist/integration.js +22 -91
- package/dist/mcp.js +29 -112
- package/dist/runPaths.js +1 -0
- package/dist/summarize.js +42 -39
- package/dist/templates.js +39 -215
- package/package.json +3 -1
- package/scripts/read-evidence.ps1 +9 -3
- package/scripts/run-harness.ps1 +8 -5
- package/templates/default-project/AGENTS.md +9 -0
- package/templates/default-project/SKILLS.md +13 -0
- package/templates/default-project/TOOLS.md +8 -0
- package/templates/default-project/skills/add_feature/SKILL.md +34 -0
- package/templates/default-project/skills/add_feature/context.yaml +30 -0
- package/templates/default-project/skills/add_feature/verify.yaml +2 -0
- package/templates/default-project/skills/fix_bug/SKILL.md +45 -0
- package/templates/default-project/skills/fix_bug/context.yaml +30 -0
- package/templates/default-project/skills/fix_bug/verify.yaml +2 -0
- package/templates/default-project/skills/update_docs/SKILL.md +28 -0
- package/templates/default-project/skills/update_docs/context.yaml +26 -0
- package/templates/default-project/skills/update_docs/verify.yaml +2 -0
- package/templates/default-project/skills/write_tests/SKILL.md +30 -0
- package/templates/default-project/skills/write_tests/context.yaml +29 -0
- package/templates/default-project/skills/write_tests/verify.yaml +2 -0
- package/templates/integrations/claude/SKILL.md +79 -0
- package/templates/integrations/codex/AGENTS_BLOCK.md +51 -0
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { AuditLogger, prepareRunFiles, writeJson } from "./audit.js";
|
|
5
|
-
import { createAgentAdapter } from "./agent.js";
|
|
5
|
+
import { checkAgentContract, createAgentAdapter } from "./agent.js";
|
|
6
6
|
import { loadConfig, loadSkill } from "./config.js";
|
|
7
7
|
import { buildContextPack } from "./context.js";
|
|
8
8
|
import { initHarness } from "./init.js";
|
|
@@ -32,8 +32,9 @@ function parseArgs(argv) {
|
|
|
32
32
|
function usage() {
|
|
33
33
|
return `AI Dev Harness
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
用法:
|
|
36
36
|
harness init
|
|
37
|
+
harness init --no-integration
|
|
37
38
|
harness run --task <text> --skill <name> --agent <codex|claude>
|
|
38
39
|
harness run --task <text> --skill <name> --agent <codex|claude> --live
|
|
39
40
|
harness verify --run <run_id_or_path>
|
|
@@ -44,12 +45,12 @@ Usage:
|
|
|
44
45
|
function requireString(args, key) {
|
|
45
46
|
const value = args[key];
|
|
46
47
|
if (typeof value !== "string" || !value.trim())
|
|
47
|
-
throw new Error(
|
|
48
|
+
throw new Error(`缺少必填参数 --${key}`);
|
|
48
49
|
return value;
|
|
49
50
|
}
|
|
50
51
|
function requireAgent(value) {
|
|
51
52
|
if (value !== "codex" && value !== "claude")
|
|
52
|
-
throw new Error(
|
|
53
|
+
throw new Error(`无效 agent:${value}。期望 codex 或 claude。`);
|
|
53
54
|
return value;
|
|
54
55
|
}
|
|
55
56
|
function quoteCommandPath(filePath) {
|
|
@@ -63,12 +64,24 @@ async function resolveRunDir(cwd, runArg) {
|
|
|
63
64
|
const underAudit = path.join(cwd, config.audit.path, runArg);
|
|
64
65
|
if (await pathExists(underAudit))
|
|
65
66
|
return underAudit;
|
|
66
|
-
throw new Error(
|
|
67
|
+
throw new Error(`未找到运行记录:${runArg}`);
|
|
67
68
|
}
|
|
68
69
|
async function commandInit(cwd) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
console.log(`
|
|
70
|
+
const packageRoot = path.resolve(path.dirname(process.argv[1]), "..");
|
|
71
|
+
const result = await initHarness(cwd, packageRoot);
|
|
72
|
+
console.log(`AI Dev Harness 初始化完成:${cwd}`);
|
|
73
|
+
console.log(`创建 ${result.created.length} 个文件,跳过 ${result.skipped.length} 个已存在文件。`);
|
|
74
|
+
if (process.argv.includes("--no-integration")) {
|
|
75
|
+
console.log("已跳过 Claude/Codex 对话集成。");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const harnessCliPath = path.resolve(process.argv[1]);
|
|
79
|
+
const harnessCommand = `node ${quoteCommandPath(harnessCliPath)}`;
|
|
80
|
+
const integration = await installIntegration({ targetDir: cwd, harnessCommand, harnessCliPath, agent: "both", packageRoot });
|
|
81
|
+
console.log("Claude/Codex 对话集成已安装。");
|
|
82
|
+
console.log(`集成创建:${integration.created.length ? integration.created.join(", ") : "无"}`);
|
|
83
|
+
console.log(`集成更新:${integration.updated.length ? integration.updated.join(", ") : "无"}`);
|
|
84
|
+
console.log(`集成跳过:${integration.skipped.length ? integration.skipped.join(", ") : "无"}`);
|
|
72
85
|
}
|
|
73
86
|
function sayLive(live, message) {
|
|
74
87
|
if (live)
|
|
@@ -84,8 +97,8 @@ async function commandRun(cwd, args) {
|
|
|
84
97
|
const paths = makeRunPaths(cwd, config, task);
|
|
85
98
|
await prepareRunFiles(paths);
|
|
86
99
|
const audit = new AuditLogger(paths.audit);
|
|
87
|
-
sayLive(live,
|
|
88
|
-
sayLive(live,
|
|
100
|
+
sayLive(live, `运行开始:skill=${skillName} agent=${agentName}`);
|
|
101
|
+
sayLive(live, `证据目录:${paths.runDir}`);
|
|
89
102
|
await audit.event({ stage: "intake", event: "run_started", status: "started", detail: { task, skill: skillName, agent: agentName } });
|
|
90
103
|
const isGitRepo = Boolean(await git(cwd, "rev-parse --show-toplevel"));
|
|
91
104
|
const intake = {
|
|
@@ -103,12 +116,12 @@ async function commandRun(cwd, args) {
|
|
|
103
116
|
stage: "intake",
|
|
104
117
|
event: "git_repo_check",
|
|
105
118
|
status: "failed",
|
|
106
|
-
detail: "
|
|
119
|
+
detail: "当前目录不是 Git 仓库。请在业务 Git 仓库内执行 harness run。"
|
|
107
120
|
});
|
|
108
121
|
await audit.event({ stage: "run", event: "run_finished", status: "failed" });
|
|
109
|
-
throw new Error(`harness run
|
|
122
|
+
throw new Error(`harness run 必须在 Git 仓库内执行。证据目录:${paths.runDir}`);
|
|
110
123
|
}
|
|
111
|
-
sayLive(live, "
|
|
124
|
+
sayLive(live, "正在构建 Context Pack:包含 Agent MCP 指引和本地兜底上下文");
|
|
112
125
|
await audit.event({ stage: "context_pack", event: "build_started", status: "started" });
|
|
113
126
|
const contextPack = await buildContextPack({
|
|
114
127
|
cwd,
|
|
@@ -122,24 +135,25 @@ async function commandRun(cwd, args) {
|
|
|
122
135
|
await audit.event({
|
|
123
136
|
stage: "context_pack",
|
|
124
137
|
event: "build_finished",
|
|
125
|
-
status:
|
|
138
|
+
status: "succeeded"
|
|
126
139
|
});
|
|
127
|
-
sayLive(live, `
|
|
128
|
-
const plan = `#
|
|
140
|
+
sayLive(live, `Context Pack 已生成:${paths.contextPack}`);
|
|
141
|
+
const plan = `# 执行阶段协议
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
1.
|
|
132
|
-
2.
|
|
133
|
-
3.
|
|
134
|
-
4.
|
|
135
|
-
5.
|
|
143
|
+
Agent 必须:
|
|
144
|
+
1. 阅读 context_pack.md。
|
|
145
|
+
2. 按固定格式输出影响分析。
|
|
146
|
+
3. 制定最小修改计划。
|
|
147
|
+
4. 执行最小安全变更。
|
|
148
|
+
5. 说明验证计划和残余风险。
|
|
149
|
+
6. 让 Harness 运行验证并生成总结。
|
|
136
150
|
`;
|
|
137
151
|
await writeFile(paths.plan, plan, "utf8");
|
|
138
152
|
await audit.event({ stage: "plan", event: "plan_written", status: "succeeded", detail: { path: paths.plan } });
|
|
139
|
-
sayLive(live,
|
|
153
|
+
sayLive(live, `阶段协议已写入:${paths.plan}`);
|
|
140
154
|
const adapter = createAgentAdapter(agentName, config);
|
|
141
155
|
const agentCommand = agentName === "codex" ? config.agents.codex.command : config.agents.claude.command;
|
|
142
|
-
sayLive(live,
|
|
156
|
+
sayLive(live, `正在执行 ${adapter.name}:${agentCommand}`);
|
|
143
157
|
await audit.event({ stage: "execute", event: "agent_started", status: "started", detail: { agent: adapter.name, command: agentCommand } });
|
|
144
158
|
let agentResult;
|
|
145
159
|
try {
|
|
@@ -172,12 +186,20 @@ The selected agent must:
|
|
|
172
186
|
await audit.event({ stage: "execute", event: "agent_error", status: "failed", detail: agentResult.error });
|
|
173
187
|
}
|
|
174
188
|
await writeFile(paths.gitDiff, agentResult.diff, "utf8");
|
|
175
|
-
|
|
176
|
-
|
|
189
|
+
const contractCheck = checkAgentContract(`${agentResult.stdout}\n${agentResult.stderr}`);
|
|
190
|
+
await writeJson(paths.agentContract, contractCheck);
|
|
191
|
+
await audit.event({
|
|
192
|
+
stage: "execute",
|
|
193
|
+
event: "agent_contract_check",
|
|
194
|
+
status: contractCheck.status === "passed" ? "succeeded" : "failed",
|
|
195
|
+
detail: contractCheck
|
|
196
|
+
});
|
|
197
|
+
sayLive(live, `Agent 执行完成:status=${agentResult.status} changedFiles=${agentResult.changedFiles.length} contract=${contractCheck.status}`);
|
|
198
|
+
sayLive(live, "正在运行验证命令");
|
|
177
199
|
await audit.event({ stage: "verify", event: "verify_started", status: "started" });
|
|
178
200
|
const verifyResult = await runVerify(cwd, config, skill, paths.verify);
|
|
179
201
|
await audit.event({ stage: "verify", event: "verify_finished", status: verifyResult.status === "passed" ? "succeeded" : "failed", detail: verifyResult });
|
|
180
|
-
sayLive(live,
|
|
202
|
+
sayLive(live, `验证完成:${verifyResult.status} ${paths.verify}`);
|
|
181
203
|
await writeSummary({
|
|
182
204
|
task,
|
|
183
205
|
skillName,
|
|
@@ -190,28 +212,29 @@ The selected agent must:
|
|
|
190
212
|
});
|
|
191
213
|
await audit.event({ stage: "summarize", event: "summary_written", status: "succeeded" });
|
|
192
214
|
await audit.event({ stage: "persist", event: "suggestions_written", status: "succeeded" });
|
|
193
|
-
sayLive(live,
|
|
194
|
-
sayLive(live,
|
|
215
|
+
sayLive(live, `总结已写入:${paths.summary}`);
|
|
216
|
+
sayLive(live, `知识沉淀建议已写入:${paths.persistSuggestions}`);
|
|
195
217
|
const runPassed = verifyResult.status === "passed" && agentResult.status === "success";
|
|
196
218
|
await audit.event({ stage: "run", event: "run_finished", status: runPassed ? "succeeded" : "failed" });
|
|
197
|
-
console.log(
|
|
198
|
-
console.log(
|
|
219
|
+
console.log(`运行完成:${paths.runId}`);
|
|
220
|
+
console.log(`证据目录:${paths.runDir}`);
|
|
199
221
|
if (!runPassed)
|
|
200
|
-
throw new Error(`Harness
|
|
222
|
+
throw new Error(`Harness 运行失败。证据目录:${paths.runDir}`);
|
|
201
223
|
}
|
|
202
224
|
async function commandInstallIntegration(cwd, args) {
|
|
203
225
|
const rawAgent = String(args.agent ?? "both");
|
|
204
226
|
if (rawAgent !== "claude" && rawAgent !== "codex" && rawAgent !== "both") {
|
|
205
|
-
throw new Error(
|
|
227
|
+
throw new Error(`无效 --agent ${rawAgent}。期望 claude、codex 或 both。`);
|
|
206
228
|
}
|
|
207
229
|
const targetDir = typeof args.target === "string" ? path.resolve(cwd, args.target) : cwd;
|
|
208
230
|
const packageRoot = path.resolve(path.dirname(process.argv[1]), "..");
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
console.log(`
|
|
213
|
-
console.log(
|
|
214
|
-
console.log(
|
|
231
|
+
const harnessCliPath = path.resolve(process.argv[1]);
|
|
232
|
+
const harnessCommand = typeof args.command === "string" ? args.command : `node ${quoteCommandPath(harnessCliPath)}`;
|
|
233
|
+
const result = await installIntegration({ targetDir, harnessCommand, harnessCliPath, agent: rawAgent, packageRoot });
|
|
234
|
+
console.log(`AI Dev Harness 对话集成已安装:${targetDir}`);
|
|
235
|
+
console.log(`创建:${result.created.length ? result.created.join(", ") : "无"}`);
|
|
236
|
+
console.log(`更新:${result.updated.length ? result.updated.join(", ") : "无"}`);
|
|
237
|
+
console.log(`跳过:${result.skipped.length ? result.skipped.join(", ") : "无"}`);
|
|
215
238
|
}
|
|
216
239
|
async function commandVerify(cwd, args) {
|
|
217
240
|
const runDir = await resolveRunDir(cwd, requireString(args, "run"));
|
|
@@ -221,7 +244,7 @@ async function commandVerify(cwd, args) {
|
|
|
221
244
|
const result = await runVerify(cwd, config, skill, path.join(runDir, "verify.json"));
|
|
222
245
|
const audit = new AuditLogger(path.join(runDir, "audit.jsonl"));
|
|
223
246
|
await audit.event({ stage: "verify", event: "verify_rerun", status: result.status === "passed" ? "succeeded" : "failed", detail: result });
|
|
224
|
-
console.log(
|
|
247
|
+
console.log(`验证 ${result.status}:${runDir}`);
|
|
225
248
|
}
|
|
226
249
|
async function commandSummarize(cwd, args) {
|
|
227
250
|
const runDir = await resolveRunDir(cwd, requireString(args, "run"));
|
|
@@ -252,7 +275,7 @@ async function commandSummarize(cwd, args) {
|
|
|
252
275
|
});
|
|
253
276
|
const audit = new AuditLogger(path.join(runDir, "audit.jsonl"));
|
|
254
277
|
await audit.event({ stage: "summarize", event: "summary_rerun", status: "succeeded" });
|
|
255
|
-
console.log(
|
|
278
|
+
console.log(`总结已刷新:${runDir}`);
|
|
256
279
|
}
|
|
257
280
|
async function main() {
|
|
258
281
|
const cwd = process.cwd();
|
|
@@ -271,7 +294,7 @@ async function main() {
|
|
|
271
294
|
return commandSummarize(cwd, args);
|
|
272
295
|
if (command === "install-integration")
|
|
273
296
|
return commandInstallIntegration(cwd, args);
|
|
274
|
-
throw new Error(
|
|
297
|
+
throw new Error(`未知命令:${command}\n${usage()}`);
|
|
275
298
|
}
|
|
276
299
|
main().catch((error) => {
|
|
277
300
|
console.error(error instanceof Error ? error.message : String(error));
|
package/dist/config.js
CHANGED
|
@@ -7,8 +7,8 @@ export const defaultConfig = {
|
|
|
7
7
|
claude: { command: "claude -p -" }
|
|
8
8
|
},
|
|
9
9
|
mcp: {
|
|
10
|
-
codegraph: { url: "http://localhost:7331/mcp" },
|
|
11
|
-
knowledgeGraph: { url: "http://localhost:7332/mcp" },
|
|
10
|
+
codegraph: { type: "http", url: "http://localhost:7331/mcp", headers: {} },
|
|
11
|
+
knowledgeGraph: { type: "http", url: "http://localhost:7332/mcp", headers: {} },
|
|
12
12
|
timeoutMs: 15000
|
|
13
13
|
},
|
|
14
14
|
verify: {
|
|
@@ -48,6 +48,8 @@ export function renderYaml(value, indent = 0) {
|
|
|
48
48
|
.join("\n");
|
|
49
49
|
}
|
|
50
50
|
if (typeof value === "object" && value !== null) {
|
|
51
|
+
if (Object.keys(value).length === 0)
|
|
52
|
+
return `${pad}{}`;
|
|
51
53
|
return Object.entries(value)
|
|
52
54
|
.map(([key, item]) => {
|
|
53
55
|
if (typeof item === "object" && item !== null)
|
|
@@ -65,7 +67,10 @@ function readScalar(raw) {
|
|
|
65
67
|
const value = raw.trim().replace(/^["']|["']$/g, "");
|
|
66
68
|
if (/^\d+$/.test(value))
|
|
67
69
|
return Number(value);
|
|
68
|
-
return value;
|
|
70
|
+
return expandEnv(value);
|
|
71
|
+
}
|
|
72
|
+
function expandEnv(value) {
|
|
73
|
+
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => process.env[name] ?? "");
|
|
69
74
|
}
|
|
70
75
|
function parseVerifyCommands(text) {
|
|
71
76
|
const commands = [];
|
|
@@ -95,14 +100,71 @@ function blockList(text, key) {
|
|
|
95
100
|
const match = text.match(new RegExp(`^\\s*${key}:\\n([\\s\\S]*?)(?=^\\s*[a-zA-Z][\\w]*:|^\\S|$)`, "m"));
|
|
96
101
|
return match ? parseStringList(match[1]) : [];
|
|
97
102
|
}
|
|
103
|
+
function mcpAskFor(text, source) {
|
|
104
|
+
const lines = text.split("\n");
|
|
105
|
+
const sourceIndex = lines.findIndex((line) => line === ` ${source}:`);
|
|
106
|
+
if (sourceIndex < 0)
|
|
107
|
+
return [];
|
|
108
|
+
const askForIndex = lines.findIndex((line, index) => index > sourceIndex && line === " ask_for:");
|
|
109
|
+
if (askForIndex < 0)
|
|
110
|
+
return [];
|
|
111
|
+
const values = [];
|
|
112
|
+
for (const line of lines.slice(askForIndex + 1)) {
|
|
113
|
+
if (!line.trim())
|
|
114
|
+
continue;
|
|
115
|
+
if (!line.startsWith(" - "))
|
|
116
|
+
break;
|
|
117
|
+
values.push(String(readScalar(line.slice(" - ".length))));
|
|
118
|
+
}
|
|
119
|
+
return values;
|
|
120
|
+
}
|
|
121
|
+
function objectBlock(text, key) {
|
|
122
|
+
const values = {};
|
|
123
|
+
const lines = text.split("\n");
|
|
124
|
+
const start = lines.findIndex((line) => line.match(new RegExp(`^\\s*${key}:\\s*$`)));
|
|
125
|
+
if (start < 0)
|
|
126
|
+
return values;
|
|
127
|
+
const baseIndent = lines[start].match(/^\s*/)?.[0].length ?? 0;
|
|
128
|
+
for (const line of lines.slice(start + 1)) {
|
|
129
|
+
if (!line.trim())
|
|
130
|
+
continue;
|
|
131
|
+
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
132
|
+
if (indent <= baseIndent)
|
|
133
|
+
break;
|
|
134
|
+
const item = line.match(/^\s*([A-Za-z0-9_-]+):\s*(.+)$/);
|
|
135
|
+
if (item)
|
|
136
|
+
values[item[1]] = String(readScalar(item[2]));
|
|
137
|
+
}
|
|
138
|
+
return values;
|
|
139
|
+
}
|
|
140
|
+
function indentedSection(text, key, indent) {
|
|
141
|
+
const lines = text.split("\n");
|
|
142
|
+
const start = lines.findIndex((line) => line.match(new RegExp(`^ {${indent}}${key}:\\s*$`)));
|
|
143
|
+
if (start < 0)
|
|
144
|
+
return "";
|
|
145
|
+
const collected = [];
|
|
146
|
+
for (const line of lines.slice(start + 1)) {
|
|
147
|
+
if (!line.trim()) {
|
|
148
|
+
collected.push(line);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const currentIndent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
152
|
+
if (currentIndent <= indent)
|
|
153
|
+
break;
|
|
154
|
+
collected.push(line);
|
|
155
|
+
}
|
|
156
|
+
return collected.join("\n");
|
|
157
|
+
}
|
|
98
158
|
export async function loadConfig(cwd) {
|
|
99
159
|
const configPath = path.join(cwd, "harness.yaml");
|
|
100
160
|
if (!(await pathExists(configPath)))
|
|
101
161
|
return defaultConfig;
|
|
102
|
-
const text = await readFile(configPath, "utf8");
|
|
162
|
+
const text = (await readFile(configPath, "utf8")).replace(/\r\n/g, "\n");
|
|
103
163
|
const verifyRoot = topSection(text, "verify");
|
|
104
164
|
const verifySection = verifyRoot.match(/defaultCommands:\n([\s\S]*?)(?=^\S|^ [a-zA-Z][\w]*:|$)/m)?.[1] ?? "";
|
|
105
165
|
const contextSection = topSection(text, "context");
|
|
166
|
+
const codegraphSection = indentedSection(text, "codegraph", 2);
|
|
167
|
+
const knowledgeSection = indentedSection(text, "knowledgeGraph", 2);
|
|
106
168
|
return {
|
|
107
169
|
agents: {
|
|
108
170
|
codex: {
|
|
@@ -114,10 +176,14 @@ export async function loadConfig(cwd) {
|
|
|
114
176
|
},
|
|
115
177
|
mcp: {
|
|
116
178
|
codegraph: {
|
|
117
|
-
|
|
179
|
+
type: String(scalar(codegraphSection, /^\s*type:\s*([^\n]+)/m, defaultConfig.mcp.codegraph.type ?? "http")),
|
|
180
|
+
url: String(scalar(codegraphSection, /^\s*url:\s*([^\n]+)/m, defaultConfig.mcp.codegraph.url)),
|
|
181
|
+
headers: objectBlock(codegraphSection, "headers")
|
|
118
182
|
},
|
|
119
183
|
knowledgeGraph: {
|
|
120
|
-
|
|
184
|
+
type: String(scalar(knowledgeSection, /^\s*type:\s*([^\n]+)/m, defaultConfig.mcp.knowledgeGraph.type ?? "http")),
|
|
185
|
+
url: String(scalar(knowledgeSection, /^\s*url:\s*([^\n]+)/m, defaultConfig.mcp.knowledgeGraph.url)),
|
|
186
|
+
headers: objectBlock(knowledgeSection, "headers")
|
|
121
187
|
},
|
|
122
188
|
timeoutMs: Number(scalar(text, /mcp:\n[\s\S]*?timeoutMs:\s*([^\n]+)/m, defaultConfig.mcp.timeoutMs))
|
|
123
189
|
},
|
|
@@ -143,14 +209,19 @@ export async function loadSkill(cwd, name) {
|
|
|
143
209
|
throw new Error(`Skill not found: ${name}. Expected ${path.join(dir, "SKILL.md")}`);
|
|
144
210
|
}
|
|
145
211
|
const markdown = await readTextIfExists(path.join(dir, "SKILL.md"));
|
|
146
|
-
const verifyText = await readTextIfExists(path.join(dir, "verify.yaml"));
|
|
147
|
-
const contextText = await readTextIfExists(path.join(dir, "context.yaml"));
|
|
212
|
+
const verifyText = (await readTextIfExists(path.join(dir, "verify.yaml"))).replace(/\r\n/g, "\n");
|
|
213
|
+
const contextText = (await readTextIfExists(path.join(dir, "context.yaml"))).replace(/\r\n/g, "\n");
|
|
148
214
|
return {
|
|
149
215
|
name,
|
|
150
216
|
dir,
|
|
151
217
|
markdown,
|
|
152
218
|
verifyCommands: parseVerifyCommands(verifyText),
|
|
153
219
|
contextQueries: blockList(contextText, "queries"),
|
|
220
|
+
mcpPreflight: {
|
|
221
|
+
codegraph: mcpAskFor(contextText, "codegraph"),
|
|
222
|
+
knowledgeGraph: mcpAskFor(contextText, "knowledgeGraph")
|
|
223
|
+
},
|
|
224
|
+
stopConditions: blockList(contextText, "stop_conditions"),
|
|
154
225
|
fallbackInclude: blockList(contextText, "fallbackInclude")
|
|
155
226
|
};
|
|
156
227
|
}
|
package/dist/context.js
CHANGED
|
@@ -41,24 +41,15 @@ async function fallbackContext(cwd, config, skill) {
|
|
|
41
41
|
}
|
|
42
42
|
return snippets.join("\n\n");
|
|
43
43
|
}
|
|
44
|
+
function listSection(items, empty = "- (none configured)") {
|
|
45
|
+
return items.length ? items.map((item) => `- ${item}`).join("\n") : empty;
|
|
46
|
+
}
|
|
44
47
|
export async function buildContextPack(options) {
|
|
45
48
|
const { cwd, task, config, skill, contextPackPath, mcpLogPath, audit } = options;
|
|
46
|
-
await audit.event({ stage: "context_pack", event: "
|
|
49
|
+
await audit.event({ stage: "context_pack", event: "mcp_sources_record", status: "started" });
|
|
47
50
|
const source = new ContextSource(config, mcpLogPath);
|
|
48
51
|
const { codeContext, knowledgeContext } = await source.searchTaskContext(task, skill);
|
|
49
|
-
|
|
50
|
-
if (degraded)
|
|
51
|
-
await audit.event({
|
|
52
|
-
stage: "context_pack",
|
|
53
|
-
event: "mcp_query",
|
|
54
|
-
status: "degraded",
|
|
55
|
-
detail: {
|
|
56
|
-
codegraph: codeContext.error,
|
|
57
|
-
knowledgeGraph: knowledgeContext.error
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
else
|
|
61
|
-
await audit.event({ stage: "context_pack", event: "mcp_query", status: "succeeded" });
|
|
52
|
+
await audit.event({ stage: "context_pack", event: "mcp_sources_record", status: "succeeded" });
|
|
62
53
|
const branch = await git(cwd, "branch --show-current");
|
|
63
54
|
const status = await git(cwd, "status --short");
|
|
64
55
|
const recent = await git(cwd, "log -5 --oneline");
|
|
@@ -66,12 +57,53 @@ export async function buildContextPack(options) {
|
|
|
66
57
|
const markdown = `# Context Pack
|
|
67
58
|
|
|
68
59
|
## How To Use This Context
|
|
69
|
-
-
|
|
70
|
-
-
|
|
60
|
+
- CodeGraph and Knowledge Graph are agent-side MCP sources. Use Claude/Codex MCP tools directly before and during execution when available.
|
|
61
|
+
- Harness records MCP configuration and audit evidence, but it does not implement MCP transport or replace the agent MCP client.
|
|
62
|
+
- If the current agent cannot access an MCP source, explicitly mention the missing source and rely on fallback context carefully.
|
|
71
63
|
- Do not assume fallback file snippets are complete repository knowledge.
|
|
72
64
|
- Cross-check task assumptions against Git state, project rules, and available source files before editing.
|
|
73
65
|
- Prefer minimal changes in files directly connected to the task.
|
|
74
66
|
|
|
67
|
+
## Agent Context Assembly Protocol
|
|
68
|
+
Before editing, assemble context in this exact shape:
|
|
69
|
+
|
|
70
|
+
### MCP Preflight Findings
|
|
71
|
+
- CodeGraph tools used:
|
|
72
|
+
- CodeGraph findings:
|
|
73
|
+
- Related files:
|
|
74
|
+
- Related symbols:
|
|
75
|
+
- Callers / dependencies:
|
|
76
|
+
- Impact scope:
|
|
77
|
+
- Knowledge Graph tools used:
|
|
78
|
+
- Knowledge Graph findings:
|
|
79
|
+
- ADRs / decisions:
|
|
80
|
+
- Module notes:
|
|
81
|
+
- API notes:
|
|
82
|
+
- Runbooks / known issues:
|
|
83
|
+
- Missing context:
|
|
84
|
+
|
|
85
|
+
### Impact Analysis
|
|
86
|
+
- Root cause or implementation intent:
|
|
87
|
+
- Affected files/modules:
|
|
88
|
+
- User-visible behavior:
|
|
89
|
+
- Compatibility and risk:
|
|
90
|
+
|
|
91
|
+
### Proposed Plan
|
|
92
|
+
- Files to inspect:
|
|
93
|
+
- Files to change:
|
|
94
|
+
- Verification plan:
|
|
95
|
+
- Stop conditions checked:
|
|
96
|
+
|
|
97
|
+
## Skill MCP Preflight Requirements
|
|
98
|
+
### CodeGraph ask_for
|
|
99
|
+
${listSection(skill.mcpPreflight.codegraph)}
|
|
100
|
+
|
|
101
|
+
### Knowledge Graph ask_for
|
|
102
|
+
${listSection(skill.mcpPreflight.knowledgeGraph)}
|
|
103
|
+
|
|
104
|
+
## Skill Stop Conditions
|
|
105
|
+
${listSection(skill.stopConditions)}
|
|
106
|
+
|
|
75
107
|
## Task
|
|
76
108
|
${task}
|
|
77
109
|
|
|
@@ -92,15 +124,15 @@ ${status || "(clean or unavailable)"}
|
|
|
92
124
|
${recent || "(unavailable)"}
|
|
93
125
|
\`\`\`
|
|
94
126
|
|
|
95
|
-
## CodeGraph MCP
|
|
96
|
-
|
|
127
|
+
## CodeGraph MCP Source
|
|
128
|
+
Mode: agent-driven
|
|
97
129
|
|
|
98
130
|
\`\`\`json
|
|
99
131
|
${truncate(JSON.stringify(codeContext.data ?? { error: codeContext.error }, null, 2), 12000)}
|
|
100
132
|
\`\`\`
|
|
101
133
|
|
|
102
|
-
## Knowledge Graph MCP
|
|
103
|
-
|
|
134
|
+
## Knowledge Graph MCP Source
|
|
135
|
+
Mode: agent-driven
|
|
104
136
|
|
|
105
137
|
\`\`\`json
|
|
106
138
|
${truncate(JSON.stringify(knowledgeContext.data ?? { error: knowledgeContext.error }, null, 2), 12000)}
|
|
@@ -110,5 +142,5 @@ ${truncate(JSON.stringify(knowledgeContext.data ?? { error: knowledgeContext.err
|
|
|
110
142
|
${fallback || "(no fallback context found)"}
|
|
111
143
|
`;
|
|
112
144
|
await writeFile(contextPackPath, markdown, "utf8");
|
|
113
|
-
return { markdown,
|
|
145
|
+
return { markdown, mcpResults: [codeContext, knowledgeContext] };
|
|
114
146
|
}
|
package/dist/init.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { rootTemplates, skillTemplates } from "./templates.js";
|
|
2
|
+
import { defaultProjectTemplates, packageRootFromHere } from "./templates.js";
|
|
4
3
|
import { ensureDir, writeFileIfMissing } from "./utils.js";
|
|
5
|
-
export async function initHarness(cwd) {
|
|
4
|
+
export async function initHarness(cwd, packageRoot = packageRootFromHere()) {
|
|
6
5
|
const created = [];
|
|
7
6
|
const skipped = [];
|
|
8
|
-
for (const [file, content] of Object.entries(rootTemplates)) {
|
|
9
|
-
const target = path.join(cwd, file);
|
|
10
|
-
if (await writeFileIfMissing(target, content))
|
|
11
|
-
created.push(file);
|
|
12
|
-
else
|
|
13
|
-
skipped.push(file);
|
|
14
|
-
}
|
|
15
7
|
await ensureDir(path.join(cwd, "runs"));
|
|
16
8
|
await ensureDir(path.join(cwd, "skills"));
|
|
17
|
-
for (const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
created.push(rel);
|
|
24
|
-
else
|
|
25
|
-
skipped.push(rel);
|
|
26
|
-
}
|
|
9
|
+
for (const file of await defaultProjectTemplates(packageRoot)) {
|
|
10
|
+
const target = path.join(cwd, file.relPath);
|
|
11
|
+
if (await writeFileIfMissing(target, file.content))
|
|
12
|
+
created.push(file.relPath);
|
|
13
|
+
else
|
|
14
|
+
skipped.push(file.relPath);
|
|
27
15
|
}
|
|
28
16
|
return { created, skipped };
|
|
29
17
|
}
|