ai-dev-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -0
- package/dist/agent.js +100 -0
- package/dist/audit.js +21 -0
- package/dist/cli.js +279 -0
- package/dist/config.js +156 -0
- package/dist/context.js +114 -0
- package/dist/init.js +29 -0
- package/dist/integration.js +173 -0
- package/dist/mcp.js +145 -0
- package/dist/runPaths.js +21 -0
- package/dist/summarize.js +72 -0
- package/dist/templates.js +217 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +95 -0
- package/dist/verify.js +28 -0
- package/package.json +40 -0
- package/scripts/read-evidence.ps1 +36 -0
- package/scripts/run-harness.ps1 +53 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ContextSource } from "./mcp.js";
|
|
4
|
+
import { git, pathExists, truncate } from "./utils.js";
|
|
5
|
+
async function listFallbackFiles(cwd, includes, excludes) {
|
|
6
|
+
const found = [];
|
|
7
|
+
const excluded = new Set(excludes.map((item) => path.normalize(item)));
|
|
8
|
+
async function walk(rel) {
|
|
9
|
+
const normalized = path.normalize(rel);
|
|
10
|
+
if (excluded.has(normalized) || [...excluded].some((ex) => normalized.startsWith(`${ex}${path.sep}`)))
|
|
11
|
+
return;
|
|
12
|
+
const abs = path.join(cwd, rel);
|
|
13
|
+
if (!(await pathExists(abs)))
|
|
14
|
+
return;
|
|
15
|
+
const info = await stat(abs);
|
|
16
|
+
if (info.isDirectory()) {
|
|
17
|
+
const entries = await readdir(abs);
|
|
18
|
+
for (const entry of entries)
|
|
19
|
+
await walk(path.join(rel, entry));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (info.size <= 80_000)
|
|
23
|
+
found.push(rel);
|
|
24
|
+
}
|
|
25
|
+
for (const item of includes)
|
|
26
|
+
await walk(item);
|
|
27
|
+
return found.slice(0, 80);
|
|
28
|
+
}
|
|
29
|
+
async function fallbackContext(cwd, config, skill) {
|
|
30
|
+
const includes = skill.fallbackInclude.length ? skill.fallbackInclude : config.context.fallbackInclude;
|
|
31
|
+
const files = await listFallbackFiles(cwd, includes, config.context.exclude);
|
|
32
|
+
const snippets = [];
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
try {
|
|
35
|
+
const text = await readFile(path.join(cwd, file), "utf8");
|
|
36
|
+
snippets.push(`## ${file}\n\n\`\`\`\n${truncate(text, 5000)}\n\`\`\``);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Binary or unreadable files are ignored in fallback context.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return snippets.join("\n\n");
|
|
43
|
+
}
|
|
44
|
+
export async function buildContextPack(options) {
|
|
45
|
+
const { cwd, task, config, skill, contextPackPath, mcpLogPath, audit } = options;
|
|
46
|
+
await audit.event({ stage: "context_pack", event: "mcp_query", status: "started" });
|
|
47
|
+
const source = new ContextSource(config, mcpLogPath);
|
|
48
|
+
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" });
|
|
62
|
+
const branch = await git(cwd, "branch --show-current");
|
|
63
|
+
const status = await git(cwd, "status --short");
|
|
64
|
+
const recent = await git(cwd, "log -5 --oneline");
|
|
65
|
+
const fallback = await fallbackContext(cwd, config, skill);
|
|
66
|
+
const markdown = `# Context Pack
|
|
67
|
+
|
|
68
|
+
## 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.
|
|
71
|
+
- Do not assume fallback file snippets are complete repository knowledge.
|
|
72
|
+
- Cross-check task assumptions against Git state, project rules, and available source files before editing.
|
|
73
|
+
- Prefer minimal changes in files directly connected to the task.
|
|
74
|
+
|
|
75
|
+
## Task
|
|
76
|
+
${task}
|
|
77
|
+
|
|
78
|
+
## Skill
|
|
79
|
+
${skill.name}
|
|
80
|
+
|
|
81
|
+
${skill.markdown}
|
|
82
|
+
|
|
83
|
+
## Git State
|
|
84
|
+
- Branch: ${branch || "(unknown)"}
|
|
85
|
+
|
|
86
|
+
\`\`\`
|
|
87
|
+
${status || "(clean or unavailable)"}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
## Recent Commits
|
|
91
|
+
\`\`\`
|
|
92
|
+
${recent || "(unavailable)"}
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
## CodeGraph MCP
|
|
96
|
+
Status: ${codeContext.ok ? "ok" : "degraded"}
|
|
97
|
+
|
|
98
|
+
\`\`\`json
|
|
99
|
+
${truncate(JSON.stringify(codeContext.data ?? { error: codeContext.error }, null, 2), 12000)}
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
## Knowledge Graph MCP
|
|
103
|
+
Status: ${knowledgeContext.ok ? "ok" : "degraded"}
|
|
104
|
+
|
|
105
|
+
\`\`\`json
|
|
106
|
+
${truncate(JSON.stringify(knowledgeContext.data ?? { error: knowledgeContext.error }, null, 2), 12000)}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
## Fallback Repository Context
|
|
110
|
+
${fallback || "(no fallback context found)"}
|
|
111
|
+
`;
|
|
112
|
+
await writeFile(contextPackPath, markdown, "utf8");
|
|
113
|
+
return { markdown, degraded, mcpResults: [codeContext, knowledgeContext] };
|
|
114
|
+
}
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { rootTemplates, skillTemplates } from "./templates.js";
|
|
4
|
+
import { ensureDir, writeFileIfMissing } from "./utils.js";
|
|
5
|
+
export async function initHarness(cwd) {
|
|
6
|
+
const created = [];
|
|
7
|
+
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
|
+
await ensureDir(path.join(cwd, "runs"));
|
|
16
|
+
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
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { created, skipped };
|
|
29
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { appendFile, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { pathExists } from "./utils.js";
|
|
4
|
+
export async function installIntegration(options) {
|
|
5
|
+
const created = [];
|
|
6
|
+
const updated = [];
|
|
7
|
+
const skipped = [];
|
|
8
|
+
const scriptResult = await installScriptAssets(options.targetDir, options.packageRoot ?? process.cwd());
|
|
9
|
+
created.push(...scriptResult.created);
|
|
10
|
+
updated.push(...scriptResult.updated);
|
|
11
|
+
skipped.push(...scriptResult.skipped);
|
|
12
|
+
if (options.agent === "claude" || options.agent === "both") {
|
|
13
|
+
const result = await installClaudeSkill(options.targetDir, options.harnessCommand);
|
|
14
|
+
if (result === "created")
|
|
15
|
+
created.push(".claude/skills/ai-dev-harness/SKILL.md");
|
|
16
|
+
else if (result === "updated")
|
|
17
|
+
updated.push(".claude/skills/ai-dev-harness/SKILL.md");
|
|
18
|
+
else
|
|
19
|
+
skipped.push(".claude/skills/ai-dev-harness/SKILL.md");
|
|
20
|
+
}
|
|
21
|
+
if (options.agent === "codex" || options.agent === "both") {
|
|
22
|
+
const result = await installCodexRules(options.targetDir, options.harnessCommand);
|
|
23
|
+
if (result === "created")
|
|
24
|
+
created.push("AGENTS.md");
|
|
25
|
+
else if (result === "updated")
|
|
26
|
+
updated.push("AGENTS.md");
|
|
27
|
+
else
|
|
28
|
+
skipped.push("AGENTS.md");
|
|
29
|
+
}
|
|
30
|
+
return { created, updated, skipped };
|
|
31
|
+
}
|
|
32
|
+
async function installScriptAssets(targetDir, packageRoot) {
|
|
33
|
+
const created = [];
|
|
34
|
+
const updated = [];
|
|
35
|
+
const skipped = [];
|
|
36
|
+
const assets = ["run-harness.ps1", "read-evidence.ps1"];
|
|
37
|
+
const targetScriptDir = path.join(targetDir, ".harness", "ai-dev-harness", "scripts");
|
|
38
|
+
await mkdir(targetScriptDir, { recursive: true });
|
|
39
|
+
for (const asset of assets) {
|
|
40
|
+
const source = path.join(packageRoot, "scripts", asset);
|
|
41
|
+
const target = path.join(targetScriptDir, asset);
|
|
42
|
+
const rel = path.join(".harness", "ai-dev-harness", "scripts", asset);
|
|
43
|
+
if (!(await pathExists(source)))
|
|
44
|
+
continue;
|
|
45
|
+
const next = await readFile(source, "utf8");
|
|
46
|
+
if (await pathExists(target)) {
|
|
47
|
+
const current = await readFile(target, "utf8");
|
|
48
|
+
if (current === next) {
|
|
49
|
+
skipped.push(rel);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
await writeFile(target, next, "utf8");
|
|
53
|
+
updated.push(rel);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
await copyFile(source, target);
|
|
57
|
+
created.push(rel);
|
|
58
|
+
}
|
|
59
|
+
return { created, updated, skipped };
|
|
60
|
+
}
|
|
61
|
+
async function installClaudeSkill(targetDir, harnessCommand) {
|
|
62
|
+
const skillPath = path.join(targetDir, ".claude", "skills", "ai-dev-harness", "SKILL.md");
|
|
63
|
+
await mkdir(path.dirname(skillPath), { recursive: true });
|
|
64
|
+
if (await pathExists(skillPath)) {
|
|
65
|
+
const current = await readFile(skillPath, "utf8");
|
|
66
|
+
const next = claudeSkill(harnessCommand);
|
|
67
|
+
if (current === next)
|
|
68
|
+
return "skipped";
|
|
69
|
+
await writeFile(skillPath, next, "utf8");
|
|
70
|
+
return "updated";
|
|
71
|
+
}
|
|
72
|
+
await writeFile(skillPath, claudeSkill(harnessCommand), "utf8");
|
|
73
|
+
return "created";
|
|
74
|
+
}
|
|
75
|
+
async function installCodexRules(targetDir, harnessCommand) {
|
|
76
|
+
const agentsPath = path.join(targetDir, "AGENTS.md");
|
|
77
|
+
const block = codexBlock(harnessCommand);
|
|
78
|
+
if (!(await pathExists(agentsPath))) {
|
|
79
|
+
await writeFile(agentsPath, `# Project Agent Rules\n\n${block}`, "utf8");
|
|
80
|
+
return "created";
|
|
81
|
+
}
|
|
82
|
+
const current = await readFile(agentsPath, "utf8");
|
|
83
|
+
if (current.includes("## AI Dev Harness")) {
|
|
84
|
+
const next = current.replace(/(?:\r?\n){0,2}## AI Dev Harness[\s\S]*$/m, `\n\n${block}`);
|
|
85
|
+
if (next === current)
|
|
86
|
+
return "skipped";
|
|
87
|
+
await writeFile(agentsPath, next, "utf8");
|
|
88
|
+
return "updated";
|
|
89
|
+
}
|
|
90
|
+
await appendFile(agentsPath, `\n\n${block}`, "utf8");
|
|
91
|
+
return "updated";
|
|
92
|
+
}
|
|
93
|
+
function claudeSkill(harnessCommand) {
|
|
94
|
+
return `---
|
|
95
|
+
name: ai-dev-harness
|
|
96
|
+
description: Trigger the auditable AI Dev Harness CLI from Claude Code conversations and stream the Harness process in the current chat.
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# AI Dev Harness
|
|
100
|
+
|
|
101
|
+
Use this skill when the user asks to use Harness, AI Dev Harness, an audited run, CodeGraph/Knowledge Graph context, or one of these skills: \`fix_bug\`, \`add_feature\`, \`write_tests\`, \`update_docs\`.
|
|
102
|
+
|
|
103
|
+
## Command
|
|
104
|
+
|
|
105
|
+
Run from the repository root:
|
|
106
|
+
|
|
107
|
+
\`\`\`bash
|
|
108
|
+
powershell -ExecutionPolicy Bypass -File ".harness/ai-dev-harness/scripts/run-harness.ps1" -Task "<task>" -Skill <skill> -Agent claude -Live
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
Fallback direct command:
|
|
112
|
+
|
|
113
|
+
\`\`\`bash
|
|
114
|
+
${harnessCommand} run --task "<task>" --skill <skill> --agent claude --live
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
## Skill Mapping
|
|
118
|
+
|
|
119
|
+
- Defect or regression: \`fix_bug\`
|
|
120
|
+
- Scoped feature: \`add_feature\`
|
|
121
|
+
- Add or improve tests: \`write_tests\`
|
|
122
|
+
- Read-only analysis or docs: \`update_docs\`
|
|
123
|
+
|
|
124
|
+
For read-only analysis, include "Do not modify repository files" in the task.
|
|
125
|
+
|
|
126
|
+
## Reporting
|
|
127
|
+
|
|
128
|
+
After the command finishes, inspect and report:
|
|
129
|
+
|
|
130
|
+
- Evidence directory printed by Harness
|
|
131
|
+
- \`summary.md\`
|
|
132
|
+
- \`verify.json\`
|
|
133
|
+
- \`audit.jsonl\`
|
|
134
|
+
- MCP degradation errors, if present
|
|
135
|
+
|
|
136
|
+
You may use:
|
|
137
|
+
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
powershell -ExecutionPolicy Bypass -File ".harness/ai-dev-harness/scripts/read-evidence.ps1" -Run <run_id_or_path>
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
Do not bypass Harness for implementation tasks unless the user explicitly asks for direct manual work.
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
function codexBlock(harnessCommand) {
|
|
146
|
+
return `## AI Dev Harness
|
|
147
|
+
|
|
148
|
+
Use the AI Dev Harness CLI when the user asks for Harness, an audited run, CodeGraph/Knowledge Graph assisted work, or one of these skills:
|
|
149
|
+
|
|
150
|
+
- \`fix_bug\`
|
|
151
|
+
- \`add_feature\`
|
|
152
|
+
- \`write_tests\`
|
|
153
|
+
- \`update_docs\`
|
|
154
|
+
|
|
155
|
+
Run from the repository root:
|
|
156
|
+
|
|
157
|
+
\`\`\`bash
|
|
158
|
+
powershell -ExecutionPolicy Bypass -File ".harness/ai-dev-harness/scripts/run-harness.ps1" -Task "<task>" -Skill <skill> -Agent codex -Live
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
Fallback direct command:
|
|
162
|
+
|
|
163
|
+
\`\`\`bash
|
|
164
|
+
${harnessCommand} run --task "<task>" --skill <skill> --agent codex --live
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
For read-only analysis, include "Do not modify repository files" in the task and usually use \`update_docs\`.
|
|
168
|
+
|
|
169
|
+
After every Harness run, report the evidence directory, inspect \`summary.md\`, \`verify.json\`, and \`audit.jsonl\`, and report MCP degradation errors if present.
|
|
170
|
+
Use \`.harness/ai-dev-harness/scripts/read-evidence.ps1\` to print the evidence summary when useful.
|
|
171
|
+
Do not write directly to Knowledge Graph in v1; use \`persist_suggestions.md\`.
|
|
172
|
+
`;
|
|
173
|
+
}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { appendFile } from "node:fs/promises";
|
|
2
|
+
import { truncate } from "./utils.js";
|
|
3
|
+
async function postJson(url, body, timeoutMs) {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(url, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: { "content-type": "application/json", accept: "application/json, text/event-stream" },
|
|
10
|
+
body: JSON.stringify(body),
|
|
11
|
+
signal: controller.signal
|
|
12
|
+
});
|
|
13
|
+
const text = await response.text();
|
|
14
|
+
if (!response.ok)
|
|
15
|
+
throw new Error(`HTTP ${response.status}: ${truncate(text, 1000)}`);
|
|
16
|
+
return parseMcpResponse(text, response.headers.get("content-type") ?? "");
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function parseMcpResponse(text, contentType) {
|
|
23
|
+
if (contentType.includes("text/event-stream")) {
|
|
24
|
+
const dataLines = text
|
|
25
|
+
.split(/\r?\n/)
|
|
26
|
+
.filter((line) => line.startsWith("data:"))
|
|
27
|
+
.map((line) => line.slice(5).trim())
|
|
28
|
+
.filter((line) => line && line !== "[DONE]");
|
|
29
|
+
return dataLines.map((line) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(line);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return line;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function summarize(data) {
|
|
46
|
+
return truncate(typeof data === "string" ? data : JSON.stringify(data, null, 2), 6000);
|
|
47
|
+
}
|
|
48
|
+
export class CodeGraphSource {
|
|
49
|
+
config;
|
|
50
|
+
queryLogPath;
|
|
51
|
+
constructor(config, queryLogPath) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
this.queryLogPath = queryLogPath;
|
|
54
|
+
}
|
|
55
|
+
async getImpactContext(task, skill, changedFiles) {
|
|
56
|
+
const request = {
|
|
57
|
+
type: "codegraph.impact",
|
|
58
|
+
task,
|
|
59
|
+
skill: skill.name,
|
|
60
|
+
queries: skill.contextQueries,
|
|
61
|
+
changedFiles
|
|
62
|
+
};
|
|
63
|
+
return this.query("codegraph", this.config.mcp.codegraph.url, request);
|
|
64
|
+
}
|
|
65
|
+
async query(source, url, request) {
|
|
66
|
+
try {
|
|
67
|
+
const data = await postJson(url, request, this.config.mcp.timeoutMs);
|
|
68
|
+
const result = {
|
|
69
|
+
source,
|
|
70
|
+
ok: true,
|
|
71
|
+
degraded: false,
|
|
72
|
+
request,
|
|
73
|
+
responseSummary: summarize(data),
|
|
74
|
+
data
|
|
75
|
+
};
|
|
76
|
+
await appendFile(this.queryLogPath, `${JSON.stringify(result)}\n`, "utf8");
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const result = {
|
|
81
|
+
source,
|
|
82
|
+
ok: false,
|
|
83
|
+
degraded: true,
|
|
84
|
+
request,
|
|
85
|
+
error: error instanceof Error ? error.message : String(error)
|
|
86
|
+
};
|
|
87
|
+
await appendFile(this.queryLogPath, `${JSON.stringify(result)}\n`, "utf8");
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export class KnowledgeGraphSource {
|
|
93
|
+
config;
|
|
94
|
+
queryLogPath;
|
|
95
|
+
constructor(config, queryLogPath) {
|
|
96
|
+
this.config = config;
|
|
97
|
+
this.queryLogPath = queryLogPath;
|
|
98
|
+
}
|
|
99
|
+
async searchKnowledge(task, skill, codeContext) {
|
|
100
|
+
const request = {
|
|
101
|
+
type: "knowledge.search",
|
|
102
|
+
task,
|
|
103
|
+
skill: skill.name,
|
|
104
|
+
queries: skill.contextQueries,
|
|
105
|
+
codeContext
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
const data = await postJson(this.config.mcp.knowledgeGraph.url, request, this.config.mcp.timeoutMs);
|
|
109
|
+
const result = {
|
|
110
|
+
source: "knowledgeGraph",
|
|
111
|
+
ok: true,
|
|
112
|
+
degraded: false,
|
|
113
|
+
request,
|
|
114
|
+
responseSummary: summarize(data),
|
|
115
|
+
data
|
|
116
|
+
};
|
|
117
|
+
await appendFile(this.queryLogPath, `${JSON.stringify(result)}\n`, "utf8");
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const result = {
|
|
122
|
+
source: "knowledgeGraph",
|
|
123
|
+
ok: false,
|
|
124
|
+
degraded: true,
|
|
125
|
+
request,
|
|
126
|
+
error: error instanceof Error ? error.message : String(error)
|
|
127
|
+
};
|
|
128
|
+
await appendFile(this.queryLogPath, `${JSON.stringify(result)}\n`, "utf8");
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export class ContextSource {
|
|
134
|
+
codeGraph;
|
|
135
|
+
knowledgeGraph;
|
|
136
|
+
constructor(config, queryLogPath) {
|
|
137
|
+
this.codeGraph = new CodeGraphSource(config, queryLogPath);
|
|
138
|
+
this.knowledgeGraph = new KnowledgeGraphSource(config, queryLogPath);
|
|
139
|
+
}
|
|
140
|
+
async searchTaskContext(task, skill) {
|
|
141
|
+
const codeContext = await this.codeGraph.getImpactContext(task, skill);
|
|
142
|
+
const knowledgeContext = await this.knowledgeGraph.searchKnowledge(task, skill, codeContext.data ?? codeContext.error);
|
|
143
|
+
return { codeContext, knowledgeContext };
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/runPaths.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { slugify, timestampId } from "./utils.js";
|
|
3
|
+
export function makeRunPaths(cwd, config, task) {
|
|
4
|
+
const runId = `${timestampId()}-${slugify(task)}`;
|
|
5
|
+
const runDir = path.join(cwd, config.audit.path, runId);
|
|
6
|
+
return {
|
|
7
|
+
runId,
|
|
8
|
+
runDir,
|
|
9
|
+
intake: path.join(runDir, "intake.json"),
|
|
10
|
+
mcpQueries: path.join(runDir, "mcp_queries.jsonl"),
|
|
11
|
+
contextPack: path.join(runDir, "context_pack.md"),
|
|
12
|
+
agentPrompt: path.join(runDir, "agent_prompt.md"),
|
|
13
|
+
plan: path.join(runDir, "plan.md"),
|
|
14
|
+
executionLog: path.join(runDir, "execution.log"),
|
|
15
|
+
gitDiff: path.join(runDir, "git_diff.patch"),
|
|
16
|
+
verify: path.join(runDir, "verify.json"),
|
|
17
|
+
summary: path.join(runDir, "summary.md"),
|
|
18
|
+
persistSuggestions: path.join(runDir, "persist_suggestions.md"),
|
|
19
|
+
audit: path.join(runDir, "audit.jsonl")
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { truncate } from "./utils.js";
|
|
3
|
+
export async function writeSummary(options) {
|
|
4
|
+
const diff = await readFile(options.gitDiffPath, "utf8").catch(() => "");
|
|
5
|
+
const summary = `# Run Summary
|
|
6
|
+
|
|
7
|
+
## Executive Status
|
|
8
|
+
- Task status: ${options.agentResult?.status === "success" && options.verifyResult?.status === "passed" ? "ready for review" : "requires attention"}
|
|
9
|
+
- Agent status: ${options.agentResult?.status ?? "not-run"}
|
|
10
|
+
- Verification status: ${options.verifyResult?.status ?? "not-run"}
|
|
11
|
+
|
|
12
|
+
## Task
|
|
13
|
+
${options.task}
|
|
14
|
+
|
|
15
|
+
## Skill
|
|
16
|
+
${options.skillName}
|
|
17
|
+
|
|
18
|
+
## Agent
|
|
19
|
+
${options.agentName}
|
|
20
|
+
|
|
21
|
+
## Execution
|
|
22
|
+
- Status: ${options.agentResult?.status ?? "not-run"}
|
|
23
|
+
- Exit code: ${options.agentResult?.exitCode ?? "n/a"}
|
|
24
|
+
- Changed files: ${options.agentResult?.changedFiles.join(", ") || "(none)"}
|
|
25
|
+
|
|
26
|
+
## Verification
|
|
27
|
+
- Status: ${options.verifyResult?.status ?? "not-run"}
|
|
28
|
+
|
|
29
|
+
## Review Checklist
|
|
30
|
+
- Confirm the change is limited to the requested task.
|
|
31
|
+
- Confirm public interfaces and backward compatibility were preserved.
|
|
32
|
+
- Confirm verification failures or skipped checks are understood.
|
|
33
|
+
- Confirm Knowledge Graph suggestions are reviewed before persistence.
|
|
34
|
+
|
|
35
|
+
## PR Description Draft
|
|
36
|
+
### What changed
|
|
37
|
+
${options.agentResult?.changedFiles.length ? `Updated ${options.agentResult.changedFiles.length} file(s).` : "No file changes detected."}
|
|
38
|
+
|
|
39
|
+
### Why
|
|
40
|
+
- See execution.log for the agent's impact analysis, plan, and final report.
|
|
41
|
+
|
|
42
|
+
### Verification
|
|
43
|
+
${options.verifyResult?.commands.map((cmd) => `- ${cmd.name}: ${cmd.exitCode === 0 ? "passed" : "failed"} (${cmd.command})`).join("\n") || "- Not run"}
|
|
44
|
+
|
|
45
|
+
### Risk
|
|
46
|
+
- Review required for correctness, safety, compatibility, and knowledge persistence before merging.
|
|
47
|
+
|
|
48
|
+
### Knowledge Follow-up
|
|
49
|
+
- Review persist_suggestions.md before updating Knowledge Graph.
|
|
50
|
+
|
|
51
|
+
## Diff Snapshot
|
|
52
|
+
\`\`\`diff
|
|
53
|
+
${truncate(diff, 12000)}
|
|
54
|
+
\`\`\`
|
|
55
|
+
`;
|
|
56
|
+
const persist = `# Knowledge Persist Suggestions
|
|
57
|
+
|
|
58
|
+
Review these suggestions before updating the Knowledge Graph.
|
|
59
|
+
|
|
60
|
+
- Task: ${options.task}
|
|
61
|
+
- Skill: ${options.skillName}
|
|
62
|
+
- Changed files: ${options.agentResult?.changedFiles.join(", ") || "(none)"}
|
|
63
|
+
- Persistence policy: human review required before writing to Knowledge Graph.
|
|
64
|
+
- Suggested knowledge updates:
|
|
65
|
+
- Add or update module notes if the change clarifies ownership or behavior.
|
|
66
|
+
- Add ADR/decision note if the implementation chose between meaningful alternatives.
|
|
67
|
+
- Add runbook/testing note if verification uncovered a recurring operational step.
|
|
68
|
+
- Record known MCP degradation or missing context if it affected confidence.
|
|
69
|
+
`;
|
|
70
|
+
await writeFile(options.summaryPath, summary, "utf8");
|
|
71
|
+
await writeFile(options.persistPath, persist, "utf8");
|
|
72
|
+
}
|