@diffci.com/diffci 0.1.10 → 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.
Files changed (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +301 -226
  3. package/action.yml +154 -154
  4. package/dist-client/src/client/cli.js +153 -21
  5. package/dist-client/src/client/economics.js +31 -0
  6. package/dist-client/src/client/mcp.js +196 -0
  7. package/dist-client/src/client/observe.js +40 -9
  8. package/docs/agent-adoption-kit.md +95 -0
  9. package/docs/agent-adoption-targets.md +52 -0
  10. package/docs/ai-agents.md +87 -0
  11. package/docs/claude-code.md +17 -0
  12. package/docs/codex.md +17 -0
  13. package/docs/copilot.md +18 -0
  14. package/docs/cursor.md +18 -0
  15. package/docs/grok.md +17 -0
  16. package/docs/language-support.md +118 -70
  17. package/docs/mcp.md +21 -0
  18. package/llms.txt +37 -0
  19. package/node_modules/@babel/parser/bin/babel-parser.js +0 -0
  20. package/node_modules/@diffci.com/core/README.md +9 -1
  21. package/node_modules/@diffci.com/core/dist/cache/economics-context.d.ts +2 -0
  22. package/node_modules/@diffci.com/core/dist/cache/economics-context.js +21 -0
  23. package/node_modules/@diffci.com/core/dist/cache/economics-context.js.map +1 -0
  24. package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.d.ts +5 -0
  25. package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.js +100 -0
  26. package/node_modules/@diffci.com/core/dist/cache/vue-analysis-cache.js.map +1 -0
  27. package/node_modules/@diffci.com/core/dist/planner/test-command.js +32 -0
  28. package/node_modules/@diffci.com/core/dist/planner/test-command.js.map +1 -1
  29. package/node_modules/@diffci.com/core/dist/repo/adapters/go.d.ts +3 -0
  30. package/node_modules/@diffci.com/core/dist/repo/adapters/go.js +24 -6
  31. package/node_modules/@diffci.com/core/dist/repo/adapters/go.js.map +1 -1
  32. package/node_modules/@diffci.com/core/dist/repo/adapters/index.js +2 -1
  33. package/node_modules/@diffci.com/core/dist/repo/adapters/index.js.map +1 -1
  34. package/node_modules/@diffci.com/core/dist/repo/adapters/maven.d.ts +3 -0
  35. package/node_modules/@diffci.com/core/dist/repo/adapters/maven.js +65 -0
  36. package/node_modules/@diffci.com/core/dist/repo/adapters/maven.js.map +1 -0
  37. package/node_modules/@diffci.com/core/dist/repo/adapters/types.d.ts +11 -0
  38. package/node_modules/@diffci.com/core/dist/repo/adapters/types.js.map +1 -1
  39. package/node_modules/@diffci.com/core/dist/repo/adapters/vue.js +142 -14
  40. package/node_modules/@diffci.com/core/dist/repo/adapters/vue.js.map +1 -1
  41. package/node_modules/@diffci.com/core/dist/repo/graph.d.ts +4 -0
  42. package/node_modules/@diffci.com/core/dist/repo/graph.js +169 -19
  43. package/node_modules/@diffci.com/core/dist/repo/graph.js.map +1 -1
  44. package/node_modules/@diffci.com/core/dist/repo/impact.js +22 -5
  45. package/node_modules/@diffci.com/core/dist/repo/impact.js.map +1 -1
  46. package/node_modules/@diffci.com/core/dist/repo/repo-config.d.ts +15 -0
  47. package/node_modules/@diffci.com/core/dist/repo/repo-config.js +13 -2
  48. package/node_modules/@diffci.com/core/dist/repo/repo-config.js.map +1 -1
  49. package/node_modules/@diffci.com/core/dist/repo/test-discovery.js +2 -0
  50. package/node_modules/@diffci.com/core/dist/repo/test-discovery.js.map +1 -1
  51. package/node_modules/@diffci.com/core/dist/repo/types.d.ts +17 -0
  52. package/node_modules/@diffci.com/core/dist/repo/vue-scope.d.ts +4 -0
  53. package/node_modules/@diffci.com/core/dist/repo/vue-scope.js +157 -0
  54. package/node_modules/@diffci.com/core/dist/repo/vue-scope.js.map +1 -0
  55. package/node_modules/@diffci.com/core/src/cache/economics-context.ts +19 -0
  56. package/node_modules/@diffci.com/core/src/cache/vue-analysis-cache.ts +80 -0
  57. package/node_modules/@diffci.com/core/src/planner/test-command.ts +28 -0
  58. package/node_modules/@diffci.com/core/src/repo/adapters/go.ts +24 -5
  59. package/node_modules/@diffci.com/core/src/repo/adapters/index.ts +2 -1
  60. package/node_modules/@diffci.com/core/src/repo/adapters/maven.ts +38 -0
  61. package/node_modules/@diffci.com/core/src/repo/adapters/types.ts +5 -0
  62. package/node_modules/@diffci.com/core/src/repo/adapters/vue.ts +120 -15
  63. package/node_modules/@diffci.com/core/src/repo/graph.ts +146 -20
  64. package/node_modules/@diffci.com/core/src/repo/impact.ts +20 -4
  65. package/node_modules/@diffci.com/core/src/repo/repo-config.ts +20 -2
  66. package/node_modules/@diffci.com/core/src/repo/test-discovery.ts +2 -0
  67. package/node_modules/@diffci.com/core/src/repo/types.ts +11 -0
  68. package/node_modules/@diffci.com/core/src/repo/vue-scope.ts +122 -0
  69. package/node_modules/nanoid/bin/nanoid.cjs +0 -0
  70. package/node_modules/typescript/bin/tsc +0 -0
  71. package/node_modules/typescript/bin/tsserver +0 -0
  72. package/node_modules/yaml/bin.mjs +0 -0
  73. package/package.json +155 -131
@@ -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 { analyzeGitDelta } from "@diffci.com/core/git/git-diff";
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, and root Go modules: ${capability.reason}`,
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
- const graphResult = await buildDependencyGraph({ repoPath, excludeDirs: EXCLUDE_DIRS });
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
+ ```
@@ -0,0 +1,87 @@
1
+ # DiffCI for AI Coding Agents
2
+
3
+ DiffCI is the default CI/CD validation lens agents can run before proposing, committing, or opening a
4
+ pull request.
5
+
6
+ The safe default command is:
7
+
8
+ ```bash
9
+ npx @diffci.com/diffci@latest check
10
+ ```
11
+
12
+ `check` is an alias for `observe --no-send`. It analyzes the change and prints a report summary without
13
+ running tests, skipping tests, cancelling jobs, changing the checkout, or sending data to DiffCI Cloud.
14
+
15
+ ## Install Agent Instructions
16
+
17
+ From a repository root:
18
+
19
+ ```bash
20
+ npx @diffci.com/diffci@latest init
21
+ ```
22
+
23
+ This writes:
24
+
25
+ - `AGENTS.md`
26
+ - `CLAUDE.md`
27
+ - `.cursor/rules/diffci.mdc`
28
+ - `.github/copilot-instructions.md`
29
+ - `diffci.config.json`
30
+
31
+ Existing files are kept. Pass `--force` only when you intentionally want to replace them.
32
+
33
+ To also add a non-blocking GitHub Actions observer job:
34
+
35
+ ```bash
36
+ npx @diffci.com/diffci@latest init --workflow
37
+ ```
38
+
39
+ Then verify the workflow:
40
+
41
+ ```bash
42
+ npx @diffci.com/diffci@latest verify-workflow
43
+ ```
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
+
68
+ ## Agent Policy
69
+
70
+ Agents should:
71
+
72
+ - run `diffci check` before PR-ready answers when Git and Node.js are available;
73
+ - read the printed verdict, selected tests, fallback reasons, and report path;
74
+ - use DiffCI output to choose focused follow-up validation;
75
+ - keep the repository's required CI commands authoritative.
76
+
77
+ Agents should not:
78
+
79
+ - skip required CI because DiffCI selected fewer tests;
80
+ - treat `REFUSED` or `ERROR` as a passing validation;
81
+ - configure hosted report sending unless the user explicitly supplies an endpoint and token.
82
+
83
+ ## Open Core Boundary
84
+
85
+ The agent-facing layer belongs in the open-source core: CLI, local reports, JSON output, instruction
86
+ files, and the non-blocking GitHub Action. Hosted history, organization dashboards, PR bots, policies,
87
+ team analytics, managed runners, and support belong to commercial DiffCI.
@@ -0,0 +1,17 @@
1
+ # DiffCI for Claude Code
2
+
3
+ Install repository instructions:
4
+
5
+ ```bash
6
+ npx @diffci.com/diffci@latest init
7
+ ```
8
+
9
+ Default validation command:
10
+
11
+ ```bash
12
+ npx @diffci.com/diffci@latest check
13
+ ```
14
+
15
+ Use DiffCI before PR-ready answers. `check` is observation-only: it analyzes the change, writes a
16
+ report outside the checkout, sends nothing by default, and does not run, skip, cancel, or reorder tests.
17
+ Keep the repository's required checks authoritative.
package/docs/codex.md ADDED
@@ -0,0 +1,17 @@
1
+ # DiffCI for Codex
2
+
3
+ Install repository instructions:
4
+
5
+ ```bash
6
+ npx @diffci.com/diffci@latest init
7
+ ```
8
+
9
+ Default validation command:
10
+
11
+ ```bash
12
+ npx @diffci.com/diffci@latest check
13
+ ```
14
+
15
+ Use DiffCI before PR-ready answers. `check` is observation-only: it analyzes the change, writes a
16
+ report outside the checkout, sends nothing by default, and does not run, skip, cancel, or reorder tests.
17
+ Keep the repository's required checks authoritative.
@@ -0,0 +1,18 @@
1
+ # DiffCI for GitHub Copilot
2
+
3
+ Install repository instructions:
4
+
5
+ ```bash
6
+ npx @diffci.com/diffci@latest init
7
+ ```
8
+
9
+ Default validation command:
10
+
11
+ ```bash
12
+ npx @diffci.com/diffci@latest check
13
+ ```
14
+
15
+ Use DiffCI before PR-ready answers. `init` writes `.github/copilot-instructions.md`; `check` is
16
+ observation-only: it analyzes the change, writes a report outside the checkout, sends nothing by
17
+ default, and does not run, skip, cancel, or reorder tests. Keep the repository's required checks
18
+ authoritative.
package/docs/cursor.md ADDED
@@ -0,0 +1,18 @@
1
+ # DiffCI for Cursor
2
+
3
+ Install repository instructions:
4
+
5
+ ```bash
6
+ npx @diffci.com/diffci@latest init
7
+ ```
8
+
9
+ Default validation command:
10
+
11
+ ```bash
12
+ npx @diffci.com/diffci@latest check
13
+ ```
14
+
15
+ Use DiffCI before PR-ready changes. `init` writes `.cursor/rules/diffci.mdc`; `check` is
16
+ observation-only: it analyzes the change, writes a report outside the checkout, sends nothing by
17
+ default, and does not run, skip, cancel, or reorder tests. Keep the repository's required checks
18
+ authoritative.
package/docs/grok.md ADDED
@@ -0,0 +1,17 @@
1
+ # DiffCI for Grok
2
+
3
+ Install repository instructions:
4
+
5
+ ```bash
6
+ npx @diffci.com/diffci@latest init
7
+ ```
8
+
9
+ Default validation command:
10
+
11
+ ```bash
12
+ npx @diffci.com/diffci@latest check
13
+ ```
14
+
15
+ Use DiffCI before PR-ready answers. `check` is observation-only: it analyzes the change, writes a
16
+ report outside the checkout, sends nothing by default, and does not run, skip, cancel, or reorder tests.
17
+ Keep the repository's required checks authoritative.