madeby 0.3.4 → 0.3.6

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 (2) hide show
  1. package/dist/madeby.mjs +264 -67
  2. package/package.json +12 -11
package/dist/madeby.mjs CHANGED
@@ -37,6 +37,36 @@ function readGitLog(repoPath, limit, range) {
37
37
  }
38
38
 
39
39
  // ../classify/src/classify.ts
40
+ var AI_PATTERNS = [
41
+ { re: /claude/i, provider: "anthropic", model: "claude" },
42
+ { re: /copilot/i, provider: "github-copilot" },
43
+ { re: /\bcursor(?:agent)?\b/i, provider: "cursor" },
44
+ { re: /\bdevin\b/i, provider: "cognition" },
45
+ { re: /gemini/i, provider: "google" },
46
+ { re: /chatgpt|openai|codex|\bgpt-/i, provider: "openai" },
47
+ { re: /codeium|windsurf/i, provider: "codeium" },
48
+ { re: /\baider\b/i, provider: "aider" },
49
+ { re: /codewhisperer/i, provider: "amazon" },
50
+ { re: /tabnine/i, provider: "tabnine" }
51
+ ];
52
+ function matchAi(text) {
53
+ for (const p of AI_PATTERNS) {
54
+ if (p.re.test(text)) return { provider: p.provider, ...p.model ? { model: p.model } : {}, raw: text.trim() };
55
+ }
56
+ return null;
57
+ }
58
+ var GENERIC_AI_TRAILER = /^(?:AI|an? AI|AI (?:assistant|tool|pair)|artificial intelligence)$/i;
59
+ function genericAiDisclosure(who) {
60
+ const w = who.trim();
61
+ return GENERIC_AI_TRAILER.test(w) ? { provider: "unspecified", raw: w } : null;
62
+ }
63
+ var TRAILER_RE = /^[ \t]*(?:Co-authored-by|Generated-by|Assisted-by):[ \t]*(.+)$/gim;
64
+ var AI_AUTHOR_TRAILER = /^[ \t]*Authored-by-ai:[ \t]*(.+)$/gim;
65
+ var HUMAN_ATTEST = /^[ \t]*(?:Authored-by-human|Human-authored(?:-by)?):/im;
66
+ var CONF_WITH_AI = 0.9;
67
+ var CONF_AI = 0.85;
68
+ var CONF_HUMAN = 0.55;
69
+ var CONF_BOT = 0.95;
40
70
  var KNOWN_BOTS = /\b(?:dependabot|renovate(?:-bot)?|greenkeeper|snyk-bot|github-actions|semantic-release-bot|stainless-app|allcontributors|mergify|codecov-commenter|pre-commit-ci|imgbot|whitesource-bot|copybara)\b/i;
41
71
  function isBotIdentity(name, email) {
42
72
  const n = (name ?? "").trim();
@@ -45,6 +75,60 @@ function isBotIdentity(name, email) {
45
75
  if (e === "actions@github.com") return true;
46
76
  return KNOWN_BOTS.test(n) || KNOWN_BOTS.test(e);
47
77
  }
78
+ function classifyCommit(commit) {
79
+ const signals = [];
80
+ const aiContributors = [];
81
+ if (isBotIdentity(commit.authorName, commit.authorEmail)) {
82
+ const botText = `${commit.authorName ?? ""} ${commit.authorEmail ?? ""}`.trim();
83
+ const agentAi = botText ? matchAi(botText) : null;
84
+ if (agentAi) {
85
+ return {
86
+ class: "ai",
87
+ confidence: CONF_AI,
88
+ signals: [`author:${agentAi.provider}`],
89
+ aiContributors: [{ ...agentAi, raw: botText }]
90
+ };
91
+ }
92
+ return { class: "bot", confidence: CONF_BOT, signals: ["author:bot"], aiContributors };
93
+ }
94
+ const aiAuthored = [...commit.message.matchAll(AI_AUTHOR_TRAILER)].map((m) => m[1].trim());
95
+ if (aiAuthored.length > 0 && !HUMAN_ATTEST.test(commit.message)) {
96
+ const who = aiAuthored[0];
97
+ const c = matchAi(who) ?? { provider: "unspecified", raw: who };
98
+ return { class: "ai", confidence: CONF_AI, signals: [`authored-by-ai:${c.provider}`], aiContributors: [c] };
99
+ }
100
+ const trailers = [...commit.message.matchAll(TRAILER_RE)].map((m) => m[1].trim());
101
+ let humanCoAuthors = 0;
102
+ for (const who of trailers) {
103
+ const ai = matchAi(who) ?? genericAiDisclosure(who);
104
+ if (ai) {
105
+ aiContributors.push(ai);
106
+ signals.push(`co-authored-by:${ai.provider}`);
107
+ } else {
108
+ humanCoAuthors += 1;
109
+ }
110
+ }
111
+ for (const who of aiAuthored) {
112
+ const ai = matchAi(who) ?? { provider: "unspecified", raw: who };
113
+ aiContributors.push(ai);
114
+ signals.push(`authored-by-ai:${ai.provider}`);
115
+ }
116
+ const authorText = `${commit.authorName ?? ""} ${commit.authorEmail ?? ""}`.trim();
117
+ const authorAi = authorText ? matchAi(authorText) : null;
118
+ if (authorAi) {
119
+ aiContributors.push({ ...authorAi, raw: authorText });
120
+ signals.push(`author:${authorAi.provider}`);
121
+ }
122
+ const hasAi = aiContributors.length > 0;
123
+ const humanPresent = authorAi === null || humanCoAuthors > 0;
124
+ if (!hasAi) {
125
+ return { class: "human", confidence: CONF_HUMAN, signals, aiContributors };
126
+ }
127
+ if (authorAi !== null && !humanPresent) {
128
+ return { class: "ai", confidence: CONF_AI, signals, aiContributors };
129
+ }
130
+ return { class: "with_ai", confidence: CONF_WITH_AI, signals, aiContributors };
131
+ }
48
132
 
49
133
  // ../classify/src/benchmark/benchmark-v0.json
50
134
  var benchmark_v0_default = {
@@ -1091,7 +1175,7 @@ function provenanceMap(root, opts = {}) {
1091
1175
  }
1092
1176
  const commits = readDisclosures(root, { limit: opts.commitLimit ?? 500 });
1093
1177
  const disclosed = commits.filter((c) => c.disclosures.length > 0).length;
1094
- const pct = commits.length ? Math.round(disclosed / commits.length * 100) : 0;
1178
+ const pct2 = commits.length ? Math.round(disclosed / commits.length * 100) : 0;
1095
1179
  const scope = prefix ?? "repo";
1096
1180
  const allModels = [...new Set(witnessed.flatMap((w) => w.models))];
1097
1181
  let aiDefault;
@@ -1110,11 +1194,39 @@ function provenanceMap(root, opts = {}) {
1110
1194
  witnessed,
1111
1195
  commitCoverage: { disclosed, total: commits.length },
1112
1196
  aiDisclosureDefault: aiDefault,
1113
- summary: `${scope}: ${trackedFiles} tracked file${trackedFiles === 1 ? "" : "s"}; ${witnessed.length} with witnessed AI provenance${allModels.length ? ` (${allModels.join(", ")})` : ""}; ${pct}% of the last ${commits.length} commits disclose origin.`,
1197
+ summary: `${scope}: ${trackedFiles} tracked file${trackedFiles === 1 ? "" : "s"}; ${witnessed.length} with witnessed AI provenance${allModels.length ? ` (${allModels.join(", ")})` : ""}; ${pct2}% of the last ${commits.length} commits disclose origin.`,
1114
1198
  note: "Witnessed spans are precise (region-level, model-named); commit coverage is commit-level, not per-file authorship. Files without a witnessed span have unknown origin \u2014 disclosure, not detection: we report what's disclosed and never infer origin we can't see. Resolution is a configurable convention (ARCH \xA712)."
1115
1199
  };
1116
1200
  }
1117
1201
 
1202
+ // ../analyzer/src/coverage.ts
1203
+ function repoCoverage(root, opts = {}) {
1204
+ const commits = readGitLog(root, opts.range ? void 0 : opts.limit ?? 500, opts.range);
1205
+ const byCategory = { human: 0, with_ai: 0, ai: 0, bot: 0 };
1206
+ const byTier = { asserted: 0, verified: 0 };
1207
+ let known = 0;
1208
+ for (const c of commits) {
1209
+ const kinds = commitDisclosureKinds({ message: c.message, signed: c.signed });
1210
+ const cls = classifyCommit({ message: c.message, authorName: c.authorName, authorEmail: c.authorEmail, signed: c.signed, sha: c.sha }).class;
1211
+ const category = cls === "human" && !kinds.includes("human-attestation") ? "unknown" : cls;
1212
+ if (category === "unknown") continue;
1213
+ known += 1;
1214
+ byCategory[category] += 1;
1215
+ if (c.signed) byTier.verified += 1;
1216
+ else byTier.asserted += 1;
1217
+ }
1218
+ return {
1219
+ isEstimate: true,
1220
+ methodology: "commit-level authorship disclosure (estimate); undisclosed = unknown, never inferred",
1221
+ scope: opts.range ? `range ${opts.range}` : `last ${commits.length} commits`,
1222
+ total: commits.length,
1223
+ known,
1224
+ unknown: commits.length - known,
1225
+ byCategory,
1226
+ byTier
1227
+ };
1228
+ }
1229
+
1118
1230
  // ../analyzer/src/record-spans.ts
1119
1231
  import { execFileSync as execFileSync3 } from "node:child_process";
1120
1232
  import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync, writeFileSync } from "node:fs";
@@ -1658,7 +1770,7 @@ function parseWithAi(args) {
1658
1770
  function git3(root, args) {
1659
1771
  return execFileSync5("git", ["-C", root, ...args], { encoding: "utf8" }).trim();
1660
1772
  }
1661
- var HUMAN_ATTEST = /^[ \t]*(?:Authored-by-human|Human-authored(?:-by)?)[ \t]*:/im;
1773
+ var HUMAN_ATTEST2 = /^[ \t]*(?:Authored-by-human|Human-authored(?:-by)?)[ \t]*:/im;
1662
1774
  var ASSIST = /^[ \t]*Assisted-by[ \t]*:/im;
1663
1775
  var AI_AUTHOR = /^[ \t]*Authored-by-ai[ \t]*:/im;
1664
1776
  function attestAiAuthorship(root, opts = {}) {
@@ -1713,7 +1825,7 @@ function affirmAuthorship(root, opts = {}) {
1713
1825
  } catch {
1714
1826
  return { ...base, reason: "no-commit" };
1715
1827
  }
1716
- const hasHuman = HUMAN_ATTEST.test(message);
1828
+ const hasHuman = HUMAN_ATTEST2.test(message);
1717
1829
  const hasAssist = ASSIST.test(message);
1718
1830
  const trailers = [];
1719
1831
  const added = [];
@@ -1879,61 +1991,101 @@ function whoCommand(args) {
1879
1991
  }
1880
1992
  }
1881
1993
 
1994
+ // src/commands/coverage.ts
1995
+ var pct = (n, d) => d > 0 ? Math.round(n / d * 100) : 0;
1996
+ var bar = (p) => "\u2588".repeat(Math.round(p / 5)) + "\u2591".repeat(20 - Math.round(p / 5));
1997
+ function render(r) {
1998
+ const knownPct = pct(r.known, r.total);
1999
+ const unknownPct = 100 - knownPct;
2000
+ console.log(`authorship coverage \u2014 ${r.scope} (estimate \xB7 disclosure, not detection)`);
2001
+ if (r.total === 0) {
2002
+ console.log(" (no commits to score)");
2003
+ return;
2004
+ }
2005
+ console.log(` known ${String(knownPct).padStart(3)}% ${bar(knownPct)} disclosed \u2014 on the record`);
2006
+ console.log(` unknown ${String(unknownPct).padStart(3)}% ${bar(unknownPct)} no disclosure \u2014 origin unknown, never guessed`);
2007
+ if (r.known > 0) {
2008
+ const c = r.byCategory;
2009
+ const parts = [`human ${pct(c.human, r.known)}%`, `with_ai ${pct(c.with_ai, r.known)}%`, `ai ${pct(c.ai, r.known)}%`];
2010
+ if (c.bot > 0) parts.push(`bot ${pct(c.bot, r.known)}%`);
2011
+ console.log(`
2012
+ of the ${r.known} known: ${parts.join(" \xB7 ")}`);
2013
+ console.log(` trust: asserted ${pct(r.byTier.asserted, r.known)}% \xB7 verified ${pct(r.byTier.verified, r.known)}% \u2014 self-reported; sign commits to verify`);
2014
+ }
2015
+ if (r.unknown > 0) {
2016
+ console.log(`
2017
+ \u2192 ${unknownPct}% unknown. Require disclosure going forward: madeby init --required`);
2018
+ }
2019
+ }
2020
+ function coverageCommand(args) {
2021
+ const json = args.includes("--json");
2022
+ const range = args.find((a) => !a.startsWith("--"));
2023
+ const root = repoRoot();
2024
+ if (!root) {
2025
+ console.error("madeby coverage: not a git repository.");
2026
+ process.exit(2);
2027
+ return;
2028
+ }
2029
+ const r = repoCoverage(root, { range });
2030
+ if (json) {
2031
+ console.log(JSON.stringify(r, null, 2));
2032
+ return;
2033
+ }
2034
+ render(r);
2035
+ }
2036
+
1882
2037
  // src/init-repo.ts
1883
2038
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
1884
2039
  import { dirname as dirname2, join as join5 } from "node:path";
1885
- var ACO_SENTINEL = "<!-- madeby:aco -->";
1886
- var GH_WORKFLOW = `name: Disclosure
1887
- on: pull_request
1888
- jobs:
1889
- madeby:
1890
- runs-on: ubuntu-latest
1891
- steps:
1892
- - uses: actions/checkout@v4
1893
- with:
1894
- fetch-depth: 0
1895
- - uses: MadeByFYI/MadeBy/packages/action@v0.2.0
1896
- `;
1897
- var AGENTS_CONVENTION = `# AGENTS.md \u2014 Authorship disclosure convention (ACO)
1898
- ${ACO_SENTINEL}
1899
-
1900
- This repository asks any AI agent \u2014 or a human working with one \u2014 to **disclose who authored the
1901
- changes they make**, per **ACO, the Authorship Certificate of Origin**
1902
- (https://github.com/MadeByFYI/MadeBy/blob/main/ACO.md).
1903
-
1904
- ## The rule
1905
-
1906
- When you author or modify content here, disclose the authorship **category** on your commit \u2014 one of:
1907
-
1908
- - \`human\` \u2014 a human authored it, no AI involvement.
1909
- - \`with_ai\` \u2014 a human authored it with disclosed AI assistance.
1910
- - \`ai\` \u2014 an AI agent authored it (a human remains accountable).
1911
-
1912
- Disclose **per commit, honestly** \u2014 the category describes what actually happened for that change.
1913
-
1914
- ## The one-line way (asserted tier \u2014 no keys, no setup)
1915
-
1916
- After you commit, run the matching command (works with no install via \`npx\`):
2040
+ var ACO_VERSION = 1;
2041
+ var ACO_OPEN = `<!-- madeby:aco v${ACO_VERSION} -->`;
2042
+ var ACO_CLOSE = `<!-- /madeby:aco -->`;
2043
+ var ACO_OPEN_RE = /<!-- madeby:aco(?: v(\d+))? -->/;
2044
+ var ACO_BODY = `## Authorship disclosure (ACO)
1917
2045
 
1918
- | Category | Command |
1919
- |-----------|----------------------------------|
1920
- | \`human\` | \`npx madeby@latest me\` |
1921
- | \`with_ai\` | \`npx madeby@latest me --with-ai <tool>\` |
1922
- | \`ai\` | \`npx madeby@latest ai [tool]\` |
1923
-
1924
- Undisclosed content stays *unknown*, never guessed. See ACO for the standard.
1925
- `;
1926
- var ACO_SECTION = `
1927
- ${ACO_SENTINEL}
1928
- ## Authorship disclosure (ACO)
1929
-
1930
- Disclose who authored your changes, per [ACO](https://github.com/MadeByFYI/MadeBy/blob/main/ACO.md).
1931
- After committing, run the command for the honest category (no install needed):
2046
+ When you author or modify content here, **disclose who authored it** \u2014 per
2047
+ [ACO, the Authorship Certificate of Origin](https://github.com/MadeByFYI/MadeBy/blob/main/ACO.md).
2048
+ After you commit, run the command for the honest category (no install, no setup):
1932
2049
 
1933
2050
  - **ai** \u2014 an AI authored it (the committer stays accountable): \`npx madeby@latest ai [tool]\`
1934
2051
  - **with_ai** \u2014 a human authored it with AI assistance: \`npx madeby@latest me --with-ai [tool]\`
1935
2052
  - **human** \u2014 a human authored it: \`npx madeby@latest me\`
2053
+
2054
+ Disclose per commit, honestly. Undisclosed content stays *unknown*, never guessed.
2055
+ `;
2056
+ var ACO_BLOCK = `${ACO_OPEN}
2057
+ ${ACO_BODY}${ACO_CLOSE}
1936
2058
  `;
2059
+ var AGENTS_CONVENTION = `# AGENTS.md
2060
+
2061
+ ${ACO_BLOCK}`;
2062
+ var ACO_SECTION = `
2063
+ ${ACO_BLOCK}`;
2064
+ function findAcoBlock(content) {
2065
+ const m = ACO_OPEN_RE.exec(content);
2066
+ if (!m) return null;
2067
+ const start = m.index;
2068
+ const version = m[1] !== void 0 ? Number(m[1]) : null;
2069
+ const closeIdx = content.indexOf(ACO_CLOSE, start);
2070
+ let end;
2071
+ if (closeIdx !== -1) {
2072
+ end = closeIdx + ACO_CLOSE.length;
2073
+ if (content[end] === "\n") end += 1;
2074
+ } else {
2075
+ end = content.length;
2076
+ }
2077
+ return { version, start, end };
2078
+ }
2079
+ function acoStatus(content) {
2080
+ const loc = findAcoBlock(content);
2081
+ if (!loc) return "absent";
2082
+ if (loc.version === null || loc.version < ACO_VERSION) return "outdated";
2083
+ if (loc.version > ACO_VERSION) return "newer";
2084
+ return "current";
2085
+ }
2086
+ function replaceAcoBlock(content, loc) {
2087
+ return content.slice(0, loc.start) + ACO_BLOCK + content.slice(loc.end);
2088
+ }
1937
2089
  function writeIfAbsent(abs, rel, content, created, skipped) {
1938
2090
  if (existsSync4(abs)) {
1939
2091
  skipped.push(rel);
@@ -1951,25 +2103,36 @@ function ensureAco(abs, rel, res, opts) {
1951
2103
  res.created.push(rel);
1952
2104
  return;
1953
2105
  }
1954
- const current = readFileSync6(abs, "utf8");
1955
- if (current.includes(ACO_SENTINEL)) {
2106
+ const content = readFileSync6(abs, "utf8");
2107
+ const status = acoStatus(content);
2108
+ if (status === "absent") {
2109
+ const sep = content.endsWith("\n") ? "" : "\n";
2110
+ writeFileSync2(abs, content + sep + ACO_SECTION);
2111
+ res.merged.push(rel);
2112
+ } else if (status === "outdated") {
2113
+ if (opts.update) {
2114
+ writeFileSync2(abs, replaceAcoBlock(content, findAcoBlock(content)));
2115
+ res.updated.push(rel);
2116
+ } else {
2117
+ res.outdated.push(rel);
2118
+ }
2119
+ } else {
1956
2120
  res.skipped.push(rel);
1957
- return;
1958
2121
  }
1959
- const sep = current.endsWith("\n") ? "\n" : "\n\n";
1960
- writeFileSync2(abs, current + sep + ACO_SECTION);
1961
- res.merged.push(rel);
1962
2122
  }
1963
2123
  function initRepo(root, opts = {}) {
1964
2124
  const mode = opts.mode ?? "advisory";
1965
2125
  const host = opts.host ?? "github";
2126
+ const update = opts.update ?? false;
1966
2127
  const created = [];
1967
2128
  const skipped = [];
1968
2129
  const merged = [];
1969
- const res = { created, skipped, merged };
2130
+ const updated = [];
2131
+ const outdated = [];
2132
+ const res = { created, skipped, merged, updated, outdated };
1970
2133
  writeIfAbsent(join5(root, ".madeby", "policy.json"), ".madeby/policy.json", JSON.stringify({ version: 0, mode }, null, 2) + "\n", created, skipped);
1971
- ensureAco(join5(root, "AGENTS.md"), "AGENTS.md", res, { createIfAbsent: true });
1972
- ensureAco(join5(root, "CLAUDE.md"), "CLAUDE.md", res, { createIfAbsent: false });
2134
+ ensureAco(join5(root, "AGENTS.md"), "AGENTS.md", res, { createIfAbsent: true, update });
2135
+ ensureAco(join5(root, "CLAUDE.md"), "CLAUDE.md", res, { createIfAbsent: false, update });
1973
2136
  if (host === "github") {
1974
2137
  writeIfAbsent(join5(root, ".github", "workflows", "disclosure.yml"), ".github/workflows/disclosure.yml", GH_WORKFLOW, created, skipped);
1975
2138
  }
@@ -1980,6 +2143,12 @@ function initRepo(root, opts = {}) {
1980
2143
  if (merged.length > 0) {
1981
2144
  nextSteps.push(`Merged the ACO disclosure rule into your existing ${merged.join(" + ")} \u2014 review the appended "Authorship disclosure (ACO)" section and tune it to your repo.`);
1982
2145
  }
2146
+ if (updated.length > 0) {
2147
+ nextSteps.push(`Refreshed an outdated ACO convention in ${updated.join(" + ")} to the current version \u2014 review the change before committing.`);
2148
+ }
2149
+ if (outdated.length > 0) {
2150
+ nextSteps.push(`${outdated.join(" + ")} carries an OUTDATED ACO convention \u2014 run 'madeby init --update' to refresh it in place (only the ACO block changes).`);
2151
+ }
1983
2152
  if (host === "azure-devops") {
1984
2153
  nextSteps.push("Add the Azure Pipelines template (packages/action/azure-pipelines-disclosure.yml) and wire it as a PR build-validation branch policy.");
1985
2154
  } else if (host !== "github") {
@@ -1990,8 +2159,19 @@ function initRepo(root, opts = {}) {
1990
2159
  }
1991
2160
  nextSteps.push("Contributors disclose origin with a Co-Authored-By / Generated-by trailer (AI), an Authored-by-human trailer ('madeby me'), an Authored-by-ai trailer ('madeby ai' \u2014 AI authored, committer accountable), a DCO Signed-off-by, or a signed commit. Evidence upgrade: 'madeby ai --witness' records your own AI session's witnessed spans.");
1992
2161
  nextSteps.push("Existing history is accounted for honestly, not fabricated: run 'madeby check' over full history to recognize what's already disclosed; pre-adoption commits stay unknown. See madeby://guide/backfill.");
1993
- return { created, skipped, merged, policyMode: mode, host, nextSteps };
2162
+ return { created, skipped, merged, updated, outdated, policyMode: mode, host, nextSteps };
1994
2163
  }
2164
+ var GH_WORKFLOW = `name: Disclosure
2165
+ on: pull_request
2166
+ jobs:
2167
+ madeby:
2168
+ runs-on: ubuntu-latest
2169
+ steps:
2170
+ - uses: actions/checkout@v4
2171
+ with:
2172
+ fetch-depth: 0
2173
+ - uses: MadeByFYI/MadeBy/packages/action@v0.2.0
2174
+ `;
1995
2175
 
1996
2176
  // src/commands/init.ts
1997
2177
  function initCommand(args) {
@@ -2002,11 +2182,14 @@ function initCommand(args) {
2002
2182
  return;
2003
2183
  }
2004
2184
  const mode = args.includes("--required") ? "required" : "advisory";
2185
+ const update = args.includes("--update");
2005
2186
  const hostIdx = args.indexOf("--host");
2006
2187
  const host = hostIdx >= 0 ? args[hostIdx + 1] : void 0;
2007
- const r = initRepo(root, { mode, host });
2188
+ const r = initRepo(root, { mode, host, update });
2008
2189
  for (const f of r.created) console.log(` + ${f}`);
2009
2190
  for (const f of r.merged) console.log(` ~ ${f} (merged in the ACO rule)`);
2191
+ for (const f of r.updated) console.log(` \u2191 ${f} (ACO convention refreshed to the current version)`);
2192
+ for (const f of r.outdated) console.log(` ! ${f} (ACO convention OUTDATED \u2014 run 'madeby init --update')`);
2010
2193
  for (const f of r.skipped) console.log(` \xB7 ${f} (exists \u2014 left as-is)`);
2011
2194
  console.log(`
2012
2195
  madeby init \u2014 policy: ${r.policyMode}, host: ${r.host}`);
@@ -2018,7 +2201,7 @@ madeby init \u2014 policy: ${r.policyMode}, host: ${r.host}`);
2018
2201
  import { execFileSync as execFileSync6 } from "node:child_process";
2019
2202
  import { createInterface } from "node:readline";
2020
2203
  var DEFAULT_PROTOCOL = "2024-11-05";
2021
- var SERVER_INFO = { name: "madeby", version: "0.3.4" };
2204
+ var SERVER_INFO = { name: "madeby", version: "0.3.6" };
2022
2205
  function rootFor(path) {
2023
2206
  const cwd = path && path.length ? path : process.cwd();
2024
2207
  return execFileSync6("git", ["-C", cwd, "-c", "core.quotePath=false", "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
@@ -2033,16 +2216,17 @@ var repoInput = {
2033
2216
  var TOOLS = [
2034
2217
  {
2035
2218
  name: "init",
2036
- description: "Align a repository with MadeBy in one call: create .madeby/policy.json (advisory by default \u2014 reports, never fails) and the CI disclosure check for the host (github default; azure-devops guided). Idempotent \u2014 never clobbers an existing policy or workflow. Returns the files created/skipped and the recommended next steps. Read the resources: madeby://guide/align (full workflow), madeby://guide/enforce (make the check required \u2014 the host's control plane, exact commands), madeby://guide/backfill (account for existing history honestly \u2014 never fabricate).",
2219
+ description: "Align a repository with MadeBy in one call: create .madeby/policy.json (advisory by default \u2014 reports, never fails) and the CI disclosure check for the host (github default; azure-devops guided). Idempotent \u2014 never clobbers an existing policy or workflow. Returns the files created/skipped and the recommended next steps. Read the resources: madeby://guide/align (full workflow), madeby://guide/enforce (make the check required \u2014 the host's control plane, exact commands), madeby://guide/backfill (account for existing history honestly \u2014 never fabricate). The ACO convention block is versioned: the result flags an `outdated` AGENTS.md/CLAUDE.md (drift); call again with update:true to refresh just that block in place \u2014 how an agent keeps the convention current.",
2037
2220
  inputSchema: {
2038
2221
  type: "object",
2039
2222
  properties: {
2040
2223
  path: { type: "string", description: "path to the repo (default: the server's working directory)" },
2041
2224
  mode: { enum: ["advisory", "required"], description: "policy mode to seed (default: advisory)" },
2042
- host: { type: "string", description: "CI host to wire: github (default) or azure-devops" }
2225
+ host: { type: "string", description: "CI host to wire: github (default) or azure-devops" },
2226
+ update: { type: "boolean", description: "refresh an outdated ACO convention block in place (drift update); default false (report only)" }
2043
2227
  }
2044
2228
  },
2045
- handler: (args) => initRepo(rootFor(args.path), { mode: args.mode, host: args.host })
2229
+ handler: (args) => initRepo(rootFor(args.path), { mode: args.mode, host: args.host, update: args.update })
2046
2230
  },
2047
2231
  {
2048
2232
  name: "check",
@@ -2050,6 +2234,12 @@ var TOOLS = [
2050
2234
  inputSchema: repoInput,
2051
2235
  handler: (args) => evaluateRepoDisclosure(rootFor(args.path), { range: args.range })
2052
2236
  },
2237
+ {
2238
+ name: "coverage",
2239
+ description: "Authorship coverage scoreboard for a repo \u2014 the honest estimate of how much of the code's authorship is ON THE RECORD (known) vs undisclosed (unknown \u2014 origin unknown, NEVER guessed), the human/with_ai/ai mix OF THE KNOWN (never of the whole repo), and the asserted-vs-verified trust split. Disclosure, not detection; the report is a labeled estimate (counts only \u2014 derive percents at the edge). Use it to show a maintainer their provenance picture and motivate requiring disclosure.",
2240
+ inputSchema: repoInput,
2241
+ handler: (args) => repoCoverage(rootFor(args.path), { range: args.range })
2242
+ },
2053
2243
  {
2054
2244
  name: "who",
2055
2245
  description: "Made by whom? Read what's ON THE RECORD about who made this. With `path`: one file's origin \u2014 witnessed AI spans (model/tool, precise) where captured, last-touch commit disclosure from git blame elsewhere (approximate, labeled), unknown otherwise. With no `path`: a repo-wide map (the orientation to read BEFORE you work) \u2014 the witnessed AI surface plus commit-level disclosure coverage, cheap (no per-file blame). The universal contract is the line range: for a symbol, turn it into a line range with your own tooling (tree-sitter/LSP/ctags) and pass it \u2014 MadeBy never parses code. Disclosure, not detection: it reports what's disclosed and never asserts 'human'.",
@@ -2305,13 +2495,17 @@ function help() {
2305
2495
  console.log(`madeby \u2014 verifiable content provenance ("made by whom?")
2306
2496
 
2307
2497
  Usage:
2308
- madeby init Set MadeBy up: write .madeby/policy.json + the CI disclosure check.
2309
- --required to enforce now; --host azure-devops for Azure Pipelines.
2498
+ madeby init Set MadeBy up: write .madeby/policy.json + the ACO AGENTS.md convention
2499
+ + the CI disclosure check. --update refreshes an outdated ACO block in
2500
+ place; --required to enforce now; --host azure-devops for Azure Pipelines.
2310
2501
  madeby check [<range>] The gate: do commits meet .madeby/policy.json? (a verdict \u2014 exit
2311
2502
  code gates). <range> e.g. origin/main..HEAD; in CI it's auto-detected.
2312
2503
  --json for a machine-readable result.
2313
2504
  madeby who [<path>] Made by whom? Read what's on the record about who made this \u2014 one
2314
2505
  (alias: whom) file's origin, or the whole repo with no path. --lines=A-B to scope.
2506
+ madeby coverage [<range>] The scoreboard: how much of your repo's authorship is disclosed (known)
2507
+ vs unknown, the human/with_ai/ai mix, and the asserted/verified split.
2508
+ --json for a machine-readable result.
2315
2509
  madeby ai [<log>] [<ref>] Made by AI: record your AI session's witnessed spans into .madeby/spans.
2316
2510
  <log> defaults to the auto-discovered Claude Code transcript.
2317
2511
  madeby me Made by me: affirm you authored HEAD yourself (an Authored-by-human
@@ -2336,6 +2530,9 @@ switch (cmd) {
2336
2530
  case "whom":
2337
2531
  whoCommand(rest);
2338
2532
  break;
2533
+ case "coverage":
2534
+ coverageCommand(rest);
2535
+ break;
2339
2536
  case "ai":
2340
2537
  aiCommand(rest);
2341
2538
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "madeby",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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",
@@ -47,18 +47,19 @@
47
47
  "dist",
48
48
  "README.md"
49
49
  ],
50
- "devDependencies": {
51
- "@types/node": "^22.10.0",
52
- "esbuild": "^0.25.0",
53
- "typescript": "^5.7.0",
54
- "vitest": "^4.1.9",
55
- "@madeby/analyzer": "0.0.0",
56
- "@madeby/core": "0.0.0"
57
- },
58
50
  "scripts": {
59
51
  "build": "esbuild src/madeby.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/madeby.mjs --banner:js='#!/usr/bin/env node' && chmod +x dist/madeby.mjs",
60
52
  "typecheck": "tsc --noEmit",
61
53
  "pretest": "pnpm build",
62
- "test": "vitest run"
54
+ "test": "vitest run",
55
+ "prepublishOnly": "pnpm build"
56
+ },
57
+ "devDependencies": {
58
+ "@madeby/analyzer": "workspace:*",
59
+ "@madeby/core": "workspace:*",
60
+ "@types/node": "^22.10.0",
61
+ "esbuild": "^0.25.0",
62
+ "typescript": "^5.7.0",
63
+ "vitest": "^4.1.9"
63
64
  }
64
- }
65
+ }