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.
Files changed (32) hide show
  1. package/COLLEAGUE_TRIAL_GUIDE.md +147 -0
  2. package/README.md +116 -52
  3. package/dist/agent.js +44 -31
  4. package/dist/cli.js +65 -42
  5. package/dist/config.js +79 -8
  6. package/dist/context.js +53 -21
  7. package/dist/init.js +8 -20
  8. package/dist/integration.js +22 -91
  9. package/dist/mcp.js +29 -112
  10. package/dist/runPaths.js +1 -0
  11. package/dist/summarize.js +42 -39
  12. package/dist/templates.js +39 -215
  13. package/package.json +3 -1
  14. package/scripts/read-evidence.ps1 +9 -3
  15. package/scripts/run-harness.ps1 +8 -5
  16. package/templates/default-project/AGENTS.md +9 -0
  17. package/templates/default-project/SKILLS.md +13 -0
  18. package/templates/default-project/TOOLS.md +8 -0
  19. package/templates/default-project/skills/add_feature/SKILL.md +34 -0
  20. package/templates/default-project/skills/add_feature/context.yaml +30 -0
  21. package/templates/default-project/skills/add_feature/verify.yaml +2 -0
  22. package/templates/default-project/skills/fix_bug/SKILL.md +45 -0
  23. package/templates/default-project/skills/fix_bug/context.yaml +30 -0
  24. package/templates/default-project/skills/fix_bug/verify.yaml +2 -0
  25. package/templates/default-project/skills/update_docs/SKILL.md +28 -0
  26. package/templates/default-project/skills/update_docs/context.yaml +26 -0
  27. package/templates/default-project/skills/update_docs/verify.yaml +2 -0
  28. package/templates/default-project/skills/write_tests/SKILL.md +30 -0
  29. package/templates/default-project/skills/write_tests/context.yaml +29 -0
  30. package/templates/default-project/skills/write_tests/verify.yaml +2 -0
  31. package/templates/integrations/claude/SKILL.md +79 -0
  32. 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
- Usage:
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(`Missing required --${key}`);
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(`Invalid agent: ${value}. Expected codex or claude.`);
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(`Run not found: ${runArg}`);
67
+ throw new Error(`未找到运行记录:${runArg}`);
67
68
  }
68
69
  async function commandInit(cwd) {
69
- const result = await initHarness(cwd);
70
- console.log(`Initialized AI Dev Harness in ${cwd}`);
71
- console.log(`Created ${result.created.length} file(s), skipped ${result.skipped.length} existing file(s).`);
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, `run started: skill=${skillName} agent=${agentName}`);
88
- sayLive(live, `evidence directory: ${paths.runDir}`);
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: "Current directory is not a Git repository. Run inside a business Git repository before executing harness run."
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 must execute inside a Git repository. Evidence: ${paths.runDir}`);
122
+ throw new Error(`harness run 必须在 Git 仓库内执行。证据目录:${paths.runDir}`);
110
123
  }
111
- sayLive(live, "building context pack with MCP first, local fallback second");
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: contextPack.degraded ? "degraded" : "succeeded"
138
+ status: "succeeded"
126
139
  });
127
- sayLive(live, `context pack ${contextPack.degraded ? "degraded" : "ready"}: ${paths.contextPack}`);
128
- const plan = `# Execution Plan
140
+ sayLive(live, `Context Pack 已生成:${paths.contextPack}`);
141
+ const plan = `# 执行阶段协议
129
142
 
130
- The selected agent must:
131
- 1. Read the context pack.
132
- 2. Produce impact analysis.
133
- 3. Make the smallest safe change.
134
- 4. Leave enough evidence for review.
135
- 5. Allow the harness to run verification and summarize results.
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, `plan written: ${paths.plan}`);
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, `executing ${adapter.name}: ${agentCommand}`);
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
- sayLive(live, `agent finished: status=${agentResult.status} changedFiles=${agentResult.changedFiles.length}`);
176
- sayLive(live, "running verification commands");
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, `verification ${verifyResult.status}: ${paths.verify}`);
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, `summary written: ${paths.summary}`);
194
- sayLive(live, `persist suggestions written: ${paths.persistSuggestions}`);
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(`Run complete: ${paths.runId}`);
198
- console.log(`Evidence: ${paths.runDir}`);
219
+ console.log(`运行完成:${paths.runId}`);
220
+ console.log(`证据目录:${paths.runDir}`);
199
221
  if (!runPassed)
200
- throw new Error(`Harness run failed. Evidence: ${paths.runDir}`);
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(`Invalid --agent ${rawAgent}. Expected claude, codex, or both.`);
227
+ throw new Error(`无效 --agent ${rawAgent}。期望 claudecodex 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 harnessCommand = typeof args.command === "string" ? args.command : `node ${quoteCommandPath(path.resolve(process.argv[1]))}`;
210
- const result = await installIntegration({ targetDir, harnessCommand, agent: rawAgent, packageRoot });
211
- console.log(`Installed AI Dev Harness conversation integration in ${targetDir}`);
212
- console.log(`Created: ${result.created.length ? result.created.join(", ") : "(none)"}`);
213
- console.log(`Updated: ${result.updated.length ? result.updated.join(", ") : "(none)"}`);
214
- console.log(`Skipped: ${result.skipped.length ? result.skipped.join(", ") : "(none)"}`);
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(`Verify ${result.status}: ${runDir}`);
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(`Summary refreshed: ${runDir}`);
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(`Unknown command: ${command}\n${usage()}`);
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
- url: String(scalar(text, /mcp:\n[\s\S]*?codegraph:\n\s+url:\s*([^\n]+)/m, defaultConfig.mcp.codegraph.url))
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
- url: String(scalar(text, /mcp:\n[\s\S]*?knowledgeGraph:\n\s+url:\s*([^\n]+)/m, defaultConfig.mcp.knowledgeGraph.url))
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: "mcp_query", status: "started" });
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
- const degraded = codeContext.degraded || knowledgeContext.degraded;
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
- - Treat CodeGraph and Knowledge Graph results as primary context when status is ok.
70
- - If either MCP source is degraded, explicitly mention the degraded source in your analysis and rely on fallback context carefully.
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
- Status: ${codeContext.ok ? "ok" : "degraded"}
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
- Status: ${knowledgeContext.ok ? "ok" : "degraded"}
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, degraded, mcpResults: [codeContext, knowledgeContext] };
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 { mkdir } from "node:fs/promises";
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 [skillName, files] of Object.entries(skillTemplates)) {
18
- await mkdir(path.join(cwd, "skills", skillName), { recursive: true });
19
- for (const [file, content] of Object.entries(files)) {
20
- const rel = path.join("skills", skillName, file);
21
- const target = path.join(cwd, rel);
22
- if (await writeFileIfMissing(target, content))
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
  }