@diffci.com/diffci 0.1.11 → 0.1.12
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/LICENSE +1 -1
- package/README.md +301 -237
- package/action.yml +154 -154
- package/dist-client/src/client/cli.js +107 -88
- package/dist-client/src/client/economics.js +31 -0
- package/dist-client/src/client/mcp.js +196 -0
- package/dist-client/src/client/observe.js +40 -9
- package/docs/agent-adoption-kit.md +95 -0
- package/docs/agent-adoption-targets.md +52 -0
- package/docs/ai-agents.md +23 -0
- package/docs/language-support.md +118 -70
- package/docs/mcp.md +21 -0
- package/llms.txt +3 -0
- package/node_modules/@babel/parser/bin/babel-parser.js +0 -0
- package/node_modules/@diffci.com/core/README.md +9 -1
- package/node_modules/@diffci.com/core/dist/cache/economics-context.d.ts +2 -0
- package/node_modules/@diffci.com/core/dist/cache/economics-context.js +21 -0
- package/node_modules/@diffci.com/core/dist/cache/economics-context.js.map +1 -0
- package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.d.ts +5 -0
- package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.js +100 -0
- package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.js.map +1 -0
- package/node_modules/@diffci.com/core/dist/planner/test-command.js +32 -0
- package/node_modules/@diffci.com/core/dist/planner/test-command.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/adapters/go.d.ts +3 -0
- package/node_modules/@diffci.com/core/dist/repo/adapters/go.js +24 -6
- package/node_modules/@diffci.com/core/dist/repo/adapters/go.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/adapters/index.js +2 -1
- package/node_modules/@diffci.com/core/dist/repo/adapters/index.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/adapters/maven.d.ts +3 -0
- package/node_modules/@diffci.com/core/dist/repo/adapters/maven.js +65 -0
- package/node_modules/@diffci.com/core/dist/repo/adapters/maven.js.map +1 -0
- package/node_modules/@diffci.com/core/dist/repo/adapters/types.d.ts +11 -0
- package/node_modules/@diffci.com/core/dist/repo/adapters/types.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/adapters/vue.js +142 -14
- package/node_modules/@diffci.com/core/dist/repo/adapters/vue.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/graph.d.ts +4 -0
- package/node_modules/@diffci.com/core/dist/repo/graph.js +169 -19
- package/node_modules/@diffci.com/core/dist/repo/graph.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/impact.js +22 -5
- package/node_modules/@diffci.com/core/dist/repo/impact.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/repo-config.d.ts +15 -0
- package/node_modules/@diffci.com/core/dist/repo/repo-config.js +13 -2
- package/node_modules/@diffci.com/core/dist/repo/repo-config.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/test-discovery.js +2 -0
- package/node_modules/@diffci.com/core/dist/repo/test-discovery.js.map +1 -1
- package/node_modules/@diffci.com/core/dist/repo/types.d.ts +17 -0
- package/node_modules/@diffci.com/core/dist/repo/vue-scope.d.ts +4 -0
- package/node_modules/@diffci.com/core/dist/repo/vue-scope.js +157 -0
- package/node_modules/@diffci.com/core/dist/repo/vue-scope.js.map +1 -0
- package/node_modules/@diffci.com/core/src/cache/economics-context.ts +19 -0
- package/node_modules/@diffci.com/core/src/cache/vue-analysis-cache.ts +80 -0
- package/node_modules/@diffci.com/core/src/planner/test-command.ts +28 -0
- package/node_modules/@diffci.com/core/src/repo/adapters/go.ts +24 -5
- package/node_modules/@diffci.com/core/src/repo/adapters/index.ts +2 -1
- package/node_modules/@diffci.com/core/src/repo/adapters/maven.ts +38 -0
- package/node_modules/@diffci.com/core/src/repo/adapters/types.ts +5 -0
- package/node_modules/@diffci.com/core/src/repo/adapters/vue.ts +120 -15
- package/node_modules/@diffci.com/core/src/repo/graph.ts +146 -20
- package/node_modules/@diffci.com/core/src/repo/impact.ts +20 -4
- package/node_modules/@diffci.com/core/src/repo/repo-config.ts +20 -2
- package/node_modules/@diffci.com/core/src/repo/test-discovery.ts +2 -0
- package/node_modules/@diffci.com/core/src/repo/types.ts +11 -0
- package/node_modules/@diffci.com/core/src/repo/vue-scope.ts +122 -0
- package/node_modules/nanoid/bin/nanoid.cjs +0 -0
- package/node_modules/typescript/bin/tsc +0 -0
- package/node_modules/typescript/bin/tsserver +0 -0
- package/node_modules/yaml/bin.mjs +0 -0
- package/package.json +155 -138
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
5
|
+
const SERVER_INFO = { name: "diffci-mcp", version: "0.1.0" };
|
|
6
|
+
const tools = [
|
|
7
|
+
{
|
|
8
|
+
name: "diffci_check",
|
|
9
|
+
description: "Run DiffCI's observation-only AI-agent validation command. It sends nothing by default and does not run, skip, cancel, or reorder tests.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
repo: { type: "string", description: "Repository path. Defaults to the MCP server working directory." },
|
|
14
|
+
base: { type: "string", description: "Optional base commit SHA." },
|
|
15
|
+
head: { type: "string", description: "Optional head commit SHA." },
|
|
16
|
+
json: { type: "boolean", description: "Print the full JSON observation report." },
|
|
17
|
+
quiet: { type: "boolean", description: "Suppress the human summary." },
|
|
18
|
+
redactPaths: { type: "boolean", description: "Replace paths with stable digests in the report." },
|
|
19
|
+
},
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "diffci_init",
|
|
25
|
+
description: "Seed a repository with AI-agent instruction files for DiffCI.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
repo: { type: "string", description: "Repository path. Defaults to the MCP server working directory." },
|
|
30
|
+
workflow: { type: "boolean", description: "Also add the non-blocking GitHub Actions observer workflow." },
|
|
31
|
+
force: { type: "boolean", description: "Overwrite existing instruction files." },
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "diffci_verify_workflow",
|
|
38
|
+
description: "Check that a DiffCI GitHub Actions job cannot affect the rest of CI.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
repo: { type: "string", description: "Repository path. Defaults to the MCP server working directory." },
|
|
43
|
+
},
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
function cliPath() {
|
|
49
|
+
return join(dirname(import.meta.filename), "cli.js");
|
|
50
|
+
}
|
|
51
|
+
function stringArg(args, name) {
|
|
52
|
+
const value = args[name];
|
|
53
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
54
|
+
}
|
|
55
|
+
function booleanArg(args, name) {
|
|
56
|
+
return args[name] === true;
|
|
57
|
+
}
|
|
58
|
+
function commandForTool(name, args) {
|
|
59
|
+
const repo = stringArg(args, "repo");
|
|
60
|
+
switch (name) {
|
|
61
|
+
case "diffci_check": {
|
|
62
|
+
const command = ["check"];
|
|
63
|
+
if (repo)
|
|
64
|
+
command.push("--repo", resolve(repo));
|
|
65
|
+
for (const flag of ["base", "head"]) {
|
|
66
|
+
const value = stringArg(args, flag);
|
|
67
|
+
if (value)
|
|
68
|
+
command.push(`--${flag}`, value);
|
|
69
|
+
}
|
|
70
|
+
if (booleanArg(args, "json"))
|
|
71
|
+
command.push("--json");
|
|
72
|
+
if (booleanArg(args, "quiet"))
|
|
73
|
+
command.push("--quiet");
|
|
74
|
+
if (booleanArg(args, "redactPaths"))
|
|
75
|
+
command.push("--redact-paths");
|
|
76
|
+
return command;
|
|
77
|
+
}
|
|
78
|
+
case "diffci_init": {
|
|
79
|
+
const command = ["init"];
|
|
80
|
+
if (repo)
|
|
81
|
+
command.push("--repo", resolve(repo));
|
|
82
|
+
if (booleanArg(args, "workflow"))
|
|
83
|
+
command.push("--workflow");
|
|
84
|
+
if (booleanArg(args, "force"))
|
|
85
|
+
command.push("--force");
|
|
86
|
+
return command;
|
|
87
|
+
}
|
|
88
|
+
case "diffci_verify_workflow": {
|
|
89
|
+
const command = ["verify-workflow"];
|
|
90
|
+
if (repo)
|
|
91
|
+
command.push("--repo", resolve(repo));
|
|
92
|
+
return command;
|
|
93
|
+
}
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`unknown tool: ${name}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function runCli(args) {
|
|
99
|
+
return new Promise((resolveRun) => {
|
|
100
|
+
execFile(process.execPath, [cliPath(), ...args], {
|
|
101
|
+
cwd: process.cwd(),
|
|
102
|
+
env: { ...process.env, DIFFCI_MCP: "1" },
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
105
|
+
windowsHide: true,
|
|
106
|
+
}, (error, stdout, stderr) => {
|
|
107
|
+
const code = error?.code;
|
|
108
|
+
const exitCode = typeof code === "number" ? code : error ? 1 : 0;
|
|
109
|
+
resolveRun({ exitCode, stdout, stderr });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function writeMessage(message) {
|
|
114
|
+
const body = JSON.stringify(message);
|
|
115
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
116
|
+
}
|
|
117
|
+
function success(id, result) {
|
|
118
|
+
if (id === undefined)
|
|
119
|
+
return;
|
|
120
|
+
writeMessage({ jsonrpc: "2.0", id, result });
|
|
121
|
+
}
|
|
122
|
+
function failure(id, code, message) {
|
|
123
|
+
if (id === undefined)
|
|
124
|
+
return;
|
|
125
|
+
writeMessage({ jsonrpc: "2.0", id, error: { code, message } });
|
|
126
|
+
}
|
|
127
|
+
async function handle(request) {
|
|
128
|
+
const id = request.id;
|
|
129
|
+
switch (request.method) {
|
|
130
|
+
case "initialize":
|
|
131
|
+
success(id, {
|
|
132
|
+
protocolVersion: "2024-11-05",
|
|
133
|
+
capabilities: { tools: {} },
|
|
134
|
+
serverInfo: SERVER_INFO,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
case "notifications/initialized":
|
|
138
|
+
return;
|
|
139
|
+
case "tools/list":
|
|
140
|
+
success(id, { tools });
|
|
141
|
+
return;
|
|
142
|
+
case "tools/call": {
|
|
143
|
+
const params = request.params;
|
|
144
|
+
if (!params?.name) {
|
|
145
|
+
failure(id, -32602, "tools/call requires params.name");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const args = params.arguments ?? {};
|
|
149
|
+
const run = await runCli(commandForTool(params.name, args));
|
|
150
|
+
const text = [
|
|
151
|
+
run.stdout.trim(),
|
|
152
|
+
run.stderr.trim() ? `stderr:\n${run.stderr.trim()}` : "",
|
|
153
|
+
`exitCode: ${run.exitCode}`,
|
|
154
|
+
].filter(Boolean).join("\n\n");
|
|
155
|
+
success(id, {
|
|
156
|
+
content: [{ type: "text", text }],
|
|
157
|
+
isError: run.exitCode !== 0,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
default:
|
|
162
|
+
failure(id, -32601, `unknown method: ${request.method ?? ""}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let buffer = Buffer.alloc(0);
|
|
166
|
+
const decoder = new StringDecoder("utf8");
|
|
167
|
+
function readMessages() {
|
|
168
|
+
while (true) {
|
|
169
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
170
|
+
if (headerEnd === -1)
|
|
171
|
+
return;
|
|
172
|
+
const header = decoder.write(buffer.subarray(0, headerEnd));
|
|
173
|
+
const match = /^Content-Length:\s*(\d+)\s*$/im.exec(header);
|
|
174
|
+
if (!match) {
|
|
175
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const length = Number(match[1]);
|
|
179
|
+
const bodyStart = headerEnd + 4;
|
|
180
|
+
const bodyEnd = bodyStart + length;
|
|
181
|
+
if (buffer.length < bodyEnd)
|
|
182
|
+
return;
|
|
183
|
+
const body = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
184
|
+
buffer = buffer.subarray(bodyEnd);
|
|
185
|
+
const request = JSON.parse(body);
|
|
186
|
+
void handle(request).catch((error) => {
|
|
187
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
+
failure(request.id, -32603, message);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
process.stdin.on("data", (chunk) => {
|
|
193
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
194
|
+
readMessages();
|
|
195
|
+
});
|
|
196
|
+
process.stdin.resume();
|
|
@@ -22,11 +22,7 @@
|
|
|
22
22
|
import { execFileSync } from "node:child_process";
|
|
23
23
|
import { createHash } from "node:crypto";
|
|
24
24
|
import { relative, resolve } from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
import { classifyRepositoryProject, buildDependencyGraph } from "@diffci.com/core/repo/graph";
|
|
27
|
-
import { ImpactAnalyzer } from "@diffci.com/core/repo/impact";
|
|
28
|
-
import { runPathBaseline } from "@diffci.com/core/planner/path-baseline";
|
|
29
|
-
import { commandSpecToString, planSelectiveTestCommands } from "@diffci.com/core/planner/test-command";
|
|
25
|
+
import { economicsContext, evaluateEconomics } from "./economics.js";
|
|
30
26
|
import { readCiEnvironment, resolveCommitRange, } from "./context.js";
|
|
31
27
|
import { OBSERVATION_SCHEMA, redactPath, } from "./report.js";
|
|
32
28
|
import { auditWorkflows } from "./workflow-guard.js";
|
|
@@ -72,6 +68,10 @@ export function isInsideRepository(repoPath, candidate) {
|
|
|
72
68
|
}
|
|
73
69
|
export async function observe(options) {
|
|
74
70
|
const startedAt = Date.now();
|
|
71
|
+
const preObserveMs = Math.round(process.uptime() * 1000);
|
|
72
|
+
const phasesMs = {};
|
|
73
|
+
let phaseStart = performance.now();
|
|
74
|
+
const markPhase = (name) => { const now = performance.now(); phasesMs[name] = Math.round(now - phaseStart); phaseStart = now; };
|
|
75
75
|
const repoPath = resolve(options.repoPath);
|
|
76
76
|
const git = options.git ?? makeGitRunner(repoPath);
|
|
77
77
|
const ci = readCiEnvironment(options.env);
|
|
@@ -81,6 +81,7 @@ export async function observe(options) {
|
|
|
81
81
|
// Declared before `finish` closes over it: the range is part of every report, including the reports
|
|
82
82
|
// produced by failures that happen after it was resolved.
|
|
83
83
|
let range;
|
|
84
|
+
let economics;
|
|
84
85
|
const finish = (status, stage, extra) => {
|
|
85
86
|
const after = { head: headSha(git), worktree: worktreeDigest(git) };
|
|
86
87
|
const nonInterference = {
|
|
@@ -95,6 +96,7 @@ export async function observe(options) {
|
|
|
95
96
|
reportWrittenOutsideRepository: options.reportPath === undefined ? true : !isInsideRepository(repoPath, options.reportPath),
|
|
96
97
|
workflowFindings: safeAuditWorkflows(repoPath),
|
|
97
98
|
};
|
|
99
|
+
markPhase("finalization");
|
|
98
100
|
return {
|
|
99
101
|
schema: OBSERVATION_SCHEMA,
|
|
100
102
|
producedAt: new Date().toISOString(),
|
|
@@ -125,6 +127,7 @@ export async function observe(options) {
|
|
|
125
127
|
stage,
|
|
126
128
|
reason: extra.reason,
|
|
127
129
|
result: extra.result,
|
|
130
|
+
economics,
|
|
128
131
|
payload: {
|
|
129
132
|
includesFilePaths: options.redactPaths !== true,
|
|
130
133
|
includesFileContents: false,
|
|
@@ -133,7 +136,7 @@ export async function observe(options) {
|
|
|
133
136
|
pathRedaction: options.redactPaths ? "sha256-12" : undefined,
|
|
134
137
|
},
|
|
135
138
|
nonInterference,
|
|
136
|
-
timings: { totalMs: Date.now() - startedAt },
|
|
139
|
+
timings: { totalMs: Date.now() - startedAt, preObserveMs, phasesMs },
|
|
137
140
|
};
|
|
138
141
|
};
|
|
139
142
|
try {
|
|
@@ -146,12 +149,31 @@ export async function observe(options) {
|
|
|
146
149
|
if (!resolved.ok)
|
|
147
150
|
return finish("REFUSED", "context", { reason: resolved.reason });
|
|
148
151
|
range = resolved.range;
|
|
152
|
+
markPhase("context");
|
|
153
|
+
if (options.economicsJobKey || options.economicsHistory) {
|
|
154
|
+
const remote = git(["remote", "get-url", "origin"]);
|
|
155
|
+
const repository = remote.ok ? /^(?:https:\/\/github\.com\/|git@github\.com:)([^/\s]+\/[^/\s]+?)(?:\.git)?$/.exec(remote.stdout.trim())?.[1] : undefined;
|
|
156
|
+
economics = evaluateEconomics(options.economicsHistory, { repository, jobKey: options.economicsJobKey, contextKey: economicsContext(repoPath), observerVersion: options.version });
|
|
157
|
+
if (economics.decision === "BYPASS_FULL") {
|
|
158
|
+
const samples = options.economicsHistory.samples;
|
|
159
|
+
if (options.forceAnalysis || samples.some(sample => !git(["merge-base", "--is-ancestor", sample.headSha, range.baseSha]).ok))
|
|
160
|
+
economics = { ...economics, decision: "ANALYZE", reason: "Forced resampling or history is not ancestral to this change" };
|
|
161
|
+
}
|
|
162
|
+
markPhase("economics");
|
|
163
|
+
if (economics.decision === "BYPASS_FULL")
|
|
164
|
+
return finish("REFUSED", "eligibility", { reason: `ECONOMICS_FULL_BYPASS: ${economics.reason}. No selective command is authorized.` });
|
|
165
|
+
}
|
|
166
|
+
const [{ analyzeGitDelta }, { classifyRepositoryProject, buildDependencyGraph }, { ImpactAnalyzer }, { runPathBaseline }, { commandSpecToString, planSelectiveTestCommands }] = await Promise.all([
|
|
167
|
+
import("@diffci.com/core/git/git-diff"), import("@diffci.com/core/repo/graph"), import("@diffci.com/core/repo/impact"), import("@diffci.com/core/planner/path-baseline"), import("@diffci.com/core/planner/test-command"),
|
|
168
|
+
]);
|
|
169
|
+
markPhase("engineLoad");
|
|
149
170
|
// The eligibility gate is asked of the graph builder itself (classifyRepositoryProject), not of a
|
|
150
171
|
// separate list of conditions that can drift away from it. Phase 01 F3 is what that drift costs.
|
|
151
172
|
const capability = classifyRepositoryProject(repoPath);
|
|
173
|
+
markPhase("eligibility");
|
|
152
174
|
if (!capability.capable) {
|
|
153
175
|
return finish("REFUSED", "eligibility", {
|
|
154
|
-
reason: `DiffCI supports TypeScript/JavaScript projects, Vue components,
|
|
176
|
+
reason: `DiffCI supports TypeScript/JavaScript projects, Vue components, root Go modules, and conventional Maven reactors: ${capability.reason}`,
|
|
155
177
|
});
|
|
156
178
|
}
|
|
157
179
|
const deltaResult = await analyzeGitDelta({ baseSha: range.baseSha, headSha: range.headSha, repoPath });
|
|
@@ -159,10 +181,13 @@ export async function observe(options) {
|
|
|
159
181
|
return finish("REFUSED", "delta", { reason: deltaResult.error });
|
|
160
182
|
}
|
|
161
183
|
const delta = deltaResult.delta;
|
|
162
|
-
|
|
184
|
+
markPhase("delta");
|
|
185
|
+
const graphResult = await buildDependencyGraph({ repoPath, excludeDirs: EXCLUDE_DIRS, vueAnalysisCache: options.vueAnalysisCacheDir ? { directory: options.vueAnalysisCacheDir, version: `${options.version}:${options.engineSha ?? ""}` } : undefined });
|
|
186
|
+
markPhase("graph");
|
|
163
187
|
if (graphResult.graph.nodes.length === 0) {
|
|
164
188
|
return finish("REFUSED", "graph", {
|
|
165
|
-
reason: "the dependency graph came back empty - DiffCI will not propose a selection from a graph that sees none of this repository"
|
|
189
|
+
reason: "the dependency graph came back empty - DiffCI will not propose a selection from a graph that sees none of this repository" +
|
|
190
|
+
(graphResult.adapterBlockers?.length ? `; ${graphResult.adapterBlockers.join("; ")}` : ""),
|
|
166
191
|
});
|
|
167
192
|
}
|
|
168
193
|
const profile = graphResult.profile;
|
|
@@ -177,6 +202,7 @@ export async function observe(options) {
|
|
|
177
202
|
const commandPlan = impact.fallbackRequired
|
|
178
203
|
? undefined
|
|
179
204
|
: planSelectiveTestCommands(profile, selectedTests);
|
|
205
|
+
markPhase("impactAndCommands");
|
|
180
206
|
return finish("OBSERVED", "complete", {
|
|
181
207
|
result: {
|
|
182
208
|
mode: impact.fallbackRequired ? "FULL" : "SELECTIVE",
|
|
@@ -187,6 +213,9 @@ export async function observe(options) {
|
|
|
187
213
|
totalTestCount: profile.testFilePaths.length,
|
|
188
214
|
fallbackReasons: impact.fallbackReasons,
|
|
189
215
|
proposedCommands: (commandPlan?.commands ?? []).map(commandSpecToString),
|
|
216
|
+
goScope: profile.diffciConfig?.go?.scope,
|
|
217
|
+
vueScope: profile.vueScope ? { packageRoot: hashPath(profile.vueScope.packageRoot), testConfig: hashPath(profile.vueScope.testConfig) } : undefined,
|
|
218
|
+
scopedTestFiles: profile.vueScope ? profile.testFilePaths.map(hashPath) : undefined,
|
|
190
219
|
commandRefusalReason: commandPlan?.refusalReason,
|
|
191
220
|
unroutedTestPaths: (commandPlan?.unroutedPaths ?? []).map(hashPath),
|
|
192
221
|
blindSpot: profile.testUniverse?.blindSpot === true,
|
|
@@ -197,6 +226,8 @@ export async function observe(options) {
|
|
|
197
226
|
confidence: graphResult.confidence,
|
|
198
227
|
effectiveConfidence: impact.effectiveGraphConfidence,
|
|
199
228
|
durationMs: Math.round(graphResult.performance.durationMs),
|
|
229
|
+
phasesMs: graphResult.performance.phasesMs,
|
|
230
|
+
adapterMetrics: graphResult.performance.adapterMetrics,
|
|
200
231
|
},
|
|
201
232
|
pathBaseline: {
|
|
202
233
|
mode: baseline.fallbackRequired ? "FULL" : "SELECTIVE",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# DiffCI Agent Adoption Kit
|
|
2
|
+
|
|
3
|
+
Use this when adding DiffCI to a repository or opening a small adoption PR.
|
|
4
|
+
|
|
5
|
+
## One-Sentence Positioning
|
|
6
|
+
|
|
7
|
+
DiffCI gives AI coding agents a safe default CI/CD validation command.
|
|
8
|
+
|
|
9
|
+
## Maintainer Install Snippet
|
|
10
|
+
|
|
11
|
+
```md
|
|
12
|
+
## Add DiffCI for AI coding agents
|
|
13
|
+
|
|
14
|
+
Run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @diffci.com/diffci@latest init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then ask your coding agent to run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @diffci.com/diffci@latest check
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
DiffCI is observation-only by default: it analyzes the change, writes a report outside the checkout,
|
|
27
|
+
sends nothing unless explicitly configured, and does not run, skip, cancel, or reorder tests.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Agent Instruction Snippet
|
|
31
|
+
|
|
32
|
+
```md
|
|
33
|
+
Before marking changes PR-ready, run:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx @diffci.com/diffci@latest check
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use DiffCI output to understand which tests and CI paths are relevant. Do not treat a DiffCI selection
|
|
40
|
+
as permission to skip required project CI.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## GitHub Search Queries
|
|
44
|
+
|
|
45
|
+
Find repositories already prepared for coding agents:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
filename:AGENTS.md
|
|
49
|
+
filename:CLAUDE.md
|
|
50
|
+
path:.cursor/rules
|
|
51
|
+
filename:copilot-instructions.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Prioritize repositories that:
|
|
55
|
+
|
|
56
|
+
- use JavaScript, TypeScript, Vue, or Go in a normal GitHub Actions workflow;
|
|
57
|
+
- already accept small docs/config PRs;
|
|
58
|
+
- have active maintainers and recent CI runs;
|
|
59
|
+
- already document agent behavior.
|
|
60
|
+
|
|
61
|
+
Avoid repositories where:
|
|
62
|
+
|
|
63
|
+
- CI is security-sensitive and maintainers ask not to add tools;
|
|
64
|
+
- there is no clear test command or GitHub Actions setup;
|
|
65
|
+
- the project is inactive.
|
|
66
|
+
|
|
67
|
+
## Small PR Template
|
|
68
|
+
|
|
69
|
+
```md
|
|
70
|
+
Title: Add optional DiffCI instructions for AI coding agents
|
|
71
|
+
|
|
72
|
+
This adds an optional instruction for coding agents to run DiffCI before marking changes PR-ready.
|
|
73
|
+
|
|
74
|
+
DiffCI is observation-only by default:
|
|
75
|
+
|
|
76
|
+
- it analyzes the change and writes a local report;
|
|
77
|
+
- it sends nothing without explicit configuration;
|
|
78
|
+
- it does not run, skip, cancel, or reorder tests;
|
|
79
|
+
- the repository's existing required CI remains authoritative.
|
|
80
|
+
|
|
81
|
+
Default command:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx @diffci.com/diffci@latest check
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This PR does not make DiffCI a required check.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Links
|
|
91
|
+
|
|
92
|
+
- Agent docs: https://diffci.com/docs/ai-agents.html
|
|
93
|
+
- llms.txt: https://diffci.com/llms.txt
|
|
94
|
+
- npm: https://www.npmjs.com/package/@diffci.com/diffci
|
|
95
|
+
- GitHub release: https://github.com/DiffCI/DiffCI.com/releases/latest
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Agent Adoption Targets
|
|
2
|
+
|
|
3
|
+
Generated 2026-09-21 from GitHub search for repositories already centered on `AGENTS.md`,
|
|
4
|
+
`CLAUDE.md`, Cursor/Codex/Claude Code workflows, or agent instruction templates.
|
|
5
|
+
|
|
6
|
+
Do not open drive-by PRs blindly. Review each repository's contribution policy first and use the
|
|
7
|
+
small optional-instruction PR from [`agent-adoption-kit.md`](agent-adoption-kit.md).
|
|
8
|
+
|
|
9
|
+
## Best First Targets
|
|
10
|
+
|
|
11
|
+
| Repository | Why it fits |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| [`agentsmd/agents.md`](https://github.com/agentsmd/agents.md) | Defines the `AGENTS.md` convention. A DiffCI example would reach the broadest agent-instruction audience. |
|
|
14
|
+
| [`FerroxLabs/agents-md`](https://github.com/FerroxLabs/agents-md) | Explicitly targets Claude Code, Codex, Gemini, Cursor, and verification loops. |
|
|
15
|
+
| [`ciembor/agent-rules-books`](https://github.com/ciembor/agent-rules-books) | Curated rules for AI coding agents; good fit for a validation command snippet. |
|
|
16
|
+
| [`jbarbier/CLAUDE.md`](https://github.com/jbarbier/CLAUDE.md) | Drop-in Claude/Codex/Cursor instruction file; likely accepts concise validation guidance. |
|
|
17
|
+
| [`agent-sh/agnix`](https://github.com/agent-sh/agnix) | Linter/LSP for agent instruction files; possible future integration target. |
|
|
18
|
+
| [`jsynowiec/node-typescript-boilerplate`](https://github.com/jsynowiec/node-typescript-boilerplate) | Real Node/TypeScript boilerplate with GitHub Actions and `AGENTS.md`, useful as a practical adoption example. |
|
|
19
|
+
|
|
20
|
+
## Secondary Targets
|
|
21
|
+
|
|
22
|
+
| Repository | Why it fits |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| [`BayramAnnakov/claude-reflect`](https://github.com/BayramAnnakov/claude-reflect) | Syncs learning into `CLAUDE.md` and `AGENTS.md`; validation-command guidance may fit. |
|
|
25
|
+
| [`josix/awesome-claude-md`](https://github.com/josix/awesome-claude-md) | Curated collection; submit DiffCI as a validation-pattern example. |
|
|
26
|
+
| [`TheDecipherist/claude-code-mastery`](https://github.com/TheDecipherist/claude-code-mastery) | Guide-style repo; useful place for the agent-safe validation command. |
|
|
27
|
+
| [`microsoft/skills`](https://github.com/microsoft/skills) | Agent skills and MCP ecosystem. Higher bar; review contribution rules before proposing. |
|
|
28
|
+
| [`mxyhi/ok-skills`](https://github.com/mxyhi/ok-skills) | Curated skills/playbooks for Codex, Claude Code, Cursor, and other tools. |
|
|
29
|
+
|
|
30
|
+
## PR Order
|
|
31
|
+
|
|
32
|
+
1. Start with documentation/example repos, not large application repos.
|
|
33
|
+
2. Add only an optional validation instruction, never a required check.
|
|
34
|
+
3. Link to `https://diffci.com/docs/ai-agents.html` and `https://diffci.com/llms.txt`.
|
|
35
|
+
4. State that DiffCI sends nothing and changes no CI behavior by default.
|
|
36
|
+
5. If maintainers ask for a workflow, suggest `npx @diffci.com/diffci@latest init --workflow`.
|
|
37
|
+
|
|
38
|
+
## Minimal Patch Shape
|
|
39
|
+
|
|
40
|
+
Add this to the repository's agent instruction file:
|
|
41
|
+
|
|
42
|
+
```md
|
|
43
|
+
Before marking changes PR-ready, run:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @diffci.com/diffci@latest check
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
DiffCI is observation-only by default: it analyzes the change, writes a report outside the checkout,
|
|
50
|
+
sends nothing unless explicitly configured, and does not run, skip, cancel, or reorder tests. Existing
|
|
51
|
+
required CI remains authoritative.
|
|
52
|
+
```
|
package/docs/ai-agents.md
CHANGED
|
@@ -42,6 +42,29 @@ Then verify the workflow:
|
|
|
42
42
|
npx @diffci.com/diffci@latest verify-workflow
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## Copy-Paste Adoption
|
|
46
|
+
|
|
47
|
+
For a maintainer-facing snippet, PR template, and GitHub search queries for repositories that already
|
|
48
|
+
use agent instruction files, see [`agent-adoption-kit.md`](agent-adoption-kit.md).
|
|
49
|
+
For a first-pass list of repositories to review before opening PRs, see
|
|
50
|
+
[`agent-adoption-targets.md`](agent-adoption-targets.md).
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
|
|
54
|
+
- [`examples/agent-node`](../examples/agent-node)
|
|
55
|
+
- [`examples/agent-python`](../examples/agent-python)
|
|
56
|
+
- [`examples/agent-monorepo`](../examples/agent-monorepo)
|
|
57
|
+
|
|
58
|
+
## MCP
|
|
59
|
+
|
|
60
|
+
Agents that support MCP can run DiffCI as a native stdio tool server:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx -p @diffci.com/diffci@latest diffci-mcp
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See [`mcp.md`](mcp.md).
|
|
67
|
+
|
|
45
68
|
## Agent Policy
|
|
46
69
|
|
|
47
70
|
Agents should:
|