adsa-cli 0.1.0 → 0.1.2

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
@@ -58,6 +58,10 @@ the version checked out in front of them instead of reading files and guessing.
58
58
 
59
59
  `npx adsa-cli rubric` prints what 1, 3 and 5 mean for each.
60
60
 
61
+ `--out <dir>` puts every artifact of a run together somewhere else — the report, the
62
+ score, the badge and the history — and reads the previous score from there too, so a
63
+ repository can be audited without writing anything into it.
64
+
61
65
  ## Commands
62
66
 
63
67
  ```
package/bin/adsa.mjs CHANGED
@@ -11,13 +11,14 @@
11
11
  * adsa badge the README badge for the committed score
12
12
  */
13
13
  import { mkdirSync, realpathSync, writeFileSync } from "node:fs";
14
- import { join, resolve } from "node:path";
14
+ import { join, relative, resolve } from "node:path";
15
15
  import { pathToFileURL } from "node:url";
16
16
  import { loadConfig, resolveTarget } from "../lib/config.mjs";
17
17
  import { scan } from "../lib/scan.mjs";
18
18
  import { RUBRIC, score, scoreFile } from "../lib/score.mjs";
19
19
  import { buildTodo, html, markdown } from "../lib/report.mjs";
20
20
  import { badgeEndpoint, badgeMarkdown } from "../lib/badge.mjs";
21
+ import { exists, isDir } from "../lib/fsx.mjs";
21
22
  import { DIR, appendHistory, compare, readHistory, write } from "../lib/history.mjs";
22
23
  import { FIXES, applyFix } from "../lib/fix.mjs";
23
24
  import { evaluate, taskFile } from "../lib/eval.mjs";
@@ -46,7 +47,7 @@ Usage
46
47
 
47
48
  Options
48
49
  --json Machine-readable output
49
- --out <dir> Where to write the report (default: .adsa)
50
+ --out <dir> Write every artifact here instead of .adsa/
50
51
  --gate Exit non-zero if the score dropped, or is below --min
51
52
  --min <n> Minimum acceptable total for --gate
52
53
  --dry-run fix: show what would change, write nothing
@@ -156,9 +157,13 @@ function cmdAudit(dir, flags, out, json) {
156
157
  return 1;
157
158
  }
158
159
  const scored = score(facts, config);
159
- const outDir = flags.out || DIR;
160
+ // Everything one run writes lands together. --out is read from where the command
161
+ // was typed, so an absolute path is absolute and a relative one is relative to
162
+ // the reader, not to the repository being audited.
163
+ const artefacts = flags.out ? resolve(process.cwd(), flags.out) : join(root, DIR);
164
+ const fresh = !exists(join(artefacts, "score.json"));
160
165
  const file = scoreFile(facts, scored);
161
- const previous = readJson(join(root, DIR, "score.json"));
166
+ const previous = readJson(join(artefacts, "score.json"));
162
167
  const delta = compare(previous, file);
163
168
 
164
169
  if (flags.json) {
@@ -190,14 +195,25 @@ function cmdAudit(dir, flags, out, json) {
190
195
  }
191
196
  }
192
197
 
193
- const history = appendHistory(root, { date: file.generatedAt, total: file.total, max: file.max, dimensions: file.dimensions });
194
- write(join(root, DIR, "score.json"), file);
195
- write(join(root, DIR, "badge.json"), badgeEndpoint(file.total, file.max));
196
- const reportDir = join(root, outDir);
197
- mkdirSync(reportDir, { recursive: true });
198
- writeFileSync(join(reportDir, "report.html"), html(facts, scored, history));
199
- writeFileSync(join(reportDir, "report.md"), markdown(facts, scored));
200
- if (!flags.json && !flags.quiet) out(dim(`\nReport: ${join(outDir, "report.html")} · score: ${join(DIR, "score.json")}`));
198
+ const history = appendHistory(artefacts, { date: file.generatedAt, total: file.total, max: file.max, dimensions: file.dimensions });
199
+ write(join(artefacts, "score.json"), file);
200
+ write(join(artefacts, "badge.json"), badgeEndpoint(file.total, file.max));
201
+ mkdirSync(artefacts, { recursive: true });
202
+ writeFileSync(join(artefacts, "report.html"), html(facts, scored, history));
203
+ writeFileSync(join(artefacts, "report.md"), markdown(facts, scored));
204
+ // A path that climbs out of the current directory reads better absolute than as
205
+ // a ladder of "..".
206
+ const near = relative(process.cwd(), artefacts);
207
+ const shown = !near ? "." : near.startsWith("..") ? artefacts : near;
208
+ if (!flags.json && !flags.quiet) {
209
+ out(dim(`\nReport: ${join(shown, "report.html")} · score: ${join(shown, "score.json")}`));
210
+ // Say once what the directory is for, rather than leave someone to guess
211
+ // whether a new folder in their repository belongs in the commit.
212
+ if (fresh && !flags.out && exists(join(root, ".git"))) {
213
+ out(dim(" score.json, badge.json and history.json are meant to be committed — the badge and the gate read them."));
214
+ out(dim(" report.html and report.md are rewritten every run; ignore them if you would rather not carry them."));
215
+ }
216
+ }
201
217
 
202
218
  if (flags.gate) {
203
219
  const floor = flags.min ?? config.minScore ?? (previous ? previous.total : null);
@@ -222,8 +238,22 @@ function cmdFix(args, flags, out, json) {
222
238
  out(" your repository's own stack, and a generic codemod would do them badly.");
223
239
  return 0;
224
240
  }
225
- const { root, config, facts } = load(flags.cwd || ".");
226
- const ids = flags.all ? Object.keys(FIXES) : args;
241
+ // `audit <dir>` takes a directory, so `fix <dir>` has to as well. A positional
242
+ // that names a fix is a fix; anything else is where to write. Silently writing
243
+ // into the current folder because the argument was in the wrong place is the
244
+ // worst thing this command could do.
245
+ const named = args.filter((a) => a in FIXES);
246
+ const rest = args.filter((a) => !(a in FIXES));
247
+ if (rest.length > 1) {
248
+ out(`Unknown fix "${rest[1]}". Run \`npx adsa-cli fix --list\`.`);
249
+ return 1;
250
+ }
251
+ if (rest.length === 1 && !isDir(rest[0])) {
252
+ out(`Unknown fix "${rest[0]}", and no directory by that name. Run \`npx adsa-cli fix --list\`.`);
253
+ return 1;
254
+ }
255
+ const { root, config, facts } = load(flags.cwd || rest[0] || ".");
256
+ const ids = flags.all ? Object.keys(FIXES) : named;
227
257
  const actions = [];
228
258
  for (const id of ids) {
229
259
  try {
package/lib/history.mjs CHANGED
@@ -5,22 +5,24 @@ import { readJson } from "./fsx.mjs";
5
5
 
6
6
  export const DIR = ".adsa";
7
7
 
8
- export function historyPath(root) {
9
- return join(root, DIR, "history.json");
8
+ /** Takes the directory the run writes into, not the repository root: with --out
9
+ * that is somewhere else entirely, and the history has to travel with the score. */
10
+ export function historyPath(dir) {
11
+ return join(dir, "history.json");
10
12
  }
11
13
 
12
- export function readHistory(root) {
13
- const json = readJson(historyPath(root));
14
+ export function readHistory(dir) {
15
+ const json = readJson(historyPath(dir));
14
16
  return Array.isArray(json) ? json : [];
15
17
  }
16
18
 
17
19
  /** Appends a run, collapsing same-day reruns so the file does not grow per invocation. */
18
- export function appendHistory(root, entry) {
19
- const history = readHistory(root);
20
+ export function appendHistory(dir, entry) {
21
+ const history = readHistory(dir);
20
22
  const day = entry.date.slice(0, 10);
21
23
  const filtered = history.filter((h) => h.date.slice(0, 10) !== day);
22
24
  filtered.push(entry);
23
- write(historyPath(root), filtered);
25
+ write(historyPath(dir), filtered);
24
26
  return filtered;
25
27
  }
26
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adsa-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Score a design system on how well coding agents can use it, then fix what is missing.",
5
5
  "type": "module",
6
6
  "bin": {