madeby 0.3.15 → 0.3.16
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 +20 -4
- package/dist/madeby.mjs +98 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,12 @@ sign-offs, SPDX AI-disclosure tags — and lets a project **gate PRs** on it (`m
|
|
|
27
27
|
guesses whether code is AI; no tool can do that honestly. It reports only what contributors **put on
|
|
28
28
|
the record**.
|
|
29
29
|
|
|
30
|
+
The commands `init` writes into `AGENTS.md` pin the exact madeby release (`npx madeby@0.3.16 …`),
|
|
31
|
+
like the CI file does: agents run them on developers' machines, which hold credentials, so they run
|
|
32
|
+
the release the team chose rather than whatever is newest. When a newer release is out, `init`
|
|
33
|
+
reports the rule as outdated, and `npx madeby@latest init --update` moves the pin in one commit to
|
|
34
|
+
review.
|
|
35
|
+
|
|
30
36
|
Zero hosted infrastructure — runs against your local git history, on a laptop or in any CI. Nothing
|
|
31
37
|
leaves your machine.
|
|
32
38
|
|
|
@@ -75,6 +81,11 @@ A commit **discloses who wrote it** if it carries any of:
|
|
|
75
81
|
- an **affirmative human-authorship trailer** — `Authored-by-human: <you>` (see below);
|
|
76
82
|
- a **`madeby ai`** attestation (your own AI session's witnessed spans).
|
|
77
83
|
|
|
84
|
+
It checks the same commits `coverage` scores: every non-merge commit, plus any merge where someone
|
|
85
|
+
changed what git would have produced on its own (a conflict resolved by hand). A merge git made by
|
|
86
|
+
itself authors nothing and is never checked. Disclose a resolution like any commit: `madeby me
|
|
87
|
+
--with-ai <tool>` right after committing it, or `--for` later.
|
|
88
|
+
|
|
78
89
|
A **DCO `Signed-off-by:`** line or a **commit signature** is recognized too, but on its own it says
|
|
79
90
|
who is accountable for the commit, not who wrote it, so it counts only if the policy's `accept`
|
|
80
91
|
lists it. `check` reports commits that carry only one of those apart from those that carry nothing.
|
|
@@ -127,9 +138,11 @@ model versions are listed exactly as the AI commits name them — a commit namin
|
|
|
127
138
|
(`Assisted-by: Claude Code` + `Co-Authored-By: Claude Opus 5.5`) counts under both, so those rows can
|
|
128
139
|
sum past 100%. `--by author` shows one row per commit author (grouped by email): how many of their
|
|
129
140
|
commits disclose, and the mix — who discloses and who never does. `--json` (which includes `byAuthor`) emits
|
|
130
|
-
counts only (percentages are derived at the edge, never stored). A
|
|
131
|
-
isn't scored
|
|
132
|
-
|
|
141
|
+
counts only (percentages are derived at the edge, never stored). A merge git made on its own authors nothing
|
|
142
|
+
and isn't scored, even when both sides changed the same file; a merge where someone changed what git
|
|
143
|
+
would have produced (a conflict resolved by hand) is, like any other commit — chosen by what it
|
|
144
|
+
changes, never by whether it discloses. That takes git 2.36 or later (`--remerge-diff`); with older
|
|
145
|
+
git, merges git made cleanly may be counted too, and the scope says the count is approximate. Use it to see your gap, then
|
|
133
146
|
`madeby init --required` to close it going forward.
|
|
134
147
|
|
|
135
148
|
### `madeby me` — affirm human authorship
|
|
@@ -204,7 +217,10 @@ on its own error, and never invents a stricter gate than the maintainer wrote. I
|
|
|
204
217
|
Claude Code adds a `Co-Authored-By` trailer to its commits unless a developer turns it off in their
|
|
205
218
|
personal settings — and then the `AGENTS.md` rule alone may not bring it back. So in a repo that uses
|
|
206
219
|
Claude Code (it has a `CLAUDE.md` or a `.claude/` folder; `--claude-code` to force), `madeby init`
|
|
207
|
-
sets the trailer in the shared `.claude/settings.json
|
|
220
|
+
sets the trailer in the shared `.claude/settings.json`. It looks at any depth: Claude Code reads the
|
|
221
|
+
settings of the folder it's launched from, so a monorepo whose app keeps its `CLAUDE.md` in
|
|
222
|
+
`application/` gets `application/.claude/settings.json`, and the disclosure rule merged into that
|
|
223
|
+
`CLAUDE.md`. If it finds none, it says so.
|
|
208
224
|
|
|
209
225
|
```json
|
|
210
226
|
{ "attribution": { "commit": "Co-Authored-By: Claude Code <noreply@anthropic.com>" } }
|
package/dist/madeby.mjs
CHANGED
|
@@ -59,6 +59,18 @@ function readGitLog(repoPath, limit, range) {
|
|
|
59
59
|
return [];
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
var remergeDiff;
|
|
63
|
+
function supportsRemergeDiff() {
|
|
64
|
+
if (remergeDiff === void 0) {
|
|
65
|
+
try {
|
|
66
|
+
const v = /git version (\d+)\.(\d+)/.exec(execFileSync("git", ["--version"], { encoding: "utf8" }));
|
|
67
|
+
remergeDiff = !!v && (Number(v[1]) > 2 || Number(v[1]) === 2 && Number(v[2]) >= 36);
|
|
68
|
+
} catch {
|
|
69
|
+
remergeDiff = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return remergeDiff;
|
|
73
|
+
}
|
|
62
74
|
function readAuthoredMerges(repoPath, opts = {}) {
|
|
63
75
|
try {
|
|
64
76
|
const args = [
|
|
@@ -66,7 +78,7 @@ function readAuthoredMerges(repoPath, opts = {}) {
|
|
|
66
78
|
repoPath,
|
|
67
79
|
"log",
|
|
68
80
|
"--merges",
|
|
69
|
-
"--cc",
|
|
81
|
+
supportsRemergeDiff() ? "--remerge-diff" : "--cc",
|
|
70
82
|
"--name-only",
|
|
71
83
|
`--format=${RECORD}%H${FIELD}%an${FIELD}%ae${FIELD}%G?${FIELD}%B${GROUP}`,
|
|
72
84
|
...opts.since !== void 0 && !opts.range ? [`--since=@${opts.since}`] : [],
|
|
@@ -86,6 +98,14 @@ function commitTime(repoPath, sha) {
|
|
|
86
98
|
return void 0;
|
|
87
99
|
}
|
|
88
100
|
}
|
|
101
|
+
function readScoredCommits(repoPath, opts) {
|
|
102
|
+
const limit = opts.range ? void 0 : opts.limit;
|
|
103
|
+
const authored = readGitLog(repoPath, limit, opts.range);
|
|
104
|
+
const oldest = authored.at(-1)?.sha;
|
|
105
|
+
const since = limit && oldest && authored.length >= limit ? commitTime(repoPath, oldest) : void 0;
|
|
106
|
+
const merges = authored.length > 0 && (!limit || authored.length < limit || since !== void 0) ? readAuthoredMerges(repoPath, { range: opts.range, since }) : [];
|
|
107
|
+
return { authored, merges };
|
|
108
|
+
}
|
|
89
109
|
|
|
90
110
|
// ../classify/src/classify.ts
|
|
91
111
|
var AI_PATTERNS = [
|
|
@@ -1149,7 +1169,8 @@ function readDisclosures(root, opts = {}) {
|
|
|
1149
1169
|
}
|
|
1150
1170
|
function readDisclosuresWithRejects(root, opts) {
|
|
1151
1171
|
const late = readDisclosureCommits(root, rangeHead(opts.range));
|
|
1152
|
-
const
|
|
1172
|
+
const { authored, merges } = readScoredCommits(root, { range: opts.range, limit: opts.limit ?? 200 });
|
|
1173
|
+
const commits = applyLateDisclosures([...authored, ...merges], late);
|
|
1153
1174
|
return {
|
|
1154
1175
|
commits: commits.map((c) => ({
|
|
1155
1176
|
sha: c.sha ?? "",
|
|
@@ -1396,11 +1417,7 @@ function agentName(raw) {
|
|
|
1396
1417
|
return name || raw.trim();
|
|
1397
1418
|
}
|
|
1398
1419
|
function repoCoverage(root, opts = {}) {
|
|
1399
|
-
const
|
|
1400
|
-
const authored = readGitLog(root, limit, opts.range);
|
|
1401
|
-
const oldest = authored.at(-1)?.sha;
|
|
1402
|
-
const since = limit && oldest && authored.length >= limit ? commitTime(root, oldest) : void 0;
|
|
1403
|
-
const merges = authored.length > 0 && (!limit || authored.length < limit || since !== void 0) ? readAuthoredMerges(root, { range: opts.range, since }) : [];
|
|
1420
|
+
const { authored, merges } = readScoredCommits(root, { range: opts.range, limit: opts.limit ?? 500 });
|
|
1404
1421
|
const commits = applyLateDisclosures([...authored, ...merges], readDisclosureCommits(root, rangeHead(opts.range)));
|
|
1405
1422
|
const byCategory = { human: 0, with_ai: 0, ai: 0, bot: 0 };
|
|
1406
1423
|
const byTier = { asserted: 0, verified: 0 };
|
|
@@ -1440,8 +1457,8 @@ function repoCoverage(root, opts = {}) {
|
|
|
1440
1457
|
}
|
|
1441
1458
|
return {
|
|
1442
1459
|
isEstimate: true,
|
|
1443
|
-
methodology: "commit-level authorship disclosure (estimate); undisclosed = unknown, never inferred; merges scored only when they change content of their own; a disclosure commit (Discloses:) fills in commits disclosed afterwards and isn't scored itself",
|
|
1444
|
-
scope: (opts.range ? `range ${opts.range}` : `last ${authored.length} commits`) + (merges.length ? ` + ${merges.length} merges with changes of their own` : ""),
|
|
1460
|
+
methodology: "commit-level authorship disclosure (estimate); undisclosed = unknown, never inferred; merges scored only when they change content of their own" + (supportsRemergeDiff() ? " (beyond git's automatic merge)" : " (approximate: this git predates --remerge-diff, so merges git made cleanly may be counted too)") + "; a disclosure commit (Discloses:) fills in commits disclosed afterwards and isn't scored itself",
|
|
1461
|
+
scope: (opts.range ? `range ${opts.range}` : `last ${authored.length} commits`) + (merges.length ? ` + ${merges.length} merges with changes of their own${supportsRemergeDiff() ? "" : " (approximate \u2014 git < 2.36)"}` : ""),
|
|
1445
1462
|
total: commits.length,
|
|
1446
1463
|
known,
|
|
1447
1464
|
unknown: commits.length - known,
|
|
@@ -2245,10 +2262,11 @@ function parseFor(args) {
|
|
|
2245
2262
|
if (i === -1) return void 0;
|
|
2246
2263
|
const a = args[i];
|
|
2247
2264
|
const v = a.startsWith("--for=") ? a.slice("--for=".length) : args[i + 1];
|
|
2248
|
-
return v && !v.startsWith("-") ? v :
|
|
2265
|
+
return v && !v.startsWith("-") ? v.trim() : "";
|
|
2249
2266
|
}
|
|
2250
2267
|
function discloseFor(root, target, opts) {
|
|
2251
2268
|
const none = { disclosed: [], skipped: [], lines: [] };
|
|
2269
|
+
if (!target.trim()) return { ok: false, reason: "bad-target", ...none };
|
|
2252
2270
|
let email = "";
|
|
2253
2271
|
try {
|
|
2254
2272
|
email = git4(root, ["config", "user.email"]).toLowerCase();
|
|
@@ -2298,7 +2316,7 @@ function renderDiscloseFor(cmd2, target, r) {
|
|
|
2298
2316
|
if (!r.ok) {
|
|
2299
2317
|
if (r.reason === "no-identity") console.error(`madeby ${cmd2}: set your git identity first (git config user.name / user.email).`);
|
|
2300
2318
|
else if (r.reason === "staged-changes") console.error(`madeby ${cmd2}: you have staged changes. A disclosure commit carries no content \u2014 commit or unstage them first.`);
|
|
2301
|
-
else console.error(`madeby ${cmd2}: no commits found for ${target}. Give a commit or a range, e.g. --for origin/dev..HEAD
|
|
2319
|
+
else console.error(`madeby ${cmd2}: no commits found for ${target ? `'${target}'` : "an empty --for"}. Give a commit or a range, e.g. --for origin/dev..HEAD. Nothing was changed.`);
|
|
2302
2320
|
process.exit(1);
|
|
2303
2321
|
return;
|
|
2304
2322
|
}
|
|
@@ -2328,7 +2346,7 @@ function meCommand(args) {
|
|
|
2328
2346
|
return;
|
|
2329
2347
|
}
|
|
2330
2348
|
const forTarget = parseFor(args);
|
|
2331
|
-
if (forTarget) {
|
|
2349
|
+
if (forTarget !== void 0) {
|
|
2332
2350
|
renderDiscloseFor("me", forTarget, discloseFor(root, forTarget, { category: withAi ? "with_ai" : "human", tool, model }));
|
|
2333
2351
|
return;
|
|
2334
2352
|
}
|
|
@@ -2417,7 +2435,7 @@ function aiCommand(args) {
|
|
|
2417
2435
|
}
|
|
2418
2436
|
const tool = positional[0] || "AI";
|
|
2419
2437
|
const forTarget = parseFor(args);
|
|
2420
|
-
if (forTarget) {
|
|
2438
|
+
if (forTarget !== void 0) {
|
|
2421
2439
|
renderDiscloseFor("ai", forTarget, discloseFor(root, forTarget, { category: "ai", tool, model }));
|
|
2422
2440
|
return;
|
|
2423
2441
|
}
|
|
@@ -2587,37 +2605,40 @@ function coverageCommand(args) {
|
|
|
2587
2605
|
}
|
|
2588
2606
|
|
|
2589
2607
|
// src/init-repo.ts
|
|
2608
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2590
2609
|
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2591
2610
|
import { dirname as dirname2, join as join8 } from "node:path";
|
|
2592
2611
|
|
|
2593
2612
|
// src/version.ts
|
|
2594
|
-
var VERSION = "0.3.
|
|
2613
|
+
var VERSION = "0.3.16";
|
|
2595
2614
|
|
|
2596
2615
|
// src/init-repo.ts
|
|
2597
|
-
var ACO_VERSION =
|
|
2616
|
+
var ACO_VERSION = 3;
|
|
2598
2617
|
var ACO_OPEN = `<!-- madeby:aco v${ACO_VERSION} -->`;
|
|
2599
2618
|
var ACO_CLOSE = `<!-- /madeby:aco -->`;
|
|
2600
2619
|
var ACO_OPEN_RE = /<!-- madeby:aco(?: v(\d+))? -->/;
|
|
2620
|
+
var PKG = `madeby@${VERSION}`;
|
|
2601
2621
|
var ACO_BODY = `## Authorship disclosure (ACO)
|
|
2602
2622
|
|
|
2603
2623
|
When you author or modify content here, **disclose who authored it** \u2014 per
|
|
2604
2624
|
[ACO, the Authorship Certificate of Origin](https://github.com/MadeByFYI/MadeBy/blob/main/ACO.md).
|
|
2605
2625
|
After you commit, run the command for the honest category (no install, no setup):
|
|
2606
2626
|
|
|
2607
|
-
- **ai** \u2014 an AI authored it (the committer stays accountable): \`npx
|
|
2608
|
-
- **with_ai** \u2014 a human authored it with AI assistance: \`npx
|
|
2609
|
-
- **human** \u2014 a human authored it: \`npx
|
|
2627
|
+
- **ai** \u2014 an AI authored it (the committer stays accountable): \`npx ${PKG} ai [tool]\`
|
|
2628
|
+
- **with_ai** \u2014 a human authored it with AI assistance: \`npx ${PKG} me --with-ai [tool]\`
|
|
2629
|
+
- **human** \u2014 a human authored it: \`npx ${PKG} me\`
|
|
2610
2630
|
|
|
2611
2631
|
If your platform makes the commit for you (a cloud coding agent that pushes through its own tool),
|
|
2612
2632
|
amending afterwards can be lost \u2014 put the disclosure in the message instead, with \`--print\`:
|
|
2613
|
-
\`git commit -m "<message>" -m "$(npx
|
|
2633
|
+
\`git commit -m "<message>" -m "$(npx ${PKG} ai [tool] --print)"\`.
|
|
2614
2634
|
|
|
2615
|
-
Disclose per commit, honestly. Undisclosed content stays *unknown*, never guessed.
|
|
2635
|
+
Disclose per commit, honestly. Undisclosed content stays *unknown*, never guessed. (The madeby
|
|
2636
|
+
version is pinned on purpose; \`npx madeby@latest init --update\` moves it to the newest release.)
|
|
2616
2637
|
`;
|
|
2617
2638
|
var MODEL_LINE = `
|
|
2618
2639
|
**This repo also records the model.** Name your tool as usual \u2014 madeby reads the model from the
|
|
2619
2640
|
tool's own record of the session where it can (Cursor, Copilot CLI, Claude Code), e.g.
|
|
2620
|
-
\`npx
|
|
2641
|
+
\`npx ${PKG} me --with-ai Cursor\` writes \`Assisted-by: Cursor:<model>\`. Elsewhere add
|
|
2621
2642
|
\`--model <version>\` with the exact model you are running as. If you don't know it, leave it off \u2014
|
|
2622
2643
|
never guess.
|
|
2623
2644
|
`;
|
|
@@ -2644,6 +2665,13 @@ function acoStatus(content) {
|
|
|
2644
2665
|
if (!loc) return "absent";
|
|
2645
2666
|
if (loc.version === null || loc.version < ACO_VERSION) return "outdated";
|
|
2646
2667
|
if (loc.version > ACO_VERSION) return "newer";
|
|
2668
|
+
const pin = /npx madeby@(\d+)\.(\d+)\.(\d+)/.exec(content.slice(loc.start, loc.end));
|
|
2669
|
+
if (pin) {
|
|
2670
|
+
const [a, b, c] = VERSION.split(".").map(Number);
|
|
2671
|
+
const cmp = Number(pin[1]) - a || Number(pin[2]) - b || Number(pin[3]) - c;
|
|
2672
|
+
if (cmp < 0) return "outdated";
|
|
2673
|
+
if (cmp > 0) return "newer";
|
|
2674
|
+
}
|
|
2647
2675
|
return "current";
|
|
2648
2676
|
}
|
|
2649
2677
|
function replaceAcoBlock(content, loc, block) {
|
|
@@ -2688,10 +2716,23 @@ function ensurePolicy(abs, rel, want, res) {
|
|
|
2688
2716
|
return { mode: raise ? "required" : current.mode, recordModel: modelOn || current.recordModel === true, malformed: !isObject };
|
|
2689
2717
|
}
|
|
2690
2718
|
var CLAUDE_TRAILER = "Co-Authored-By: Claude Code <noreply@anthropic.com>";
|
|
2691
|
-
function
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2719
|
+
function claudeCodeDirs(root) {
|
|
2720
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
2721
|
+
if (existsSync7(join8(root, "CLAUDE.md")) || existsSync7(join8(root, ".claude"))) dirs.add("");
|
|
2722
|
+
let files = [];
|
|
2723
|
+
try {
|
|
2724
|
+
files = execFileSync7("git", ["-C", root, "-c", "core.quotePath=false", "ls-files", "--cached", "--others", "--exclude-standard"], { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 }).split("\n");
|
|
2725
|
+
} catch {
|
|
2726
|
+
}
|
|
2727
|
+
for (const f of files) {
|
|
2728
|
+
const parts = f.split("/");
|
|
2729
|
+
const at = parts.indexOf(".claude");
|
|
2730
|
+
if (at >= 0 && at < parts.length - 1) dirs.add(parts.slice(0, at).join("/"));
|
|
2731
|
+
else if (parts[parts.length - 1] === "CLAUDE.md") dirs.add(parts.slice(0, -1).join("/"));
|
|
2732
|
+
}
|
|
2733
|
+
return [...dirs].sort();
|
|
2734
|
+
}
|
|
2735
|
+
function ensureClaudeSettings(abs) {
|
|
2695
2736
|
if (!existsSync7(abs)) {
|
|
2696
2737
|
mkdirSync2(dirname2(abs), { recursive: true });
|
|
2697
2738
|
writeFileSync2(abs, JSON.stringify({ attribution: { commit: CLAUDE_TRAILER } }, null, 2) + "\n");
|
|
@@ -2762,8 +2803,17 @@ function initRepo(root, opts = {}) {
|
|
|
2762
2803
|
const aco = { update, recordModel: policy.recordModel, modelRequested };
|
|
2763
2804
|
ensureAco(join8(root, "AGENTS.md"), "AGENTS.md", res, { createIfAbsent: true, ...aco });
|
|
2764
2805
|
ensureAco(join8(root, "CLAUDE.md"), "CLAUDE.md", res, { createIfAbsent: false, ...aco });
|
|
2765
|
-
const
|
|
2766
|
-
if (
|
|
2806
|
+
const claudeDirs = claudeCodeDirs(root);
|
|
2807
|
+
if (opts.claudeCode && !claudeDirs.includes("")) claudeDirs.unshift("");
|
|
2808
|
+
const claudeSettings = [];
|
|
2809
|
+
for (const dir of claudeDirs) {
|
|
2810
|
+
const rel = (f) => dir ? `${dir}/${f}` : f;
|
|
2811
|
+
if (dir) ensureAco(join8(root, dir, "CLAUDE.md"), rel("CLAUDE.md"), res, { createIfAbsent: false, ...aco });
|
|
2812
|
+
const file = rel(".claude/settings.json");
|
|
2813
|
+
const status = ensureClaudeSettings(join8(root, file));
|
|
2814
|
+
if (status === "created") created.push(file);
|
|
2815
|
+
claudeSettings.push({ file, status });
|
|
2816
|
+
}
|
|
2767
2817
|
if (host === "github") {
|
|
2768
2818
|
writeIfAbsent(join8(root, ".github", "workflows", "disclosure.yml"), ".github/workflows/disclosure.yml", GH_WORKFLOW, created, skipped);
|
|
2769
2819
|
} else if (host === "azure-devops") {
|
|
@@ -2780,7 +2830,7 @@ function initRepo(root, opts = {}) {
|
|
|
2780
2830
|
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.');
|
|
2781
2831
|
}
|
|
2782
2832
|
if (created.includes("AGENTS.md")) {
|
|
2783
|
-
nextSteps.push(
|
|
2833
|
+
nextSteps.push(`An AGENTS.md ACO convention was added \u2014 agents that read it disclose authorship (e.g. 'npx ${PKG} ai [tool]'), with madeby pinned to this release. Tune it to your repo's tools.`);
|
|
2784
2834
|
}
|
|
2785
2835
|
if (merged.length > 0) {
|
|
2786
2836
|
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.`);
|
|
@@ -2789,7 +2839,7 @@ function initRepo(root, opts = {}) {
|
|
|
2789
2839
|
nextSteps.push(`Refreshed an outdated ACO convention in ${updated.join(" + ")} to the current version \u2014 review the change before committing.`);
|
|
2790
2840
|
}
|
|
2791
2841
|
if (outdated.length > 0) {
|
|
2792
|
-
nextSteps.push(`${outdated.join(" + ")} carries an OUTDATED ACO convention \u2014 run 'madeby init --update' to refresh it in place (only the ACO block changes).`);
|
|
2842
|
+
nextSteps.push(`${outdated.join(" + ")} carries an OUTDATED ACO convention (an older convention or an older pinned madeby) \u2014 run 'npx madeby@latest init --update' to refresh it in place (only the ACO block changes).`);
|
|
2793
2843
|
}
|
|
2794
2844
|
if (host === "azure-devops") {
|
|
2795
2845
|
nextSteps.push("In Azure DevOps, create a pipeline from .madeby/azure-pipelines.yml (a PR-only pipeline: no credentials kept, madeby pinned) and add it to the protected branch as a build-validation policy.");
|
|
@@ -2800,12 +2850,20 @@ function initRepo(root, opts = {}) {
|
|
|
2800
2850
|
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.`);
|
|
2801
2851
|
}
|
|
2802
2852
|
nextSteps.push("Contributors disclose origin with a Co-Authored-By / Generated-by trailer (AI), an Authored-by-human trailer ('madeby me'), or an Authored-by-ai trailer ('madeby ai' \u2014 AI authored, committer accountable). A DCO Signed-off-by or a signed commit alone doesn't say who wrote the code, so it counts only if the policy's 'accept' lists it. Evidence upgrade: 'madeby ai --witness' records your own AI session's witnessed spans.");
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2853
|
+
const set = claudeSettings.filter((c) => c.status === "created" || c.status === "merged").map((c) => c.file);
|
|
2854
|
+
const kept = claudeSettings.filter((c) => c.status === "kept").map((c) => c.file);
|
|
2855
|
+
const broken = claudeSettings.filter((c) => c.status === "malformed").map((c) => c.file);
|
|
2856
|
+
if (claudeSettings.length === 0) {
|
|
2857
|
+
nextSteps.push("No Claude Code files found (a CLAUDE.md or .claude/ folder, at any depth), so Claude Code's commit trailer wasn't set. If developers use Claude Code here, run 'madeby init --claude-code'.");
|
|
2858
|
+
}
|
|
2859
|
+
if (set.length > 0) {
|
|
2860
|
+
nextSteps.push(`Claude Code now adds '${CLAUDE_TRAILER}' to its commits when launched where ${set.join(", ")} applies, for everyone \u2014 including developers who turned the trailer off in their personal settings. Their .claude/settings.local.json, or an instruction of their own, can still turn it off; the check is what holds the line.`);
|
|
2861
|
+
}
|
|
2862
|
+
if (kept.length > 0) {
|
|
2863
|
+
nextSteps.push(`${kept.join(", ")} already set${kept.length === 1 ? "s" : ""} Claude Code's commit attribution, so it was left as the team set it. Clear that setting for the default trailer, or set attribution.commit to name the AI.`);
|
|
2864
|
+
}
|
|
2865
|
+
if (broken.length > 0) {
|
|
2866
|
+
nextSteps.push(`${broken.join(", ")} isn't valid JSON, so Claude Code's commit trailer wasn't set there. Fix it and run 'madeby init' again.`);
|
|
2809
2867
|
}
|
|
2810
2868
|
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.");
|
|
2811
2869
|
return { created, skipped, merged, updated, outdated, raised, modelOn, policyMode: policy.mode, recordModel: policy.recordModel, claudeSettings, host, nextSteps };
|
|
@@ -2861,7 +2919,7 @@ function initCommand(args) {
|
|
|
2861
2919
|
const r = initRepo(root, { mode, host, update, recordModel, claudeCode });
|
|
2862
2920
|
for (const f of r.created) console.log(` + ${f}`);
|
|
2863
2921
|
for (const f of r.merged) console.log(` ~ ${f} (merged in the ACO rule)`);
|
|
2864
|
-
|
|
2922
|
+
for (const c of r.claudeSettings) if (c.status === "merged") console.log(` ~ ${c.file} (merged in Claude Code's commit trailer)`);
|
|
2865
2923
|
for (const f of r.updated) console.log(` \u2191 ${f} (ACO convention refreshed to the current version)`);
|
|
2866
2924
|
for (const f of r.raised) console.log(` \u2191 ${f} (mode raised to required)`);
|
|
2867
2925
|
for (const f of r.modelOn) console.log(` \u2191 ${f} (now records the model version)`);
|
|
@@ -2874,13 +2932,13 @@ madeby init \u2014 policy: ${r.policyMode}${r.recordModel ? " + records model" :
|
|
|
2874
2932
|
}
|
|
2875
2933
|
|
|
2876
2934
|
// src/mcp.ts
|
|
2877
|
-
import { execFileSync as
|
|
2935
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
2878
2936
|
import { createInterface } from "node:readline";
|
|
2879
2937
|
var DEFAULT_PROTOCOL = "2024-11-05";
|
|
2880
2938
|
var SERVER_INFO = { name: "madeby", version: VERSION };
|
|
2881
2939
|
function rootFor(path) {
|
|
2882
2940
|
const cwd = path && path.length ? path : process.cwd();
|
|
2883
|
-
return
|
|
2941
|
+
return execFileSync8("git", ["-C", cwd, "-c", "core.quotePath=false", "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
2884
2942
|
}
|
|
2885
2943
|
var repoInput = {
|
|
2886
2944
|
type: "object",
|
|
@@ -2963,9 +3021,9 @@ var TOOLS = [
|
|
|
2963
3021
|
const tool = args.tool;
|
|
2964
3022
|
const model = args.model;
|
|
2965
3023
|
const category = args.category;
|
|
2966
|
-
if (
|
|
3024
|
+
if (args.for !== void 0 && args.for !== null) {
|
|
2967
3025
|
const c = category === "ai" ? "ai" : category === "with_ai" || args.with_ai === true ? "with_ai" : "human";
|
|
2968
|
-
return discloseFor(root, args.for, { category: c, tool, model: c === "human" ? void 0 : model });
|
|
3026
|
+
return discloseFor(root, typeof args.for === "string" ? args.for : "", { category: c, tool, model: c === "human" ? void 0 : model });
|
|
2969
3027
|
}
|
|
2970
3028
|
if (args.print === true) {
|
|
2971
3029
|
const c = category === "ai" ? "ai" : category === "with_ai" || args.with_ai === true ? "with_ai" : "human";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "madeby",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
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",
|