madeby 0.3.7 → 0.3.8

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 CHANGED
@@ -103,7 +103,9 @@ authorship coverage — last 500 commits (estimate · disclosure, not detection
103
103
  It is a **labeled estimate**, honest by construction: an undisclosed commit is **unknown**, never
104
104
  inferred as human — disclosure, not detection. The human/with_ai/ai mix is reported *of the known*,
105
105
  never of the whole repo, and the asserted-vs-verified split shows how much is signed. `--json` emits
106
- counts only (percentages are derived at the edge, never stored). Use it to see your gap, then
106
+ counts only (percentages are derived at the edge, never stored). A plain merge authors nothing and
107
+ isn't scored; a merge that changes content of its own (a conflict resolution) is, like any other
108
+ commit — chosen by what it changes, never by whether it discloses. Use it to see your gap, then
107
109
  `madeby init --required` to close it going forward.
108
110
 
109
111
  ### `madeby me` — affirm human authorship
package/dist/madeby.mjs CHANGED
@@ -4,6 +4,18 @@
4
4
  import { execFileSync } from "node:child_process";
5
5
  var FIELD = "";
6
6
  var RECORD = "";
7
+ var GROUP = "";
8
+ var parseMeta = (rec) => {
9
+ const parts = rec.split(FIELD);
10
+ const sig = parts[3] ?? "N";
11
+ return {
12
+ sha: parts[0] ?? "",
13
+ authorName: parts[1] ?? "",
14
+ authorEmail: parts[2] ?? "",
15
+ signed: sig !== "N" && sig !== "",
16
+ message: parts.slice(4).join(FIELD).trimEnd()
17
+ };
18
+ };
7
19
  function readGitLog(repoPath, limit, range) {
8
20
  try {
9
21
  const args = [
@@ -11,7 +23,7 @@ function readGitLog(repoPath, limit, range) {
11
23
  repoPath,
12
24
  "log",
13
25
  "--no-merges",
14
- // merge commits aren't authored content — exclude them from attribution
26
+ // a merge is not authored content by default; readAuthoredMerges finds those that are
15
27
  ...limit ? ["-n", String(limit)] : [],
16
28
  // %G? is a signature-PRESENCE probe for the Disclosure Score: 'N' = unsigned; any other code
17
29
  // (G/U/E/…) = a signature is present (E = present but unverifiable here — no keyring). We only
@@ -22,19 +34,38 @@ function readGitLog(repoPath, limit, range) {
22
34
  ...range ? [range] : []
23
35
  ];
24
36
  const out = execFileSync("git", args, { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] });
25
- return out.split(RECORD).map((r) => r.replace(/^\s+/, "")).filter((r) => r.length > 0).map((rec) => {
26
- const parts = rec.split(FIELD);
27
- const sha = parts[0] ?? "";
28
- const authorName = parts[1] ?? "";
29
- const authorEmail = parts[2] ?? "";
30
- const sig = parts[3] ?? "N";
31
- const message = parts.slice(4).join(FIELD).trimEnd();
32
- return { sha, authorName, authorEmail, signed: sig !== "N" && sig !== "", message };
33
- });
37
+ return out.split(RECORD).map((r) => r.replace(/^\s+/, "")).filter((r) => r.length > 0).map(parseMeta);
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+ function readAuthoredMerges(repoPath, opts = {}) {
43
+ try {
44
+ const args = [
45
+ "-C",
46
+ repoPath,
47
+ "log",
48
+ "--merges",
49
+ "--cc",
50
+ "--name-only",
51
+ `--format=${RECORD}%H${FIELD}%an${FIELD}%ae${FIELD}%G?${FIELD}%B${GROUP}`,
52
+ ...opts.since !== void 0 && !opts.range ? [`--since=@${opts.since}`] : [],
53
+ ...opts.range ? [opts.range] : []
54
+ ];
55
+ const out = execFileSync("git", args, { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] });
56
+ return out.split(RECORD).filter((r) => r.includes(GROUP)).filter((r) => r.slice(r.lastIndexOf(GROUP) + 1).trim().length > 0).map((r) => parseMeta(r.slice(0, r.lastIndexOf(GROUP))));
34
57
  } catch {
35
58
  return [];
36
59
  }
37
60
  }
61
+ function commitTime(repoPath, sha) {
62
+ try {
63
+ const t = Number(execFileSync("git", ["-C", repoPath, "log", "-1", "--format=%ct", sha], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim());
64
+ return Number.isFinite(t) && t > 0 ? t : void 0;
65
+ } catch {
66
+ return void 0;
67
+ }
68
+ }
38
69
 
39
70
  // ../classify/src/classify.ts
40
71
  var AI_PATTERNS = [
@@ -1201,7 +1232,12 @@ function provenanceMap(root, opts = {}) {
1201
1232
 
1202
1233
  // ../analyzer/src/coverage.ts
1203
1234
  function repoCoverage(root, opts = {}) {
1204
- const commits = readGitLog(root, opts.range ? void 0 : opts.limit ?? 500, opts.range);
1235
+ const limit = opts.range ? void 0 : opts.limit ?? 500;
1236
+ const authored = readGitLog(root, limit, opts.range);
1237
+ const oldest = authored.at(-1)?.sha;
1238
+ const since = limit && oldest && authored.length >= limit ? commitTime(root, oldest) : void 0;
1239
+ const merges = authored.length > 0 && (!limit || authored.length < limit || since !== void 0) ? readAuthoredMerges(root, { range: opts.range, since }) : [];
1240
+ const commits = [...authored, ...merges];
1205
1241
  const byCategory = { human: 0, with_ai: 0, ai: 0, bot: 0 };
1206
1242
  const byTier = { asserted: 0, verified: 0 };
1207
1243
  let known = 0;
@@ -1217,8 +1253,8 @@ function repoCoverage(root, opts = {}) {
1217
1253
  }
1218
1254
  return {
1219
1255
  isEstimate: true,
1220
- methodology: "commit-level authorship disclosure (estimate); undisclosed = unknown, never inferred",
1221
- scope: opts.range ? `range ${opts.range}` : `last ${commits.length} commits`,
1256
+ methodology: "commit-level authorship disclosure (estimate); undisclosed = unknown, never inferred; merges scored only when they change content of their own",
1257
+ scope: (opts.range ? `range ${opts.range}` : `last ${authored.length} commits`) + (merges.length ? ` + ${merges.length} merges with changes of their own` : ""),
1222
1258
  total: commits.length,
1223
1259
  known,
1224
1260
  unknown: commits.length - known,
@@ -2247,7 +2283,7 @@ madeby init \u2014 policy: ${r.policyMode}, host: ${r.host}`);
2247
2283
  import { execFileSync as execFileSync6 } from "node:child_process";
2248
2284
  import { createInterface } from "node:readline";
2249
2285
  var DEFAULT_PROTOCOL = "2024-11-05";
2250
- var SERVER_INFO = { name: "madeby", version: "0.3.7" };
2286
+ var SERVER_INFO = { name: "madeby", version: "0.3.8" };
2251
2287
  function rootFor(path) {
2252
2288
  const cwd = path && path.length ? path : process.cwd();
2253
2289
  return execFileSync6("git", ["-C", cwd, "-c", "core.quotePath=false", "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "madeby",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "mcpName": "io.github.MadeByFYI/madeby",
5
5
  "description": "Disclose AI authorship of code. `npx madeby ai` adds an Authored-by-ai git trailer; AI coding agents disclose per the ACO standard (human/with_ai/ai) from an AGENTS.md convention. Disclosure, not detection. Gate PRs with `madeby check`.",
6
6
  "license": "MIT",