buvard 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/AGENTS.md CHANGED
@@ -11,8 +11,10 @@ repo `git.nech.pl/pln/buvard` (visibility toggle is the owner's call).
11
11
  structure -8, servile/meta -10; punctuation budgets (1 tiret cadratin/500w,
12
12
  1 !/1000w). **Voice rule: hits are review candidates, never verdicts — the
13
13
  author's voice always wins.** buvard is a style linter, never an AI detector.
14
- - **`bin/buvard.js`** — CLI (`check`, `--json`), frontmatter stripped. Tests:
15
- `npm test` (node:test, 6/6 passing).
14
+ - **`bin/buvard.js`** — CLI (`check`, `score`, `--json`, `--hidden`, `--help`,
15
+ `--version`), frontmatter stripped. Walks skip dot-directories and never
16
+ follow directory symlinks; unreadable dirs warn, never crash. Tests:
17
+ `npm test` (node:test, 24 passing).
16
18
  - **`docs/research.md`** — the clustering study. **`docs/research-litreview.md`**
17
19
  — literature review (2026-11-07): detection literature unreliable on short/
18
20
  paraphrased text; 61.22% FP rate of commercial detectors on non-native essays
package/README.md CHANGED
@@ -10,7 +10,7 @@ bilingual frameworks, one EN framework), scored by a ratified barème, and
10
10
  tested. Zero config, by design — like black, the formatting is the tool's;
11
11
  the words stay the author's.
12
12
 
13
- ![Anatomie d'une bavure — one slop sentence, three hits, 100 → 77](docs/anatomie-du-slop.svg)
13
+ ![Anatomie d'une bavure — one slop sentence, three hits, 100 → 77](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4ODAgNDcwIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0idCBkIj4KICA8dGl0bGUgaWQ9InQiPkFuYXRvbWllIGQndW5lIGJhdnVyZTwvdGl0bGU+CiAgPGRlc2MgaWQ9ImQiPk9uZSBzbG9wIHNlbnRlbmNlIGRpc3NlY3RlZDogYnV2YXJkJ3MgY2F0YWxvZyBmbGFncyBhIGJpbmFyeSBjb250cmFzdCAo4oiSOCksIGEgc2VydmlsZSBwcm9taXNlICjiiJIxMCkgYW5kIGEgc2Vjb25kIHRpcmV0IGNhZHJhdGluIGJleW9uZCBidWRnZXQgKOKIkjUpOyB0aGUgc2NvcmUgZmFsbHMgZnJvbSAxMDAgdG8gNzcuPC9kZXNjPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuaW5rICB7IGZpbGw6IzFmMmEzMzsgfQogICAgICAucm91Z2V7IGZpbGw6I2E2M2QyZjsgfQogICAgICAubXV0ZWR7IGZpbGw6IzdhNmY1ZDsgfQogICAgICAuc2VyaWZ7IGZvbnQtZmFtaWx5OiBHZW9yZ2lhLCAnVGltZXMgTmV3IFJvbWFuJywgc2VyaWY7IH0KICAgICAgLnNhbnMgeyBmb250LWZhbWlseTogJ0F2ZW5pciBOZXh0JywgJ0hlbHZldGljYSBOZXVlJywgQXJpYWwsIHNhbnMtc2VyaWY7IH0KICAgICAgLmxpbmUgeyBzdHJva2U6IzFmMmEzMzsgZmlsbDpub25lOyBzdHJva2UtbGluZWNhcDpyb3VuZDsgfQogICAgICAucm91Z2VkIHsgc3Ryb2tlOiNhNjNkMmY7IGZpbGw6bm9uZTsgc3Ryb2tlLWxpbmVjYXA6cm91bmQ7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgoKICA8IS0tIHBhcGVyIC0tPgogIDxyZWN0IHdpZHRoPSI4ODAiIGhlaWdodD0iNDcwIiBmaWxsPSIjZmFmNWVhIi8+CgogIDwhLS0gdGl0bGUgLS0+CiAgPHRleHQgeD0iOTAiIHk9IjYyIiBjbGFzcz0ic2FucyBpbmsiIGZvbnQtc2l6ZT0iMTUiIGxldHRlci1zcGFjaW5nPSI2Ij5BTkFUT01JRSBEJ1VORSBCQVZVUkU8L3RleHQ+CiAgPHBhdGggZD0iTTkwIDc0IHEgNjAgNCAxMjAgMSB0IDEyMiAtMSIgY2xhc3M9ImxpbmUiIHN0cm9rZS13aWR0aD0iMS40Ii8+CiAgPHRleHQgeD0iNzkwIiB5PSI2MiIgY2xhc3M9InNhbnMgbXV0ZWQiIGZvbnQtc2l6ZT0iMTEiIHRleHQtYW5jaG9yPSJlbmQiIGxldHRlci1zcGFjaW5nPSIyIj5idXZhcmQ8L3RleHQ+CgogIDwhLS0gdGhlIHNlbnRlbmNlLCBzZWdtZW50IGJ5IHNlZ21lbnQgc28gdGhlIGJveGVzIGFsaWduIGV4YWN0bHkgLS0+CiAgPGcgY2xhc3M9InNlcmlmIGluayIgZm9udC1zaXplPSIyMSI+CiAgICA8dGV4dCB4PSI5MCIgIHk9IjE0MCIgdGV4dExlbmd0aD0iMjQiICBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiPsKrJiMxNjA7PC90ZXh0PgogICAgPHRleHQgeD0iMTE0IiB5PSIxNDAiIHRleHRMZW5ndGg9IjIyMiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIj5DZSBuJ2VzdCBwYXMgdW4gb3V0aWwsPC90ZXh0PgogICAgPHRleHQgeD0iMzM2IiB5PSIxNDAiIHRleHRMZW5ndGg9IjIwNSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIj5jJ2VzdCB1biBjb21wYWdub248L3RleHQ+CiAgICA8dGV4dCB4PSI5MCIgIHk9IjE4MiIgdGV4dExlbmd0aD0iMTAwIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiPmRlIHZveWFnZTwvdGV4dD4KICAgIDx0ZXh0IHg9IjE5NiIgeT0iMTgyIiB0ZXh0TGVuZ3RoPSIyNiIgIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyI+JiMxNjA74oCUJiMxNjA7PC90ZXh0PgogICAgPHRleHQgeD0iMjI4IiB5PSIxODIiIHRleHRMZW5ndGg9IjkyIiAgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIj5ldCBpbCB2YTwvdGV4dD4KICAgIDx0ZXh0IHg9IjMyNiIgeT0iMTgyIiB0ZXh0TGVuZ3RoPSIyMDAiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyI+dm91cyBjaGFuZ2VyIGxhIHZpZTwvdGV4dD4KICAgIDx0ZXh0IHg9IjUzMiIgeT0iMTgyIiB0ZXh0TGVuZ3RoPSI1MiIgIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyI+JiMxNjA7ISYjMTYwO8K7PC90ZXh0PgogIDwvZz4KCiAgPCEtLSByZWQtcGVuY2lsIGJveGVzIGFyb3VuZCB0aGUgdGhyZWUgaGl0cyAtLT4KICA8cmVjdCB4PSIxMDgiIHk9IjExOCIgd2lkdGg9IjIzNCIgaGVpZ2h0PSIzMyIgcng9IjciIGNsYXNzPSJyb3VnZWQiIHN0cm9rZS13aWR0aD0iMS42IiBzdHJva2UtZGFzaGFycmF5PSIxIDAiLz4KICA8cmVjdCB4PSIxOTEiIHk9IjE2MCIgd2lkdGg9IjM2IiAgaGVpZ2h0PSIzMyIgcng9IjciIGNsYXNzPSJyb3VnZWQiIHN0cm9rZS13aWR0aD0iMS42Ii8+CiAgPHJlY3QgeD0iMzIwIiB5PSIxNjAiIHdpZHRoPSIyMTIiIGhlaWdodD0iMzMiIHJ4PSI3IiBjbGFzcz0icm91Z2VkIiBzdHJva2Utd2lkdGg9IjEuNiIvPgoKICA8IS0tIGxlYWRlciBsaW5lcyAtLT4KICA8cGF0aCBkPSJNMjI1IDE1MSBRIDIwMCAyMDUgMTUwIDIzNiIgY2xhc3M9ImxpbmUiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWRhc2hhcnJheT0iMyA0Ii8+CiAgPGNpcmNsZSBjeD0iMTUwIiBjeT0iMjM2IiByPSIyLjIiIGNsYXNzPSJyb3VnZSIgc3Ryb2tlPSJub25lIi8+CiAgPHBhdGggZD0iTTIwOSAxOTMgUSAyNTAgMjQwIDMxMCAyODQiIGNsYXNzPSJsaW5lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1kYXNoYXJyYXk9IjMgNCIvPgogIDxjaXJjbGUgY3g9IjMxMCIgY3k9IjI4NCIgcj0iMi4yIiBjbGFzcz0icm91Z2UiIHN0cm9rZT0ibm9uZSIvPgogIDxwYXRoIGQ9Ik00MjYgMTkzIFEgNDgwIDI0MCA1MzMgMjk2IiBjbGFzcz0ibGluZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtZGFzaGFycmF5PSIzIDQiLz4KICA8Y2lyY2xlIGN4PSI1MzMiIGN5PSIyOTYiIHI9IjIuMiIgY2xhc3M9InJvdWdlIiBzdHJva2U9Im5vbmUiLz4KCiAgPCEtLSB0aGUgdGhyZWUgdmVyZGljdHMgLS0+CiAgPGcgY2xhc3M9InNlcmlmIj4KICAgIDx0ZXh0IHg9IjkwIiB5PSIyNjYiIGNsYXNzPSJyb3VnZSIgZm9udC1zaXplPSIxNyIgZm9udC13ZWlnaHQ9ImJvbGQiPmNvbnRyYXN0ZSBiaW5haXJlIDx0c3BhbiBjbGFzcz0icm91Z2UiPiYjODcyMjs4PC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dCB4PSI5MCIgeT0iMjg2IiBjbGFzcz0ibXV0ZWQiIGZvbnQtc2l6ZT0iMTIuNSIgZm9udC1zdHlsZT0iaXRhbGljIj7CqyBwYXMgWCwgYydlc3QgWSDCuyDigJQgbGEgYmFzY3VsZSBjcmV1c2U8L3RleHQ+CgogICAgPHRleHQgeD0iMzIwIiB5PSIzMDAiIGNsYXNzPSJyb3VnZSIgZm9udC1zaXplPSIxNyIgZm9udC13ZWlnaHQ9ImJvbGQiPnRpcmV0IGNhZHJhdGluIDx0c3BhbiBjbGFzcz0icm91Z2UiPiYjODcyMjs1PC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dCB4PSIzMjAiIHk9IjMyMCIgY2xhc3M9Im11dGVkIiBmb250LXNpemU9IjEyLjUiIGZvbnQtc3R5bGU9Iml0YWxpYyI+YnVkZ2V0IDogdW4gc2V1bCBwb3VyIDUwMCBtb3RzPC90ZXh0PgoKICAgIDx0ZXh0IHg9IjU0NSIgeT0iMzEyIiBjbGFzcz0icm91Z2UiIGZvbnQtc2l6ZT0iMTciIGZvbnQtd2VpZ2h0PSJib2xkIj5zZXJ2aWxlIDx0c3BhbiBjbGFzcz0icm91Z2UiPiYjODcyMjsxMDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQgeD0iNTQ1IiB5PSIzMzIiIGNsYXNzPSJtdXRlZCIgZm9udC1zaXplPSIxMi41IiBmb250LXN0eWxlPSJpdGFsaWMiPmxhIHByb21lc3NlIGQnw6ltZXJ2ZWlsbGVtZW50PC90ZXh0PgogIDwvZz4KCiAgPCEtLSB0aGUgZ2F1Z2U6IDDigJMxMDAsIHRocmVzaG9sZCBhdCA4MCwgaW5rIGZpbGwgdG8gNzcgLS0+CiAgPGc+CiAgICA8dGV4dCB4PSI5MCIgeT0iMzY4IiBjbGFzcz0ic2FucyBpbmsiIGZvbnQtc2l6ZT0iMTIiIGxldHRlci1zcGFjaW5nPSIzIj5TQ09SRTwvdGV4dD4KICAgIDxsaW5lIHgxPSI5MCIgeTE9IjQwMCIgeDI9Ijc5MCIgeTI9IjQwMCIgY2xhc3M9ImxpbmUiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgICA8bGluZSB4MT0iOTAiIHkxPSI0MDAiIHgyPSI2NDYiIHkyPSI0MDAiIGNsYXNzPSJsaW5lIiBzdHJva2Utd2lkdGg9IjciLz4KICAgIDwhLS0gdGhyZXNob2xkIHRpY2sgYXQgODAgOiA5MCArIDAuODAqNzAwID0gNjUwIC0tPgogICAgPGxpbmUgeDE9IjY1MCIgeTE9IjM5MCIgeDI9IjY1MCIgeTI9IjQxMCIgY2xhc3M9InJvdWdlZCIgc3Ryb2tlLXdpZHRoPSIxLjYiLz4KICAgIDx0ZXh0IHg9IjY1MCIgeT0iNDI2IiBjbGFzcz0icm91Z2Ugc2FucyIgZm9udC1zaXplPSIxMC41IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBsZXR0ZXItc3BhY2luZz0iMSI+U0VVSUwgODA8L3RleHQ+CiAgICA8dGV4dCB4PSI3OTAiIHk9IjQyNiIgY2xhc3M9Im11dGVkIHNhbnMiIGZvbnQtc2l6ZT0iMTAuNSIgdGV4dC1hbmNob3I9ImVuZCI+MTAwPC90ZXh0PgogICAgPHRleHQgeD0iOTAiIHk9IjQyNiIgY2xhc3M9Im11dGVkIHNhbnMiIGZvbnQtc2l6ZT0iMTAuNSI+MDwvdGV4dD4KICAgIDx0ZXh0IHg9IjY0NiIgeT0iMzg0IiBjbGFzcz0icm91Z2Ugc2VyaWYiIGZvbnQtc2l6ZT0iMjYiIGZvbnQtd2VpZ2h0PSJib2xkIiB0ZXh0LWFuY2hvcj0iZW5kIj43NzwvdGV4dD4KICAgIDx0ZXh0IHg9IjY3NiIgeT0iMzY4IiBjbGFzcz0ibXV0ZWQgc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGZvbnQtc3R5bGU9Iml0YWxpYyI+csOpw6ljcmlyZSwgcHVpcyByZS1wYXNzZXI8L3RleHQ+CiAgPC9nPgoKICA8dGV4dCB4PSI0NDAiIHk9IjQ1OCIgY2xhc3M9InNhbnMgbXV0ZWQiIGZvbnQtc2l6ZT0iMTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGxldHRlci1zcGFjaW5nPSIyIj5ISVRTIEFSRSBSRVZJRVcgQ0FORElEQVRFUywgTkVWRVIgVkVSRElDVFMg4oCUIFRIRSBBVVRIT1InUyBWT0lDRSBBTFdBWVMgV0lOUzwvdGV4dD4KPC9zdmc+Cg==)
14
14
 
15
15
  ```bash
16
16
  git clone https://git.nech.pl/pln/buvard.git && cd buvard
package/bin/buvard.js CHANGED
@@ -2,14 +2,18 @@
2
2
  /**
3
3
  * buvard — l'anti-slop qui tache. Blotting paper for AI-flavored prose.
4
4
  *
5
- * buvard check <files|dirs> [--json] run the catalog over markdown/text
6
- * buvard score <file> single-file score, machine-readable
5
+ * buvard check [files|dirs] [--json] [--hidden] run the catalog over markdown/text
6
+ * buvard score <file> [--json] single-file score, machine-readable
7
+ * buvard --help | --version
7
8
  *
8
9
  * Scoring starts at 100; each hit costs its weight (lexique 5, structure 8,
9
10
  * servile 10; punctuation charges per occurrence past the budget: one tiret
10
11
  * cadratin per 500 words, one exclamation per 1000). Every hit is a review
11
12
  * candidate, not a verdict — the author's voice always wins.
12
13
  *
14
+ * Walks skip dot-directories (--hidden to include) and never follow directory
15
+ * symlinks; unreadable directories are warned about, not fatal.
16
+ *
13
17
  * Known gaps, deliberately manual: règle de trois, synonyme acrobatique and
14
18
  * sentence-length variance need document-level analysis that regex fakes
15
19
  * badly. Read the text too.
@@ -20,26 +24,123 @@ import path from 'path';
20
24
  import { CATALOG_VERSION, lint, bodyOf } from '../lib/index.mjs';
21
25
 
22
26
  const TEXT_EXT = /\.(md|txt|mdx)$/i;
27
+ const PKG_VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
23
28
 
24
- function collect(target, files) {
25
- if (TEXT_EXT.test(target) && fs.existsSync(target)) return files.push(path.resolve(target));
26
- if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
27
- for (const f of fs.readdirSync(target)) {
28
- const p = path.join(target, f);
29
- if (fs.statSync(p).isDirectory()) collect(p, files);
30
- else if (TEXT_EXT.test(f)) files.push(path.resolve(p));
31
- }
29
+ const USAGE = `buvard — l'anti-slop qui tache. Blotting paper for AI-flavored prose.
30
+
31
+ Usage:
32
+ buvard check [files|dirs...] [--json] [--hidden] lint markdown/text files
33
+ buvard score <file> [--json] single-file score
34
+ buvard --help this help
35
+ buvard --version package version
36
+
37
+ Options:
38
+ --json machine-readable output
39
+ --hidden include dot-directories when walking (skipped by default)
40
+
41
+ Scoring: 100 base; each hit costs its weight (lexique -5, structure -8,
42
+ servile -10; punctuation past budget charges per occurrence: one tiret
43
+ cadratin per 500 words, one exclamation per 1000). Hits are review
44
+ candidates, never verdicts — the author's voice always wins.`;
45
+
46
+ /**
47
+ * Collect lintable files from a target path. Text files go straight in;
48
+ * directories are walked (skipping dot-entries unless `hidden`, and never
49
+ * following directory symlinks — readdir Dirents mark them as symlinks, not
50
+ * directories). Unresolvable targets and unreadable directories land in
51
+ * `errors` as warnings; they never throw.
52
+ */
53
+ function collect(target, files, opts, errors) {
54
+ let st;
55
+ try {
56
+ st = fs.statSync(target);
57
+ } catch {
58
+ errors.push(`buvard: no file or directory for "${target}"`);
59
+ return;
60
+ }
61
+ if (st.isFile()) {
62
+ if (TEXT_EXT.test(target)) files.push(path.resolve(target));
63
+ else errors.push(`buvard: "${target}" is not .md, .txt or .mdx — skipped`);
64
+ return;
65
+ }
66
+ if (!st.isDirectory()) {
67
+ errors.push(`buvard: "${target}" is neither file nor directory — skipped`);
68
+ return;
69
+ }
70
+ let entries;
71
+ try {
72
+ entries = fs.readdirSync(target, { withFileTypes: true });
73
+ } catch (e) {
74
+ errors.push(`buvard: cannot read "${target}" (${e.code}) — skipped`);
75
+ return;
76
+ }
77
+ for (const ent of entries) {
78
+ if (!opts.hidden && ent.name.startsWith('.')) continue;
79
+ const p = path.join(target, ent.name);
80
+ if (ent.isDirectory()) collect(p, files, opts, errors);
81
+ else if (ent.isFile() && TEXT_EXT.test(ent.name)) files.push(path.resolve(p));
82
+ }
83
+ }
84
+
85
+ function scoreReport(file, json) {
86
+ const r = { file, ...lint(bodyOf(fs.readFileSync(file, 'utf8'))) };
87
+ if (json) {
88
+ console.log(JSON.stringify({ buvard: CATALOG_VERSION, ...r }, null, 1));
32
89
  return;
33
90
  }
34
- console.error(`buvard: no file or directory for "${target}"`);
91
+ console.log(`${r.score}/100 ${path.relative(process.cwd(), r.file)} (${r.words} mots, ${r.hits.length} candidat${r.hits.length === 1 ? '' : 's'})`);
35
92
  }
36
93
 
37
94
  function main(argv) {
38
- const json = argv.includes('--json');
39
- const targets = argv.filter((a) => !a.startsWith('--'));
95
+ const flags = new Set();
96
+ const positional = [];
97
+ for (const a of argv) {
98
+ if (!a.startsWith('-')) {
99
+ positional.push(a);
100
+ continue;
101
+ }
102
+ if (a === '--json' || a === '--hidden') {
103
+ flags.add(a);
104
+ } else if (a === '-h' || a === '--help') {
105
+ console.log(USAGE);
106
+ return 0;
107
+ } else if (a === '--version') {
108
+ console.log(`buvard v${PKG_VERSION} (catalog v${CATALOG_VERSION})`);
109
+ return 0;
110
+ } else {
111
+ console.error(`buvard: unknown option "${a}"\n\n${USAGE}`);
112
+ return 2;
113
+ }
114
+ }
115
+
116
+ let cmd = 'check';
117
+ if (positional[0] === 'check' || positional[0] === 'score') cmd = positional.shift();
118
+
119
+ const json = flags.has('--json');
120
+ const opts = { hidden: flags.has('--hidden') };
121
+ const errors = [];
122
+
123
+ if (cmd === 'score') {
124
+ if (positional.length !== 1) {
125
+ console.error(`buvard score takes exactly one file\n\n${USAGE}`);
126
+ return 2;
127
+ }
128
+ const files = [];
129
+ collect(positional[0], files, opts, errors);
130
+ if (errors.length > 0 || files.length === 0) {
131
+ errors.forEach((e) => console.error(e));
132
+ return 2;
133
+ }
134
+ scoreReport(files[0], json);
135
+ return 0;
136
+ }
137
+
40
138
  const files = [];
41
- if (targets.length > 0) targets.forEach((t) => collect(t, files));
42
- else collect('.', files);
139
+ if (positional.length > 0) positional.forEach((t) => collect(t, files, opts, errors));
140
+ else collect('.', files, opts, errors);
141
+
142
+ errors.forEach((e) => console.error(e));
143
+ if (files.length === 0 && positional.length > 0) return 2;
43
144
 
44
145
  const results = files.map((file) => ({ file, ...lint(bodyOf(fs.readFileSync(file, 'utf8'))) }));
45
146
 
package/package.json CHANGED
@@ -1,14 +1,39 @@
1
1
  {
2
2
  "name": "buvard",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "buvard — l'anti-slop qui tache. Blotting paper for AI-flavored prose: a weighted multi-score linter for French and English slop patterns.",
5
5
  "type": "module",
6
- "bin": { "buvard": "bin/buvard.js" },
6
+ "bin": {
7
+ "buvard": "bin/buvard.js"
8
+ },
7
9
  "main": "lib/index.mjs",
8
- "files": ["bin", "lib", "docs", "AGENTS.md", "SKILL.md", "README.md", "LICENSE"],
9
- "scripts": { "test": "node --test test/" },
10
- "keywords": ["slop", "linter", "prose", "french", "writing", "ai-writing", "style"],
10
+ "files": [
11
+ "bin",
12
+ "lib",
13
+ "docs",
14
+ "AGENTS.md",
15
+ "SKILL.md",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "test": "node --test test/"
21
+ },
22
+ "keywords": [
23
+ "slop",
24
+ "linter",
25
+ "prose",
26
+ "french",
27
+ "writing",
28
+ "ai-writing",
29
+ "style"
30
+ ],
11
31
  "license": "GPL-3.0-only",
12
- "repository": { "type": "git", "url": "https://git.nech.pl/pln/buvard.git" },
13
- "engines": { "node": ">=18" }
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://git.nech.pl/pln/buvard.git"
35
+ },
36
+ "engines": {
37
+ "node": ">=18"
38
+ }
14
39
  }