@micelio/eslint-config 0.1.2 → 0.2.1

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.
@@ -2,6 +2,8 @@
2
2
  export declare const STANDARD_PATH = "docs/engineering-standard.md";
3
3
  /** The SHA-256 of each released version of the standard, by `major.minor`. */
4
4
  export type StandardHashes = Record<string, string>;
5
+ /** The SHA-256 of each synced file (agent prompts, skills, pull request template), by path in the repository. */
6
+ export type FileHashes = Record<string, string>;
5
7
  /** What `lintStandard` found. */
6
8
  export interface StandardResult {
7
9
  /** True when the local copy is the one published for the pinned version. */
@@ -21,14 +23,21 @@ export declare function readPin(agents: string): string | null;
21
23
  export declare function setPin(agents: string, version: string): string | null;
22
24
  /** Reads the version a copy of the standard says it is, or `null` when its header is missing. */
23
25
  export declare function standardVersion(standard: string): string | null;
24
- /** Loads the hashes bundled in this package. */
26
+ /** Loads the hash of each released version of the standard bundled in this package. */
25
27
  export declare function loadHashes(): StandardHashes;
28
+ /** Loads the hash of each file the sync carries next to the standard, by path in the repository. */
29
+ export declare function loadFileHashes(): FileHashes;
26
30
  /**
27
31
  * Checks that the copy of the standard in `root` is the one published for the version its
28
32
  * `AGENTS.md` pins (standard, section 16). A repository may pin an older version on purpose:
29
33
  * the pinned one is checked, not the latest.
30
34
  *
31
35
  * @param root - The repository root.
36
+ * It also checks the synced files the repository has (`.claude/agents/*.md`, the skills, the pull
37
+ * request template) against `fileHashes`, but only when the pin is the newest version this package
38
+ * knows: a repository that stays on an older version on purpose keeps the older files too.
39
+ *
32
40
  * @param hashes - The published hashes; the ones bundled in this package by default.
41
+ * @param fileHashes - The published hashes of the synced files; the bundled ones by default.
33
42
  */
34
- export declare function lintStandard(root: string, hashes?: StandardHashes): StandardResult;
43
+ export declare function lintStandard(root: string, hashes?: StandardHashes, fileHashes?: FileHashes): StandardResult;
package/dist/standard.js CHANGED
@@ -31,20 +31,53 @@ export function setPin(agents, version) {
31
31
  export function standardVersion(standard) {
32
32
  return HEADER.exec(standard)?.[1] ?? null;
33
33
  }
34
- /** Loads the hashes bundled in this package. */
35
- export function loadHashes() {
34
+ /** Reads `standard-hashes.json`, bundled in this package. */
35
+ function readHashesFile() {
36
36
  const file = new URL('../standard-hashes.json', import.meta.url);
37
37
  return JSON.parse(readFileSync(file, 'utf8'));
38
38
  }
39
+ /** Loads the hash of each released version of the standard bundled in this package. */
40
+ export function loadHashes() {
41
+ const hashes = {};
42
+ for (const [key, value] of Object.entries(readHashesFile())) {
43
+ if (typeof value === 'string')
44
+ hashes[key] = value;
45
+ }
46
+ return hashes;
47
+ }
48
+ /** Loads the hash of each file the sync carries next to the standard, by path in the repository. */
49
+ export function loadFileHashes() {
50
+ const files = {};
51
+ for (const [path, value] of Object.entries(readHashesFile().files ?? {})) {
52
+ if (typeof value === 'string')
53
+ files[path] = value;
54
+ }
55
+ return files;
56
+ }
57
+ /** The newest version in `hashes`, compared as numbers. */
58
+ function latestVersion(hashes) {
59
+ const order = (version) => version.split('.').map(Number);
60
+ const [latest] = Object.keys(hashes).sort((a, b) => {
61
+ const [ax = 0, ay = 0] = order(a);
62
+ const [bx = 0, by = 0] = order(b);
63
+ return bx - ax || by - ay;
64
+ });
65
+ return latest ?? null;
66
+ }
39
67
  /**
40
68
  * Checks that the copy of the standard in `root` is the one published for the version its
41
69
  * `AGENTS.md` pins (standard, section 16). A repository may pin an older version on purpose:
42
70
  * the pinned one is checked, not the latest.
43
71
  *
44
72
  * @param root - The repository root.
73
+ * It also checks the synced files the repository has (`.claude/agents/*.md`, the skills, the pull
74
+ * request template) against `fileHashes`, but only when the pin is the newest version this package
75
+ * knows: a repository that stays on an older version on purpose keeps the older files too.
76
+ *
45
77
  * @param hashes - The published hashes; the ones bundled in this package by default.
78
+ * @param fileHashes - The published hashes of the synced files; the bundled ones by default.
46
79
  */
47
- export function lintStandard(root, hashes = loadHashes()) {
80
+ export function lintStandard(root, hashes = loadHashes(), fileHashes = loadFileHashes()) {
48
81
  const agentsPath = join(root, 'AGENTS.md');
49
82
  if (!existsSync(agentsPath)) {
50
83
  return {
@@ -78,5 +111,19 @@ export function lintStandard(root, hashes = loadHashes()) {
78
111
  message: `${STANDARD_PATH} does not match standard ${pin}, the version AGENTS.md pins (sha256 ${actual}, published ${expected}). Restore it, or sync it from bogd3v/micelio-engineering.`,
79
112
  };
80
113
  }
114
+ const stale = pin === latestVersion(hashes)
115
+ ? Object.entries(fileHashes)
116
+ .filter(([path, hash]) => {
117
+ const file = join(root, path);
118
+ return existsSync(file) && hashOf(readFileSync(file)) !== hash;
119
+ })
120
+ .map(([path]) => path)
121
+ : [];
122
+ if (stale.length > 0) {
123
+ return {
124
+ ok: false,
125
+ message: `${stale.join(', ')} ${stale.length === 1 ? 'does' : 'do'} not match the published copy of standard ${pin}. Restore ${stale.length === 1 ? 'it' : 'them'} if edited by hand, or, in a sync pull request, upgrade @micelio/eslint-config to the release that publishes the new hashes.`,
126
+ };
127
+ }
81
128
  return { ok: true, message: `Standard ${pin}: ${STANDARD_PATH} matches the published copy.` };
82
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micelio/eslint-config",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "The ESLint flat config of the Micelio repositories, the licence allowlist behind lint:licenses and lint:standard.",
5
5
  "license": "MIT-0",
6
6
  "repository": {
@@ -32,9 +32,9 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "eslint-config-prettier": "^10.1.8",
35
- "eslint-plugin-jsdoc": "^65.3.6",
35
+ "eslint-plugin-jsdoc": "^65.4.2",
36
36
  "eslint-plugin-tsdoc": "0.5.4",
37
- "typescript-eslint": "^8.71.2"
37
+ "typescript-eslint": "^8.72.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "eslint": "^10.12.0",
@@ -1,4 +1,16 @@
1
1
  {
2
2
  "1.4": "0f1202bbd02a83f216313b84be4744d66ccbee60bc01b39bfc7f67cefd30e139",
3
- "1.5": "c3ee8aa9a13945e073dedeecbb7ba3a9587ec0ef6878e426eb4be7a0398d93a1"
3
+ "1.5": "c3ee8aa9a13945e073dedeecbb7ba3a9587ec0ef6878e426eb4be7a0398d93a1",
4
+ "1.6": "ea74875d30586378a64d90feb59ce197d3efa7f07e867108f3849f5308cc1f92",
5
+ "files": {
6
+ ".claude/agents/master.md": "ee982318762bccb42a8a68dfaf51628087dcd7d4563be5b8a08b1ffabea7adbd",
7
+ ".claude/agents/planner.md": "b71c815c78875252c9574b74e581115c9814903bc05c11a992689d8af979f7e8",
8
+ ".claude/agents/reviewer.md": "62b9addeab36c5166b674dd1b1cdae92c382b00a563ccbd2b652227932b7ef52",
9
+ ".claude/agents/verifier.md": "9d42abc7329774c51f63f1079182e42720b052f1f3ef5b05019c9729aa394958",
10
+ ".claude/agents/worker.md": "8390a223c983ec5ea7254e1f7fd1de79cfd1bb4c73936a133ddfb6ffd7f25d45",
11
+ ".claude/skills/ship-pr/SKILL.md": "c6267ca9022252dbd48a90c19940d70f7515857792b5df5d2e6a8320f2ecfb51",
12
+ ".claude/skills/blind-review/SKILL.md": "914d96d06ff3d34b711702c2a77315a387b3529e672eeead48fc830875a1f4ae",
13
+ ".claude/skills/verify-pr/SKILL.md": "8a6c66d9b8933af550038f58587868665daa90c7ac631fc648d92b366bd7e516",
14
+ ".github/pull_request_template.md": "3ae627043b11a7f26283ee90f68ac8ba6b6b2fb6a24a97f23637ae8ef9dd23e0"
15
+ }
4
16
  }