madeby 0.3.6 → 0.3.7

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
@@ -44,6 +44,8 @@ npm i -g madeby
44
44
  madeby init Set MadeBy up: write .madeby/policy.json + the CI disclosure check.
45
45
  madeby check [<range>] The gate: do commits meet .madeby/policy.json? (a verdict — exit code
46
46
  gates). <range> e.g. origin/main..HEAD — a PR's own commits. --json too.
47
+ madeby coverage [<range>] The scoreboard: how much of the repo's authorship is disclosed (known)
48
+ vs unknown, the human/with_ai/ai mix, and the asserted/verified split.
47
49
  madeby who [<path>] Made by whom? What's on the record about who made this — one file's
48
50
  (alias: whom) origin, or the whole repo with no path. --lines=A-B to scope. --json too.
49
51
  madeby ai [tool] Made by AI: attest AI authorship of HEAD (an Authored-by-ai trailer).
@@ -77,6 +79,33 @@ In CI, omit `<range>` — on a pull-request build `madeby check` **auto-scopes t
77
79
  commits** from the CI environment (GitHub today; other hosts via their adapter). Pass an explicit
78
80
  `<range>` to override.
79
81
 
82
+ ### `madeby coverage` — the authorship scoreboard
83
+
84
+ `check` is the gate; `coverage` is the picture. Run it in any repo to see how much of your
85
+ history's authorship is **on the record** — no policy, no config:
86
+
87
+ ```sh
88
+ madeby coverage # scores recent history
89
+ madeby coverage origin/main..HEAD # or a range
90
+ ```
91
+
92
+ ```
93
+ authorship coverage — last 500 commits (estimate · disclosure, not detection)
94
+ known 62% ████████████░░░░░░░░ disclosed — on the record
95
+ unknown 38% ███████░░░░░░░░░░░░░ no disclosure — origin unknown, never guessed
96
+
97
+ of the 310 known: human 41% · with_ai 38% · ai 21%
98
+ trust: asserted 88% · verified 12% — self-reported; sign commits to verify
99
+
100
+ → 38% unknown. Require disclosure going forward: madeby init --required
101
+ ```
102
+
103
+ It is a **labeled estimate**, honest by construction: an undisclosed commit is **unknown**, never
104
+ inferred as human — disclosure, not detection. The human/with_ai/ai mix is reported *of the known*,
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
107
+ `madeby init --required` to close it going forward.
108
+
80
109
  ### `madeby me` — affirm human authorship
81
110
 
82
111
  Disclosure runs both ways: if you wrote code yourself, affirm it so your work is *disclosed-human*,
package/dist/madeby.mjs CHANGED
@@ -1992,9 +1992,20 @@ function whoCommand(args) {
1992
1992
  }
1993
1993
 
1994
1994
  // src/commands/coverage.ts
1995
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
1996
+ import { join as join5 } from "node:path";
1997
+ function policyIsRequired(root) {
1998
+ const p = join5(root, ".madeby", "policy.json");
1999
+ if (!existsSync4(p)) return false;
2000
+ try {
2001
+ return parseDisclosurePolicy(JSON.parse(readFileSync6(p, "utf8"))).mode === "required";
2002
+ } catch {
2003
+ return false;
2004
+ }
2005
+ }
1995
2006
  var pct = (n, d) => d > 0 ? Math.round(n / d * 100) : 0;
1996
2007
  var bar = (p) => "\u2588".repeat(Math.round(p / 5)) + "\u2591".repeat(20 - Math.round(p / 5));
1997
- function render(r) {
2008
+ function render(r, required) {
1998
2009
  const knownPct = pct(r.known, r.total);
1999
2010
  const unknownPct = 100 - knownPct;
2000
2011
  console.log(`authorship coverage \u2014 ${r.scope} (estimate \xB7 disclosure, not detection)`);
@@ -2013,8 +2024,11 @@ function render(r) {
2013
2024
  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
2025
  }
2015
2026
  if (r.unknown > 0) {
2016
- console.log(`
2017
- \u2192 ${unknownPct}% unknown. Require disclosure going forward: madeby init --required`);
2027
+ console.log(
2028
+ required ? `
2029
+ \u2192 ${unknownPct}% unknown. Disclosure is already required on pull requests \u2014 the unknown is history the gate didn\u2019t cover (e.g. from before it was on).` : `
2030
+ \u2192 ${unknownPct}% unknown. Require disclosure going forward: madeby init --required`
2031
+ );
2018
2032
  }
2019
2033
  }
2020
2034
  function coverageCommand(args) {
@@ -2031,12 +2045,12 @@ function coverageCommand(args) {
2031
2045
  console.log(JSON.stringify(r, null, 2));
2032
2046
  return;
2033
2047
  }
2034
- render(r);
2048
+ render(r, policyIsRequired(root));
2035
2049
  }
2036
2050
 
2037
2051
  // src/init-repo.ts
2038
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
2039
- import { dirname as dirname2, join as join5 } from "node:path";
2052
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
2053
+ import { dirname as dirname2, join as join6 } from "node:path";
2040
2054
  var ACO_VERSION = 1;
2041
2055
  var ACO_OPEN = `<!-- madeby:aco v${ACO_VERSION} -->`;
2042
2056
  var ACO_CLOSE = `<!-- /madeby:aco -->`;
@@ -2087,7 +2101,7 @@ function replaceAcoBlock(content, loc) {
2087
2101
  return content.slice(0, loc.start) + ACO_BLOCK + content.slice(loc.end);
2088
2102
  }
2089
2103
  function writeIfAbsent(abs, rel, content, created, skipped) {
2090
- if (existsSync4(abs)) {
2104
+ if (existsSync5(abs)) {
2091
2105
  skipped.push(rel);
2092
2106
  return;
2093
2107
  }
@@ -2095,15 +2109,39 @@ function writeIfAbsent(abs, rel, content, created, skipped) {
2095
2109
  writeFileSync2(abs, content);
2096
2110
  created.push(rel);
2097
2111
  }
2112
+ function ensurePolicy(abs, rel, mode, res) {
2113
+ if (!existsSync5(abs)) {
2114
+ mkdirSync2(dirname2(abs), { recursive: true });
2115
+ writeFileSync2(abs, JSON.stringify({ version: 0, mode }, null, 2) + "\n");
2116
+ res.created.push(rel);
2117
+ return { mode, malformed: false };
2118
+ }
2119
+ let raw;
2120
+ try {
2121
+ raw = JSON.parse(readFileSync7(abs, "utf8"));
2122
+ } catch {
2123
+ res.skipped.push(rel);
2124
+ return { mode: "advisory", malformed: true };
2125
+ }
2126
+ const isObject = !!raw && typeof raw === "object" && !Array.isArray(raw);
2127
+ const current = parseDisclosurePolicy(raw).mode;
2128
+ if (mode === "required" && current !== "required" && isObject) {
2129
+ writeFileSync2(abs, JSON.stringify({ ...raw, mode: "required" }, null, 2) + "\n");
2130
+ res.raised.push(rel);
2131
+ return { mode: "required", malformed: false };
2132
+ }
2133
+ res.skipped.push(rel);
2134
+ return { mode: current, malformed: !isObject };
2135
+ }
2098
2136
  function ensureAco(abs, rel, res, opts) {
2099
- if (!existsSync4(abs)) {
2137
+ if (!existsSync5(abs)) {
2100
2138
  if (!opts.createIfAbsent) return;
2101
2139
  mkdirSync2(dirname2(abs), { recursive: true });
2102
2140
  writeFileSync2(abs, AGENTS_CONVENTION);
2103
2141
  res.created.push(rel);
2104
2142
  return;
2105
2143
  }
2106
- const content = readFileSync6(abs, "utf8");
2144
+ const content = readFileSync7(abs, "utf8");
2107
2145
  const status = acoStatus(content);
2108
2146
  if (status === "absent") {
2109
2147
  const sep = content.endsWith("\n") ? "" : "\n";
@@ -2129,14 +2167,21 @@ function initRepo(root, opts = {}) {
2129
2167
  const merged = [];
2130
2168
  const updated = [];
2131
2169
  const outdated = [];
2132
- const res = { created, skipped, merged, updated, outdated };
2133
- writeIfAbsent(join5(root, ".madeby", "policy.json"), ".madeby/policy.json", JSON.stringify({ version: 0, mode }, null, 2) + "\n", created, skipped);
2134
- ensureAco(join5(root, "AGENTS.md"), "AGENTS.md", res, { createIfAbsent: true, update });
2135
- ensureAco(join5(root, "CLAUDE.md"), "CLAUDE.md", res, { createIfAbsent: false, update });
2170
+ const raised = [];
2171
+ const res = { created, skipped, merged, updated, outdated, raised };
2172
+ const policy = ensurePolicy(join6(root, ".madeby", "policy.json"), ".madeby/policy.json", mode, res);
2173
+ ensureAco(join6(root, "AGENTS.md"), "AGENTS.md", res, { createIfAbsent: true, update });
2174
+ ensureAco(join6(root, "CLAUDE.md"), "CLAUDE.md", res, { createIfAbsent: false, update });
2136
2175
  if (host === "github") {
2137
- writeIfAbsent(join5(root, ".github", "workflows", "disclosure.yml"), ".github/workflows/disclosure.yml", GH_WORKFLOW, created, skipped);
2176
+ writeIfAbsent(join6(root, ".github", "workflows", "disclosure.yml"), ".github/workflows/disclosure.yml", GH_WORKFLOW, created, skipped);
2138
2177
  }
2139
2178
  const nextSteps = ["Commit the created files."];
2179
+ if (raised.length > 0) {
2180
+ nextSteps.push('Raised .madeby/policy.json to "required" \u2014 undisclosed commits now fail the check. To make it block merges, also mark the check required on the protected branch (see madeby://guide/enforce).');
2181
+ }
2182
+ if (policy.malformed) {
2183
+ nextSteps.push('.madeby/policy.json is not a valid policy, so the check treats it as advisory. It was left untouched \u2014 fix it (e.g. { "version": 0, "mode": "required" }) to enforce.');
2184
+ }
2140
2185
  if (created.includes("AGENTS.md")) {
2141
2186
  nextSteps.push("An AGENTS.md ACO convention was added \u2014 agents that read it disclose authorship (e.g. 'npx madeby@latest ai [tool]'). Tune it to your repo's tools.");
2142
2187
  }
@@ -2154,12 +2199,12 @@ function initRepo(root, opts = {}) {
2154
2199
  } else if (host !== "github") {
2155
2200
  nextSteps.push(`Add a CI step that runs 'npx madeby check' on pull requests for ${host}.`);
2156
2201
  }
2157
- if (mode !== "required") {
2158
- nextSteps.push(`When ready to enforce: set mode to "required", then make the check required on the protected branch \u2014 the host's control plane (GitHub: gh api branch protection; Azure: a build-validation policy). See the MCP resource madeby://guide/enforce for exact commands.`);
2202
+ if (policy.mode !== "required") {
2203
+ nextSteps.push(`When ready to enforce: run 'madeby init --required' (or set "mode": "required" in .madeby/policy.json), then make the check required on the protected branch \u2014 the host's control plane (GitHub: gh api branch protection; Azure: a build-validation policy). See the MCP resource madeby://guide/enforce for exact commands.`);
2159
2204
  }
2160
2205
  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.");
2161
2206
  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.");
2162
- return { created, skipped, merged, updated, outdated, policyMode: mode, host, nextSteps };
2207
+ return { created, skipped, merged, updated, outdated, raised, policyMode: policy.mode, host, nextSteps };
2163
2208
  }
2164
2209
  var GH_WORKFLOW = `name: Disclosure
2165
2210
  on: pull_request
@@ -2189,6 +2234,7 @@ function initCommand(args) {
2189
2234
  for (const f of r.created) console.log(` + ${f}`);
2190
2235
  for (const f of r.merged) console.log(` ~ ${f} (merged in the ACO rule)`);
2191
2236
  for (const f of r.updated) console.log(` \u2191 ${f} (ACO convention refreshed to the current version)`);
2237
+ for (const f of r.raised) console.log(` \u2191 ${f} (mode raised to required)`);
2192
2238
  for (const f of r.outdated) console.log(` ! ${f} (ACO convention OUTDATED \u2014 run 'madeby init --update')`);
2193
2239
  for (const f of r.skipped) console.log(` \xB7 ${f} (exists \u2014 left as-is)`);
2194
2240
  console.log(`
@@ -2201,7 +2247,7 @@ madeby init \u2014 policy: ${r.policyMode}, host: ${r.host}`);
2201
2247
  import { execFileSync as execFileSync6 } from "node:child_process";
2202
2248
  import { createInterface } from "node:readline";
2203
2249
  var DEFAULT_PROTOCOL = "2024-11-05";
2204
- var SERVER_INFO = { name: "madeby", version: "0.3.6" };
2250
+ var SERVER_INFO = { name: "madeby", version: "0.3.7" };
2205
2251
  function rootFor(path) {
2206
2252
  const cwd = path && path.length ? path : process.cwd();
2207
2253
  return execFileSync6("git", ["-C", cwd, "-c", "core.quotePath=false", "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
@@ -2216,7 +2262,7 @@ var repoInput = {
2216
2262
  var TOOLS = [
2217
2263
  {
2218
2264
  name: "init",
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.",
2265
+ 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; the one change it makes is mode: "required" raising an existing advisory policy to required (it never lowers one). 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.',
2220
2266
  inputSchema: {
2221
2267
  type: "object",
2222
2268
  properties: {
@@ -2300,7 +2346,8 @@ guesses whether code is AI. To align a repo:
2300
2346
 
2301
2347
  1. **Assess** \u2014 call \`check\` (or \`who\` with no path for a repo-wide map) to see current coverage.
2302
2348
  2. **Set up** \u2014 call \`init\`: it writes \`.madeby/policy.json\` (advisory) and the CI disclosure check.
2303
- Idempotent; it never clobbers an existing policy or workflow. Then commit the created files.
2349
+ Idempotent; it never clobbers an existing policy or workflow (\`mode: "required"\` only ever raises an
2350
+ advisory policy to required). Then commit the created files.
2304
2351
  3. **Disclose going forward** \u2014 contributors disclose origin with a Co-Authored-By / Generated-by
2305
2352
  trailer (AI), an \`Authored-by-human\` trailer (work they wrote themselves \u2014 the symmetric human
2306
2353
  claim; \`madeby me\` adds it), a DCO Signed-off-by, a signed commit, or \`ai\` (record your own AI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "madeby",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
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",
@@ -41,7 +41,7 @@
41
41
  "node": ">=20"
42
42
  },
43
43
  "bin": {
44
- "madeby": "./dist/madeby.mjs"
44
+ "madeby": "dist/madeby.mjs"
45
45
  },
46
46
  "files": [
47
47
  "dist",