@oddessentials/repo-standards 4.2.0 → 4.3.0

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/dist/index.cjs CHANGED
@@ -41,7 +41,7 @@ var import_node_fs = require("fs");
41
41
  var import_node_path = require("path");
42
42
 
43
43
  // src/version.ts
44
- var STANDARDS_VERSION = "4.1.0";
44
+ var STANDARDS_VERSION = "4.2.0";
45
45
  var STANDARDS_SCHEMA_VERSION = 4;
46
46
 
47
47
  // src/index.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js","../src/version.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type {\n MasterJson,\n StackChecklistJson,\n StackId,\n CiSystem,\n} from \"./types.js\";\nimport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION } from \"./version.js\";\n\n// Re-export types for consumers\nexport type { MasterJson, StackChecklistJson, StackId, CiSystem };\n\n// Re-export version info (stable API contract)\nexport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION };\n\n// ESM equivalent of __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Path to config directory:\n// - When running from src/ (dev/test): use repo root config/\n// - When running from dist/ (installed): use dist/config/\nconst isDevMode = __dirname.includes(\"src\");\nconst configDir = isDevMode\n ? join(__dirname, \"..\", \"config\")\n : join(__dirname, \"config\");\n\n/** Load the master spec JSON from the packaged dist directory */\nexport function loadMasterSpec(): MasterJson {\n const filePath = join(configDir, \"standards.json\");\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** Load a stack-specific checklist (optionally filtered by CI system) */\nexport function loadBaseline(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n const suffix = ci ? `.${ci}` : \"\";\n const file = `standards.${stack}${suffix}.json`;\n const filePath = join(configDir, file);\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** List all supported stacks (derived from the master spec) */\nexport function listSupportedStacks(): readonly StackId[] {\n const spec = loadMasterSpec();\n return Object.keys(spec.stacks) as StackId[];\n}\n\n/** List all supported CI systems (derived from the master spec) */\nexport function listSupportedCiSystems(): readonly CiSystem[] {\n const spec = loadMasterSpec();\n return spec.ciSystems as CiSystem[];\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadBaseline - loads stack-specific standards checklist.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getStandards(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n return loadBaseline(stack, ci);\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadMasterSpec - loads the master standards schema.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getSchema(): MasterJson {\n return loadMasterSpec();\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * AUTO-GENERATED at build time by scripts/build.ts\n * DO NOT EDIT MANUALLY\n *\n * This module provides version information for the repo-standards package.\n * Consumers should import from here instead of package.json to avoid\n * ESM/CJS interop issues.\n */\n\nexport const STANDARDS_VERSION = '4.1.0';\nexport const STANDARDS_SCHEMA_VERSION = 4;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,IAAM,mBAAmB,MACvB,OAAO,aAAa,cAChB,IAAI,IAAI,QAAQ,UAAU,EAAE,EAAE,OAC7B,SAAS,iBAAiB,SAAS,cAAc,QAAQ,YAAY,MAAM,WAC1E,SAAS,cAAc,MACvB,IAAI,IAAI,WAAW,SAAS,OAAO,EAAE;AAEtC,IAAM,gBAAgC,iCAAiB;;;ADZ9D,sBAA8B;AAC9B,qBAA6B;AAC7B,uBAA8B;;;AEOvB,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;;;AFQxC,IAAMA,kBAAa,+BAAc,aAAe;AAChD,IAAM,gBAAY,0BAAQA,WAAU;AAKpC,IAAM,YAAY,UAAU,SAAS,KAAK;AAC1C,IAAM,YAAY,gBACd,uBAAK,WAAW,MAAM,QAAQ,QAC9B,uBAAK,WAAW,QAAQ;AAGrB,SAAS,iBAA6B;AAC3C,QAAM,eAAW,uBAAK,WAAW,gBAAgB;AACjD,SAAO,KAAK,UAAM,6BAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,aACd,OACA,IACoB;AACpB,QAAM,SAAS,KAAK,IAAI,EAAE,KAAK;AAC/B,QAAM,OAAO,aAAa,KAAK,GAAG,MAAM;AACxC,QAAM,eAAW,uBAAK,WAAW,IAAI;AACrC,SAAO,KAAK,UAAM,6BAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,sBAA0C;AACxD,QAAM,OAAO,eAAe;AAC5B,SAAO,OAAO,KAAK,KAAK,MAAM;AAChC;AAGO,SAAS,yBAA8C;AAC5D,QAAM,OAAO,eAAe;AAC5B,SAAO,KAAK;AACd;AAOO,SAAS,aACd,OACA,IACoB;AACpB,SAAO,aAAa,OAAO,EAAE;AAC/B;AAOO,SAAS,YAAwB;AACtC,SAAO,eAAe;AACxB;","names":["__filename"]}
1
+ {"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js","../src/version.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type {\n MasterJson,\n StackChecklistJson,\n StackId,\n CiSystem,\n} from \"./types.js\";\nimport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION } from \"./version.js\";\n\n// Re-export types for consumers\nexport type { MasterJson, StackChecklistJson, StackId, CiSystem };\n\n// Re-export version info (stable API contract)\nexport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION };\n\n// ESM equivalent of __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Path to config directory:\n// - When running from src/ (dev/test): use repo root config/\n// - When running from dist/ (installed): use dist/config/\nconst isDevMode = __dirname.includes(\"src\");\nconst configDir = isDevMode\n ? join(__dirname, \"..\", \"config\")\n : join(__dirname, \"config\");\n\n/** Load the master spec JSON from the packaged dist directory */\nexport function loadMasterSpec(): MasterJson {\n const filePath = join(configDir, \"standards.json\");\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** Load a stack-specific checklist (optionally filtered by CI system) */\nexport function loadBaseline(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n const suffix = ci ? `.${ci}` : \"\";\n const file = `standards.${stack}${suffix}.json`;\n const filePath = join(configDir, file);\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** List all supported stacks (derived from the master spec) */\nexport function listSupportedStacks(): readonly StackId[] {\n const spec = loadMasterSpec();\n return Object.keys(spec.stacks) as StackId[];\n}\n\n/** List all supported CI systems (derived from the master spec) */\nexport function listSupportedCiSystems(): readonly CiSystem[] {\n const spec = loadMasterSpec();\n return spec.ciSystems as CiSystem[];\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadBaseline - loads stack-specific standards checklist.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getStandards(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n return loadBaseline(stack, ci);\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadMasterSpec - loads the master standards schema.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getSchema(): MasterJson {\n return loadMasterSpec();\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * AUTO-GENERATED at build time by scripts/build.ts\n * DO NOT EDIT MANUALLY\n *\n * This module provides version information for the repo-standards package.\n * Consumers should import from here instead of package.json to avoid\n * ESM/CJS interop issues.\n */\n\nexport const STANDARDS_VERSION = '4.2.0';\nexport const STANDARDS_SCHEMA_VERSION = 4;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,IAAM,mBAAmB,MACvB,OAAO,aAAa,cAChB,IAAI,IAAI,QAAQ,UAAU,EAAE,EAAE,OAC7B,SAAS,iBAAiB,SAAS,cAAc,QAAQ,YAAY,MAAM,WAC1E,SAAS,cAAc,MACvB,IAAI,IAAI,WAAW,SAAS,OAAO,EAAE;AAEtC,IAAM,gBAAgC,iCAAiB;;;ADZ9D,sBAA8B;AAC9B,qBAA6B;AAC7B,uBAA8B;;;AEOvB,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;;;AFQxC,IAAMA,kBAAa,+BAAc,aAAe;AAChD,IAAM,gBAAY,0BAAQA,WAAU;AAKpC,IAAM,YAAY,UAAU,SAAS,KAAK;AAC1C,IAAM,YAAY,gBACd,uBAAK,WAAW,MAAM,QAAQ,QAC9B,uBAAK,WAAW,QAAQ;AAGrB,SAAS,iBAA6B;AAC3C,QAAM,eAAW,uBAAK,WAAW,gBAAgB;AACjD,SAAO,KAAK,UAAM,6BAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,aACd,OACA,IACoB;AACpB,QAAM,SAAS,KAAK,IAAI,EAAE,KAAK;AAC/B,QAAM,OAAO,aAAa,KAAK,GAAG,MAAM;AACxC,QAAM,eAAW,uBAAK,WAAW,IAAI;AACrC,SAAO,KAAK,UAAM,6BAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,sBAA0C;AACxD,QAAM,OAAO,eAAe;AAC5B,SAAO,OAAO,KAAK,KAAK,MAAM;AAChC;AAGO,SAAS,yBAA8C;AAC5D,QAAM,OAAO,eAAe;AAC5B,SAAO,KAAK;AACd;AAOO,SAAS,aACd,OACA,IACoB;AACpB,SAAO,aAAa,OAAO,EAAE;AAC/B;AAOO,SAAS,YAAwB;AACtC,SAAO,eAAe;AACxB;","names":["__filename"]}
package/dist/index.d.cts CHANGED
@@ -156,7 +156,7 @@ interface StackChecklistJson {
156
156
  * Consumers should import from here instead of package.json to avoid
157
157
  * ESM/CJS interop issues.
158
158
  */
159
- declare const STANDARDS_VERSION = "4.1.0";
159
+ declare const STANDARDS_VERSION = "4.2.0";
160
160
  declare const STANDARDS_SCHEMA_VERSION = 4;
161
161
 
162
162
  /** Load the master spec JSON from the packaged dist directory */
package/dist/index.d.ts CHANGED
@@ -156,7 +156,7 @@ interface StackChecklistJson {
156
156
  * Consumers should import from here instead of package.json to avoid
157
157
  * ESM/CJS interop issues.
158
158
  */
159
- declare const STANDARDS_VERSION = "4.1.0";
159
+ declare const STANDARDS_VERSION = "4.2.0";
160
160
  declare const STANDARDS_SCHEMA_VERSION = 4;
161
161
 
162
162
  /** Load the master spec JSON from the packaged dist directory */
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { readFileSync } from "fs";
4
4
  import { join, dirname } from "path";
5
5
 
6
6
  // src/version.ts
7
- var STANDARDS_VERSION = "4.1.0";
7
+ var STANDARDS_VERSION = "4.2.0";
8
8
  var STANDARDS_SCHEMA_VERSION = 4;
9
9
 
10
10
  // src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/version.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type {\n MasterJson,\n StackChecklistJson,\n StackId,\n CiSystem,\n} from \"./types.js\";\nimport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION } from \"./version.js\";\n\n// Re-export types for consumers\nexport type { MasterJson, StackChecklistJson, StackId, CiSystem };\n\n// Re-export version info (stable API contract)\nexport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION };\n\n// ESM equivalent of __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Path to config directory:\n// - When running from src/ (dev/test): use repo root config/\n// - When running from dist/ (installed): use dist/config/\nconst isDevMode = __dirname.includes(\"src\");\nconst configDir = isDevMode\n ? join(__dirname, \"..\", \"config\")\n : join(__dirname, \"config\");\n\n/** Load the master spec JSON from the packaged dist directory */\nexport function loadMasterSpec(): MasterJson {\n const filePath = join(configDir, \"standards.json\");\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** Load a stack-specific checklist (optionally filtered by CI system) */\nexport function loadBaseline(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n const suffix = ci ? `.${ci}` : \"\";\n const file = `standards.${stack}${suffix}.json`;\n const filePath = join(configDir, file);\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** List all supported stacks (derived from the master spec) */\nexport function listSupportedStacks(): readonly StackId[] {\n const spec = loadMasterSpec();\n return Object.keys(spec.stacks) as StackId[];\n}\n\n/** List all supported CI systems (derived from the master spec) */\nexport function listSupportedCiSystems(): readonly CiSystem[] {\n const spec = loadMasterSpec();\n return spec.ciSystems as CiSystem[];\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadBaseline - loads stack-specific standards checklist.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getStandards(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n return loadBaseline(stack, ci);\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadMasterSpec - loads the master standards schema.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getSchema(): MasterJson {\n return loadMasterSpec();\n}\n","/**\n * AUTO-GENERATED at build time by scripts/build.ts\n * DO NOT EDIT MANUALLY\n *\n * This module provides version information for the repo-standards package.\n * Consumers should import from here instead of package.json to avoid\n * ESM/CJS interop issues.\n */\n\nexport const STANDARDS_VERSION = '4.1.0';\nexport const STANDARDS_SCHEMA_VERSION = 4;\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,MAAM,eAAe;;;ACOvB,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;;;ADQxC,IAAMA,cAAa,cAAc,YAAY,GAAG;AAChD,IAAMC,aAAY,QAAQD,WAAU;AAKpC,IAAM,YAAYC,WAAU,SAAS,KAAK;AAC1C,IAAM,YAAY,YACd,KAAKA,YAAW,MAAM,QAAQ,IAC9B,KAAKA,YAAW,QAAQ;AAGrB,SAAS,iBAA6B;AAC3C,QAAM,WAAW,KAAK,WAAW,gBAAgB;AACjD,SAAO,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,aACd,OACA,IACoB;AACpB,QAAM,SAAS,KAAK,IAAI,EAAE,KAAK;AAC/B,QAAM,OAAO,aAAa,KAAK,GAAG,MAAM;AACxC,QAAM,WAAW,KAAK,WAAW,IAAI;AACrC,SAAO,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,sBAA0C;AACxD,QAAM,OAAO,eAAe;AAC5B,SAAO,OAAO,KAAK,KAAK,MAAM;AAChC;AAGO,SAAS,yBAA8C;AAC5D,QAAM,OAAO,eAAe;AAC5B,SAAO,KAAK;AACd;AAOO,SAAS,aACd,OACA,IACoB;AACpB,SAAO,aAAa,OAAO,EAAE;AAC/B;AAOO,SAAS,YAAwB;AACtC,SAAO,eAAe;AACxB;","names":["__filename","__dirname"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/version.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport type {\n MasterJson,\n StackChecklistJson,\n StackId,\n CiSystem,\n} from \"./types.js\";\nimport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION } from \"./version.js\";\n\n// Re-export types for consumers\nexport type { MasterJson, StackChecklistJson, StackId, CiSystem };\n\n// Re-export version info (stable API contract)\nexport { STANDARDS_VERSION, STANDARDS_SCHEMA_VERSION };\n\n// ESM equivalent of __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Path to config directory:\n// - When running from src/ (dev/test): use repo root config/\n// - When running from dist/ (installed): use dist/config/\nconst isDevMode = __dirname.includes(\"src\");\nconst configDir = isDevMode\n ? join(__dirname, \"..\", \"config\")\n : join(__dirname, \"config\");\n\n/** Load the master spec JSON from the packaged dist directory */\nexport function loadMasterSpec(): MasterJson {\n const filePath = join(configDir, \"standards.json\");\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** Load a stack-specific checklist (optionally filtered by CI system) */\nexport function loadBaseline(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n const suffix = ci ? `.${ci}` : \"\";\n const file = `standards.${stack}${suffix}.json`;\n const filePath = join(configDir, file);\n return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\n/** List all supported stacks (derived from the master spec) */\nexport function listSupportedStacks(): readonly StackId[] {\n const spec = loadMasterSpec();\n return Object.keys(spec.stacks) as StackId[];\n}\n\n/** List all supported CI systems (derived from the master spec) */\nexport function listSupportedCiSystems(): readonly CiSystem[] {\n const spec = loadMasterSpec();\n return spec.ciSystems as CiSystem[];\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadBaseline - loads stack-specific standards checklist.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getStandards(\n stack: StackId,\n ci?: CiSystem,\n): StackChecklistJson {\n return loadBaseline(stack, ci);\n}\n\n/**\n * PUBLIC API CONTRACT (semver-governed)\n * Alias for loadMasterSpec - loads the master standards schema.\n * Breaking changes to this function signature require a major version bump.\n */\nexport function getSchema(): MasterJson {\n return loadMasterSpec();\n}\n","/**\n * AUTO-GENERATED at build time by scripts/build.ts\n * DO NOT EDIT MANUALLY\n *\n * This module provides version information for the repo-standards package.\n * Consumers should import from here instead of package.json to avoid\n * ESM/CJS interop issues.\n */\n\nexport const STANDARDS_VERSION = '4.2.0';\nexport const STANDARDS_SCHEMA_VERSION = 4;\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,MAAM,eAAe;;;ACOvB,IAAM,oBAAoB;AAC1B,IAAM,2BAA2B;;;ADQxC,IAAMA,cAAa,cAAc,YAAY,GAAG;AAChD,IAAMC,aAAY,QAAQD,WAAU;AAKpC,IAAM,YAAYC,WAAU,SAAS,KAAK;AAC1C,IAAM,YAAY,YACd,KAAKA,YAAW,MAAM,QAAQ,IAC9B,KAAKA,YAAW,QAAQ;AAGrB,SAAS,iBAA6B;AAC3C,QAAM,WAAW,KAAK,WAAW,gBAAgB;AACjD,SAAO,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,aACd,OACA,IACoB;AACpB,QAAM,SAAS,KAAK,IAAI,EAAE,KAAK;AAC/B,QAAM,OAAO,aAAa,KAAK,GAAG,MAAM;AACxC,QAAM,WAAW,KAAK,WAAW,IAAI;AACrC,SAAO,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAClD;AAGO,SAAS,sBAA0C;AACxD,QAAM,OAAO,eAAe;AAC5B,SAAO,OAAO,KAAK,KAAK,MAAM;AAChC;AAGO,SAAS,yBAA8C;AAC5D,QAAM,OAAO,eAAe;AAC5B,SAAO,KAAK;AACd;AAOO,SAAS,aACd,OACA,IACoB;AACpB,SAAO,aAAa,OAAO,EAAE;AAC/B;AAOO,SAAS,YAAwB;AACtC,SAAO,eAAe;AACxB;","names":["__filename","__dirname"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oddessentials/repo-standards",
3
3
  "private": false,
4
- "version": "4.2.0",
4
+ "version": "4.3.0",
5
5
  "description": "Standards and CI filtering utilities for multi-stack repository governance.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "test": "vitest run",
18
18
  "typecheck": "tsc --noEmit",
19
19
  "prepare": "husky",
20
+ "postinstall": "git config core.hooksPath .husky || true",
20
21
  "ci": "npm run lint && npm run format:check && npm run typecheck && npm run test && npm run build",
21
22
  "generate:standards": "tsx scripts/generate-standards.ts",
22
23
  "validate:schema": "tsx scripts/validate-schema.ts",
@@ -53,10 +54,10 @@
53
54
  "node": ">=22 <23"
54
55
  },
55
56
  "lint-staged": {
56
- "*.{js,ts,mjs,json,md}": [
57
+ "*.{js,cjs,ts,mjs,json,md}": [
57
58
  "prettier --write"
58
59
  ],
59
- "*.{js,ts,mjs}": [
60
+ "*.{js,cjs,ts,mjs}": [
60
61
  "eslint --fix"
61
62
  ]
62
63
  },
@@ -92,6 +93,7 @@
92
93
  "files": [
93
94
  "dist/",
94
95
  "README.md",
95
- "LICENSE"
96
+ "LICENSE",
97
+ "scripts/"
96
98
  ]
97
99
  }
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env tsx
2
+ // scripts/build.ts
3
+
4
+ /**
5
+ * Build script for @oddessentials/repo-standards
6
+ * - Copies master config/standards.json to dist/config/
7
+ * - Generates stack-specific JSON artifacts into dist/config/
8
+ * - Ensures deterministic output (sorted keys, no timestamps)
9
+ */
10
+
11
+ import { execSync } from "node:child_process";
12
+ import {
13
+ writeFileSync,
14
+ readFileSync,
15
+ mkdirSync,
16
+ copyFileSync,
17
+ readdirSync,
18
+ } from "node:fs";
19
+ import { resolve, join } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+
24
+ // Helper to sort object keys recursively for deterministic output
25
+ function sortObject(obj: unknown): unknown {
26
+ if (Array.isArray(obj)) return obj.map(sortObject);
27
+ if (obj && typeof obj === "object") {
28
+ const sorted: Record<string, unknown> = {};
29
+ Object.keys(obj as Record<string, unknown>)
30
+ .sort()
31
+ .forEach((key) => {
32
+ sorted[key] = sortObject((obj as Record<string, unknown>)[key]);
33
+ });
34
+ return sorted;
35
+ }
36
+ return obj;
37
+ }
38
+ /**
39
+ * Ensure schema version matches package.json major version.
40
+ * - Auto-upgrades schema when package.json major is higher (for semantic-release)
41
+ * - Fails if schema is ahead of package (prevents manual drift)
42
+ */
43
+ function syncSchemaVersion(rootDir: string): void {
44
+ const pkgPath = join(rootDir, "package.json");
45
+ const standardsPath = join(rootDir, "config", "standards.json");
46
+
47
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
48
+ const standards = JSON.parse(readFileSync(standardsPath, "utf8"));
49
+
50
+ // Extract major version from package.json (e.g., "2.1.0" -> 2)
51
+ const pkgMajor = parseInt(pkg.version.split(".")[0], 10);
52
+
53
+ if (pkgMajor > standards.version) {
54
+ // Auto-upgrade schema version when semantic-release bumps package.json
55
+ console.log(
56
+ `Upgrading schema version: ${standards.version} -> ${pkgMajor} (from package.json)`,
57
+ );
58
+ standards.version = pkgMajor;
59
+ writeFileSync(standardsPath, JSON.stringify(standards, null, 2) + "\n");
60
+ } else if (standards.version > pkgMajor) {
61
+ // Schema ahead of package = manual drift, fail build
62
+ throw new Error(
63
+ `Schema version drift: standards.json version=${standards.version} ` +
64
+ `is ahead of package.json major=${pkgMajor}. ` +
65
+ `This should not happen - schema version is set by CI.`,
66
+ );
67
+ } else {
68
+ console.log(
69
+ `Schema version ${standards.version} matches package.json major`,
70
+ );
71
+ }
72
+ }
73
+
74
+ // Run the existing generator for each stack (and optional CI system)
75
+ function generateStack(stack: string, ci?: string) {
76
+ const args = ["scripts/generate-standards.ts", stack];
77
+ if (ci) args.push(ci);
78
+ const cmd = `npx tsx ${args.join(" ")}`;
79
+ console.log(`Generating ${stack}${ci ? " for " + ci : ""}`);
80
+ execSync(cmd, { stdio: "inherit" });
81
+ }
82
+
83
+ /**
84
+ * Generate src/version.ts with current package version
85
+ * This runs before TypeScript compilation so the values are baked in
86
+ */
87
+ function generateVersionFile(rootDir: string): void {
88
+ const pkgPath = join(rootDir, "package.json");
89
+ const versionPath = join(rootDir, "src", "version.ts");
90
+
91
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
92
+ const standards = JSON.parse(
93
+ readFileSync(join(rootDir, "config", "standards.json"), "utf8"),
94
+ );
95
+
96
+ const content = `/**
97
+ * AUTO-GENERATED at build time by scripts/build.ts
98
+ * DO NOT EDIT MANUALLY
99
+ *
100
+ * This module provides version information for the repo-standards package.
101
+ * Consumers should import from here instead of package.json to avoid
102
+ * ESM/CJS interop issues.
103
+ */
104
+
105
+ export const STANDARDS_VERSION = '${pkg.version}';
106
+ export const STANDARDS_SCHEMA_VERSION = ${standards.version};
107
+ `;
108
+
109
+ writeFileSync(versionPath, content);
110
+ console.log(`Generated src/version.ts with version ${pkg.version}`);
111
+ }
112
+
113
+ function main() {
114
+ const rootDir = process.cwd();
115
+ const configSrc = resolve(rootDir, "config");
116
+ const configDest = resolve(rootDir, "dist", "config");
117
+
118
+ // Generate version.ts before TypeScript compilation
119
+ generateVersionFile(rootDir);
120
+
121
+ // Sync schema version with package.json major version (before generation)
122
+ syncSchemaVersion(rootDir);
123
+
124
+ // Ensure dist/config exists
125
+ mkdirSync(configDest, { recursive: true });
126
+
127
+ // Copy master standards.json first
128
+ copyFileSync(
129
+ join(configSrc, "standards.json"),
130
+ join(configDest, "standards.json"),
131
+ );
132
+ console.log("Copied master standards.json to dist/config/");
133
+
134
+ const stacks = ["typescript-js", "csharp-dotnet", "python", "rust", "go"];
135
+ const ciSystems = ["azure-devops", "github-actions"];
136
+
137
+ // Generate each stack without CI suffix
138
+ for (const stack of stacks) {
139
+ generateStack(stack);
140
+ }
141
+
142
+ // Generate CI-specific views
143
+ for (const stack of stacks) {
144
+ for (const ci of ciSystems) {
145
+ generateStack(stack, ci);
146
+ }
147
+ }
148
+
149
+ // Copy all generated files from config/ to dist/config/
150
+ const generatedFiles = readdirSync(configSrc).filter(
151
+ (f) => f.startsWith("standards.") && f.endsWith(".json"),
152
+ );
153
+
154
+ for (const file of generatedFiles) {
155
+ const srcPath = join(configSrc, file);
156
+ const destPath = join(configDest, file);
157
+
158
+ // Read, sort for determinism, and write
159
+ const data = JSON.parse(readFileSync(srcPath, "utf8"));
160
+ const sorted = sortObject(data);
161
+ writeFileSync(destPath, JSON.stringify(sorted, null, 2) + "\n");
162
+ }
163
+
164
+ console.log(
165
+ `Build complete – ${generatedFiles.length} artifacts written to dist/config/`,
166
+ );
167
+ }
168
+
169
+ // ESM entrypoint check
170
+ if (process.argv[1] === __filename) {
171
+ main();
172
+ }
@@ -0,0 +1,61 @@
1
+ // scripts/detect-bazel.test.ts
2
+
3
+ import { describe, it, expect } from "vitest";
4
+ import { detectBazel } from "./detect-bazel.js";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const fixtures = path.resolve(__dirname, "..", "test", "fixtures");
10
+
11
+ describe("detectBazel", () => {
12
+ it("detects bzlmod repo via MODULE.bazel", () => {
13
+ const result = detectBazel(path.join(fixtures, "bzlmod-repo"));
14
+ expect(result.detected).toBe(true);
15
+ expect(result.mode).toBe("bzlmod");
16
+ expect(result.markers).toContain("MODULE.bazel");
17
+ });
18
+
19
+ it("detects .bazelversion as optional marker", () => {
20
+ const result = detectBazel(path.join(fixtures, "bzlmod-repo"));
21
+ expect(result.markers).toContain(".bazelversion");
22
+ });
23
+
24
+ it("detects workspace repo via WORKSPACE.bazel", () => {
25
+ const result = detectBazel(path.join(fixtures, "workspace-repo"));
26
+ expect(result.detected).toBe(true);
27
+ expect(result.mode).toBe("workspace");
28
+ expect(result.markers).toContain("WORKSPACE.bazel");
29
+ });
30
+
31
+ it("does NOT detect non-Bazel repo (regression)", () => {
32
+ const result = detectBazel(path.join(fixtures, "no-bazel-repo"));
33
+ expect(result.detected).toBe(false);
34
+ expect(result.mode).toBeUndefined();
35
+ expect(result.markers).toHaveLength(0);
36
+ });
37
+
38
+ it("detects hybrid monorepo at root level only", () => {
39
+ const result = detectBazel(path.join(fixtures, "hybrid-monorepo"));
40
+ expect(result.detected).toBe(true);
41
+ expect(result.mode).toBe("bzlmod");
42
+ expect(result.markers).toContain("MODULE.bazel");
43
+ // Does NOT include nested BUILD files in markers
44
+ expect(result.markers).not.toContain("apps/web/BUILD.bazel");
45
+ expect(result.markers).not.toContain("libs/vendored/BUILD.bazel");
46
+ });
47
+
48
+ it("prefers bzlmod over workspace when MODULE.bazel exists", () => {
49
+ // The bzlmod-repo fixture has MODULE.bazel, should be detected as bzlmod
50
+ const result = detectBazel(path.join(fixtures, "bzlmod-repo"));
51
+ expect(result.mode).toBe("bzlmod");
52
+ });
53
+
54
+ it("does NOT detect Bazel when only nested BUILD files exist (no root markers)", () => {
55
+ // This validates the documented contract: detection uses repo-root markers only
56
+ const result = detectBazel(path.join(fixtures, "nested-build-only"));
57
+ expect(result.detected).toBe(false);
58
+ expect(result.mode).toBeUndefined();
59
+ expect(result.markers).toHaveLength(0);
60
+ });
61
+ });
@@ -0,0 +1,82 @@
1
+ // scripts/detect-bazel.ts
2
+
3
+ /**
4
+ * Standalone Bazel detection utility.
5
+ * Does NOT require Bazel to be installed.
6
+ * Only checks repo-root markers to avoid false positives from vendored deps.
7
+ */
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+
12
+ export interface BazelDetectionResult {
13
+ /** Whether Bazel markers were detected at repo root */
14
+ detected: boolean;
15
+ /** Bazel mode: bzlmod (MODULE.bazel) or workspace (WORKSPACE*) */
16
+ mode?: "bzlmod" | "workspace";
17
+ /** List of detected marker files */
18
+ markers: string[];
19
+ }
20
+
21
+ /** Root-level markers that indicate a Bazel repo */
22
+ const ROOT_MARKERS = {
23
+ bzlmod: ["MODULE.bazel"],
24
+ workspace: ["WORKSPACE.bazel", "WORKSPACE"],
25
+ };
26
+
27
+ /** Optional markers that support Bazel but don't trigger detection alone */
28
+ const OPTIONAL_MARKERS = [".bazelrc", ".bazelversion"];
29
+
30
+ /**
31
+ * Detect Bazel at repo root only.
32
+ * Does NOT scan subdirectories for BUILD files (avoids vendored deps false positives).
33
+ *
34
+ * @param repoRoot - Absolute path to repository root
35
+ * @returns Detection result with mode and found markers
36
+ */
37
+ export function detectBazel(repoRoot: string): BazelDetectionResult {
38
+ const foundMarkers: string[] = [];
39
+ let mode: "bzlmod" | "workspace" | undefined;
40
+
41
+ // Check bzlmod first (preferred)
42
+ for (const marker of ROOT_MARKERS.bzlmod) {
43
+ if (fs.existsSync(path.join(repoRoot, marker))) {
44
+ foundMarkers.push(marker);
45
+ mode = "bzlmod";
46
+ }
47
+ }
48
+
49
+ // Check workspace if no bzlmod
50
+ if (!mode) {
51
+ for (const marker of ROOT_MARKERS.workspace) {
52
+ if (fs.existsSync(path.join(repoRoot, marker))) {
53
+ foundMarkers.push(marker);
54
+ mode = "workspace";
55
+ break; // Only need one workspace marker
56
+ }
57
+ }
58
+ }
59
+
60
+ // Check optional markers (don't affect detection, just informational)
61
+ for (const marker of OPTIONAL_MARKERS) {
62
+ if (fs.existsSync(path.join(repoRoot, marker))) {
63
+ foundMarkers.push(marker);
64
+ }
65
+ }
66
+
67
+ return {
68
+ detected: mode !== undefined,
69
+ mode,
70
+ markers: foundMarkers,
71
+ };
72
+ }
73
+
74
+ // CLI entrypoint for manual testing
75
+ if (
76
+ import.meta.url.startsWith("file:") &&
77
+ process.argv[1]?.includes("detect-bazel")
78
+ ) {
79
+ const targetDir = process.argv[2] || process.cwd();
80
+ const result = detectBazel(path.resolve(targetDir));
81
+ console.log(JSON.stringify(result, null, 2));
82
+ }
@@ -0,0 +1,174 @@
1
+ // scripts/generate-instructions.ts
2
+ //
3
+ // Generates instructions.md from a stack+CI specific JSON file.
4
+ // Usage: npx ts-node-esm scripts/generate-instructions.ts [config-file]
5
+ // Example: npm run generate:instructions standards.python.github-actions.json
6
+ // Default: config/standards.typescript-js.github-actions.json
7
+
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+
11
+ interface StackHints {
12
+ exampleTools?: string[];
13
+ exampleConfigFiles?: string[];
14
+ notes?: string;
15
+ verification?: string;
16
+ requiredFiles?: string[];
17
+ optionalFiles?: string[];
18
+ requiredScripts?: string[];
19
+ }
20
+
21
+ interface ChecklistItem {
22
+ id: string;
23
+ label: string;
24
+ description: string;
25
+ ciHints?: Record<string, { job?: string; stage?: string }>;
26
+ stack?: StackHints;
27
+ }
28
+
29
+ interface StackChecklistJson {
30
+ version: number;
31
+ stack: string;
32
+ stackLabel: string;
33
+ ciSystems: string[];
34
+ checklist: {
35
+ core: ChecklistItem[];
36
+ recommended: ChecklistItem[];
37
+ optionalEnhancements: ChecklistItem[];
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Generate 2-5 high-level bullets from a checklist item.
43
+ */
44
+ function generateBullets(item: ChecklistItem): string[] {
45
+ const bullets: string[] = [];
46
+ const stack = item.stack;
47
+
48
+ // Bullet 1: Core purpose from description
49
+ bullets.push(item.description);
50
+
51
+ // Bullet 2: Required files if any
52
+ if (stack?.requiredFiles?.length) {
53
+ const files = stack.requiredFiles.join(", ");
54
+ bullets.push(`Ensure ${files} exists in the repository.`);
55
+ }
56
+
57
+ // Bullet 3: Optional files mention
58
+ if (stack?.optionalFiles?.length) {
59
+ const files = stack.optionalFiles.slice(0, 3).join(", ");
60
+ const more = stack.optionalFiles.length > 3 ? " and others" : "";
61
+ bullets.push(`Consider adding ${files}${more} if applicable.`);
62
+ }
63
+
64
+ // Bullet 4: Required scripts
65
+ if (stack?.requiredScripts?.length) {
66
+ const scripts = stack.requiredScripts.map((s) => `\`${s}\``).join(", ");
67
+ bullets.push(`Define a ${scripts} script or equivalent command.`);
68
+ }
69
+
70
+ // Bullet 5: Notes-based guidance (if short enough)
71
+ if (stack?.notes && stack.notes.length < 150) {
72
+ bullets.push(stack.notes);
73
+ }
74
+
75
+ // Limit to 5 bullets
76
+ return bullets.slice(0, 5);
77
+ }
78
+
79
+ function generateSection(title: string, items: ChecklistItem[]): string {
80
+ if (items.length === 0) return "";
81
+
82
+ const lines: string[] = [];
83
+ lines.push(`## ${title}`);
84
+ lines.push("");
85
+
86
+ for (const item of items) {
87
+ lines.push(`### ${item.label}`);
88
+ lines.push("");
89
+
90
+ const bullets = generateBullets(item);
91
+ for (const bullet of bullets) {
92
+ lines.push(`- ${bullet}`);
93
+ }
94
+ lines.push("");
95
+ }
96
+
97
+ return lines.join("\n");
98
+ }
99
+
100
+ function main() {
101
+ const rootDir = process.cwd();
102
+
103
+ // Accept config file as CLI argument, default to TypeScript+GitHub Actions
104
+ const configArg = process.argv[2];
105
+ let inputPath: string;
106
+
107
+ if (configArg) {
108
+ // If argument provided, resolve relative to config/ or as absolute
109
+ if (path.isAbsolute(configArg)) {
110
+ inputPath = configArg;
111
+ } else if (
112
+ configArg.startsWith("config/") ||
113
+ configArg.startsWith("config\\")
114
+ ) {
115
+ inputPath = path.join(rootDir, configArg);
116
+ } else {
117
+ inputPath = path.join(rootDir, "config", configArg);
118
+ }
119
+ } else {
120
+ inputPath = path.join(
121
+ rootDir,
122
+ "config",
123
+ "standards.typescript-js.github-actions.json",
124
+ );
125
+ }
126
+
127
+ // Derive output filename from input (e.g., standards.python.json -> instructions.python.md)
128
+ const inputBasename = path.basename(inputPath, ".json");
129
+ const outputBasename = inputBasename.replace(/^standards\./, "instructions.");
130
+ const outputPath = path.join(rootDir, `${outputBasename}.md`);
131
+
132
+ if (!fs.existsSync(inputPath)) {
133
+ console.error(`Input file not found: ${inputPath}`);
134
+ console.error("Usage: npm run generate:instructions [config-file]");
135
+ console.error(
136
+ "Example: npm run generate:instructions standards.python.github-actions.json",
137
+ );
138
+ process.exit(1);
139
+ }
140
+
141
+ const raw = fs.readFileSync(inputPath, "utf8");
142
+ const data: StackChecklistJson = JSON.parse(raw);
143
+
144
+ const lines: string[] = [];
145
+
146
+ // Header
147
+ lines.push("# Repository Standards Instructions");
148
+ lines.push("");
149
+ lines.push(`> Auto-generated from \`${path.relative(rootDir, inputPath)}\``);
150
+ lines.push(`> Stack: ${data.stackLabel} | CI: ${data.ciSystems.join(", ")}`);
151
+ lines.push("");
152
+ lines.push(
153
+ "This document provides high-level guidance for an autonomous coding agent to bring a repository into compliance with the defined standards.",
154
+ );
155
+ lines.push("");
156
+
157
+ // Generate sections
158
+ lines.push(generateSection("Core Requirements", data.checklist.core));
159
+ lines.push(
160
+ generateSection("Recommended Practices", data.checklist.recommended),
161
+ );
162
+ lines.push(
163
+ generateSection(
164
+ "Optional Enhancements",
165
+ data.checklist.optionalEnhancements,
166
+ ),
167
+ );
168
+
169
+ // Write output
170
+ fs.writeFileSync(outputPath, lines.join("\n"));
171
+ console.log(`Wrote ${outputPath}`);
172
+ }
173
+
174
+ main();
@@ -0,0 +1,247 @@
1
+ // scripts/generate-standards.ts
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ type StackId = "typescript-js" | "csharp-dotnet" | "python" | "rust" | "go";
7
+ type CiSystem = "azure-devops" | "github-actions";
8
+
9
+ interface StackMeta {
10
+ label: string;
11
+ languageFamily: string;
12
+ }
13
+
14
+ interface CiHintsForSystem {
15
+ stage?: string;
16
+ job?: string;
17
+ }
18
+
19
+ type CiHints = Partial<Record<CiSystem, CiHintsForSystem>>;
20
+
21
+ /**
22
+ * Bazel execution hints for individual checklist items.
23
+ * Commands are actual Bazel invocations (e.g., "bazel test //..."),
24
+ * NOT assumed pattern labels.
25
+ */
26
+ interface BazelHints {
27
+ /** Bazel commands to run (e.g., "bazel test //...", "bazel run //tools/lint") */
28
+ commands?: string[];
29
+ /** Recommended target conventions (documentation only, not assumed to exist) */
30
+ recommendedTargets?: string[];
31
+ /** Usage notes for this check in Bazel context */
32
+ notes?: string;
33
+ }
34
+
35
+ interface StackHints {
36
+ exampleTools?: string[];
37
+ exampleConfigFiles?: string[];
38
+ notes?: string;
39
+ // Human‑readable verification instructions
40
+ verification?: string;
41
+ // Machine‑readable additions (all optional)
42
+ requiredFiles?: string[];
43
+ optionalFiles?: string[];
44
+ requiredScripts?: string[];
45
+ // Either-or file compliance: at least one of these files must exist
46
+ anyOfFiles?: string[];
47
+ // Version pinning guidance for deterministic CI
48
+ pinningNotes?: string;
49
+ machineCheck?: {
50
+ command: string;
51
+ expectExitCode?: number;
52
+ description?: string;
53
+ };
54
+ // Bazel execution hints (v3+)
55
+ bazelHints?: BazelHints;
56
+ }
57
+
58
+ interface ChecklistItemMaster {
59
+ id: string;
60
+ label: string;
61
+ description: string;
62
+ appliesTo: {
63
+ stacks: StackId[];
64
+ ciSystems?: CiSystem[];
65
+ };
66
+ ciHints?: CiHints;
67
+ stackHints?: Partial<Record<StackId, StackHints>>;
68
+ }
69
+
70
+ interface ChecklistSection {
71
+ core: ChecklistItemMaster[];
72
+ recommended: ChecklistItemMaster[];
73
+ optionalEnhancements: ChecklistItemMaster[];
74
+ }
75
+
76
+ interface MigrationStep {
77
+ step: number;
78
+ title: string;
79
+ description: string;
80
+ focusIds?: string[];
81
+ notes?: string;
82
+ }
83
+
84
+ interface Meta {
85
+ defaultCoverageThreshold?: number;
86
+ complexityChecks?: {
87
+ enabledByDefault?: boolean;
88
+ description?: string;
89
+ };
90
+ qualityGatePolicy?: {
91
+ preferSoftFailOnLegacy?: boolean;
92
+ description?: string;
93
+ };
94
+ migrationGuide?: MigrationStep[];
95
+ }
96
+
97
+ interface MasterJson {
98
+ version: number;
99
+ meta?: Meta;
100
+ ciSystems: CiSystem[];
101
+ stacks: Record<StackId, StackMeta>;
102
+ checklist: ChecklistSection;
103
+ }
104
+
105
+ interface StackItem {
106
+ id: string;
107
+ label: string;
108
+ description: string;
109
+ ciHints?: CiHints;
110
+ // For the filtered file, this is the single stack’s hints including verification
111
+ stack?: StackHints;
112
+ }
113
+
114
+ interface StackChecklistJson {
115
+ version: number;
116
+ stack: StackId;
117
+ stackLabel: string;
118
+ ciSystems: CiSystem[];
119
+ meta?: Meta;
120
+ checklist: {
121
+ core: StackItem[];
122
+ recommended: StackItem[];
123
+ optionalEnhancements: StackItem[];
124
+ };
125
+ }
126
+
127
+ function filterSectionForStackAndCi(
128
+ items: ChecklistItemMaster[],
129
+ stack: StackId,
130
+ ciSystem?: CiSystem,
131
+ ): StackItem[] {
132
+ return items
133
+ .filter((item) => {
134
+ if (!item.appliesTo.stacks.includes(stack)) return false;
135
+
136
+ if (ciSystem && item.appliesTo.ciSystems) {
137
+ return item.appliesTo.ciSystems.includes(ciSystem);
138
+ }
139
+ return true;
140
+ })
141
+ .map((item) => {
142
+ const stackHint = item.stackHints?.[stack];
143
+
144
+ const result: StackItem = {
145
+ id: item.id,
146
+ label: item.label,
147
+ description: item.description,
148
+ };
149
+
150
+ if (item.ciHints) {
151
+ if (ciSystem) {
152
+ const perSystem = item.ciHints[ciSystem];
153
+ if (perSystem) {
154
+ result.ciHints = { [ciSystem]: perSystem };
155
+ }
156
+ } else {
157
+ result.ciHints = item.ciHints;
158
+ }
159
+ }
160
+
161
+ if (stackHint) {
162
+ // Includes exampleTools, exampleConfigFiles, notes, verification
163
+ result.stack = stackHint;
164
+ }
165
+
166
+ return result;
167
+ });
168
+ }
169
+
170
+ function generateStackJson(
171
+ master: MasterJson,
172
+ stack: StackId,
173
+ ciSystem?: CiSystem,
174
+ ): StackChecklistJson {
175
+ const stackMeta = master.stacks[stack];
176
+ const ciSystems = ciSystem ? [ciSystem] : master.ciSystems;
177
+
178
+ return {
179
+ version: master.version,
180
+ stack,
181
+ stackLabel: stackMeta?.label ?? stack,
182
+ ciSystems,
183
+ meta: master.meta,
184
+ checklist: {
185
+ core: filterSectionForStackAndCi(master.checklist.core, stack, ciSystem),
186
+ recommended: filterSectionForStackAndCi(
187
+ master.checklist.recommended,
188
+ stack,
189
+ ciSystem,
190
+ ),
191
+ optionalEnhancements: filterSectionForStackAndCi(
192
+ master.checklist.optionalEnhancements,
193
+ stack,
194
+ ciSystem,
195
+ ),
196
+ },
197
+ };
198
+ }
199
+
200
+ // --- entrypoint ---
201
+ const rootDir = path.join(process.cwd());
202
+ const masterPath = path.join(rootDir, "config", "standards.json");
203
+
204
+ const raw = fs.readFileSync(masterPath, "utf8");
205
+ const master: MasterJson = JSON.parse(raw);
206
+
207
+ // args: stack [ciSystem]
208
+ // args: stack [ciSystem]
209
+ const STACK_ALIASES: Record<string, StackId> = {
210
+ dotnet: "csharp-dotnet",
211
+ csharp: "csharp-dotnet",
212
+ ts: "typescript-js",
213
+ js: "typescript-js",
214
+ python: "python",
215
+ py: "python",
216
+ "typescript-js": "typescript-js",
217
+ "csharp-dotnet": "csharp-dotnet",
218
+ rust: "rust",
219
+ rs: "rust",
220
+ go: "go",
221
+ golang: "go",
222
+ };
223
+
224
+ const rawArg = process.argv[2] || "typescript-js";
225
+ const targetStack = STACK_ALIASES[rawArg.toLowerCase()];
226
+
227
+ if (!targetStack) {
228
+ console.error(`Unknown stack: ${rawArg}`);
229
+ console.error(
230
+ `Available stacks: ${["typescript-js", "csharp-dotnet", "python", "rust", "go"].join(", ")}`,
231
+ );
232
+ process.exit(1);
233
+ }
234
+
235
+ const targetCiSystem = process.argv[3] as CiSystem | undefined;
236
+
237
+ const stackJson = generateStackJson(master, targetStack, targetCiSystem);
238
+
239
+ const outDir = path.join(rootDir, "config");
240
+ fs.mkdirSync(outDir, { recursive: true });
241
+
242
+ const ciSuffix = targetCiSystem ? `.${targetCiSystem}` : "";
243
+ const outPath = path.join(outDir, `standards.${targetStack}${ciSuffix}.json`);
244
+
245
+ fs.writeFileSync(outPath, JSON.stringify(stackJson, null, 2) + "\n");
246
+
247
+ console.log(`Wrote ${outPath}`);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Syncs manifest.json version with package.json
3
+ * Called by semantic-release exec plugin during release
4
+ *
5
+ * @see https://github.com/semantic-release/exec
6
+ */
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const { execSync } = require("child_process");
10
+
11
+ const pkgPath = path.join(process.cwd(), "package.json");
12
+ const manifestPath = path.join(process.cwd(), "manifest.json");
13
+
14
+ if (!fs.existsSync(manifestPath)) {
15
+ console.log("[sync-manifest] No manifest.json found, skipping");
16
+ process.exit(0);
17
+ }
18
+
19
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
20
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
21
+
22
+ manifest.version = pkg.version;
23
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
24
+
25
+ // Run prettier to ensure consistent formatting
26
+ try {
27
+ execSync("npx prettier --write manifest.json", { stdio: "inherit" });
28
+ } catch {
29
+ console.log("[sync-manifest] prettier not available, skipping format");
30
+ }
31
+
32
+ console.log(`[sync-manifest] Updated manifest.json to ${pkg.version}`);
@@ -0,0 +1,289 @@
1
+ // scripts/validate-schema.ts
2
+ // Validates standards.json against JSON Schema and performs additional semantic checks
3
+
4
+ import Ajv, { ErrorObject } from "ajv";
5
+ import addFormats from "ajv-formats";
6
+ import stableStringify from "fast-json-stable-stringify";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+
10
+ const rootDir = process.cwd();
11
+ const configPath = path.join(rootDir, "config", "standards.json");
12
+ const schemaPath = path.join(rootDir, "config", "standards.schema.json");
13
+
14
+ interface ValidationResult {
15
+ valid: boolean;
16
+ errors: string[];
17
+ }
18
+
19
+ interface ChecklistItem {
20
+ id: string;
21
+ appliesTo?: { stacks?: string[]; ciSystems?: string[] };
22
+ ciHints?: Record<string, unknown>;
23
+ stackHints?: Record<string, unknown>;
24
+ }
25
+
26
+ interface MigrationStep {
27
+ focusIds?: string[];
28
+ }
29
+
30
+ interface Config {
31
+ version: number;
32
+ ciSystems: string[];
33
+ stacks: Record<string, unknown>;
34
+ meta?: {
35
+ defaultCoverageThreshold?: number;
36
+ coverageThresholdUnit?: string;
37
+ migrationGuide?: MigrationStep[];
38
+ };
39
+ checklist: {
40
+ core: ChecklistItem[];
41
+ recommended: ChecklistItem[];
42
+ optionalEnhancements: ChecklistItem[];
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Validate config against JSON Schema using Ajv
48
+ */
49
+ function validateSchema(config: unknown, schema: unknown): ValidationResult {
50
+ const ajv = new Ajv.default({ allErrors: true, strict: true });
51
+ addFormats.default(ajv);
52
+
53
+ const validate = ajv.compile(schema as object);
54
+ const valid = validate(config);
55
+
56
+ if (!valid && validate.errors) {
57
+ return {
58
+ valid: false,
59
+ errors: validate.errors.map(
60
+ (e: ErrorObject) =>
61
+ `${e.instancePath || "/"}: ${e.message} (${JSON.stringify(e.params)})`,
62
+ ),
63
+ };
64
+ }
65
+
66
+ return { valid: true, errors: [] };
67
+ }
68
+
69
+ /**
70
+ * Validate that all checklist IDs are unique across all sections
71
+ */
72
+ function validateUniqueIds(config: Config): ValidationResult {
73
+ const allItems = [
74
+ ...config.checklist.core,
75
+ ...config.checklist.recommended,
76
+ ...config.checklist.optionalEnhancements,
77
+ ];
78
+
79
+ const ids = allItems.map((item) => item.id);
80
+ const seen = new Set<string>();
81
+ const duplicates: string[] = [];
82
+
83
+ for (const id of ids) {
84
+ if (seen.has(id)) {
85
+ duplicates.push(id);
86
+ }
87
+ seen.add(id);
88
+ }
89
+
90
+ if (duplicates.length > 0) {
91
+ return {
92
+ valid: false,
93
+ errors: [`Duplicate checklist IDs found: ${duplicates.join(", ")}`],
94
+ };
95
+ }
96
+
97
+ return { valid: true, errors: [] };
98
+ }
99
+
100
+ /**
101
+ * Validate that migrationGuide focusIds reference existing checklist IDs
102
+ */
103
+ function validateFocusIdReferences(config: Config): ValidationResult {
104
+ const allIds = new Set([
105
+ ...config.checklist.core.map((i) => i.id),
106
+ ...config.checklist.recommended.map((i) => i.id),
107
+ ...config.checklist.optionalEnhancements.map((i) => i.id),
108
+ ]);
109
+
110
+ const errors: string[] = [];
111
+ const migrationGuide = config.meta?.migrationGuide ?? [];
112
+
113
+ for (const step of migrationGuide) {
114
+ for (const focusId of step.focusIds ?? []) {
115
+ if (!allIds.has(focusId)) {
116
+ errors.push(
117
+ `migrationGuide focusId "${focusId}" does not reference a valid checklist ID`,
118
+ );
119
+ }
120
+ }
121
+ }
122
+
123
+ return { valid: errors.length === 0, errors };
124
+ }
125
+
126
+ /**
127
+ * Validate that appliesTo.stacks only references known stack keys
128
+ */
129
+ function validateStackReferences(config: Config): ValidationResult {
130
+ const validStacks = new Set(Object.keys(config.stacks));
131
+ const errors: string[] = [];
132
+
133
+ const allItems = [
134
+ ...config.checklist.core,
135
+ ...config.checklist.recommended,
136
+ ...config.checklist.optionalEnhancements,
137
+ ];
138
+
139
+ for (const item of allItems) {
140
+ for (const stack of item.appliesTo?.stacks ?? []) {
141
+ if (!validStacks.has(stack)) {
142
+ errors.push(
143
+ `Item "${item.id}" references unknown stack "${stack}" in appliesTo.stacks`,
144
+ );
145
+ }
146
+ }
147
+ }
148
+
149
+ return { valid: errors.length === 0, errors };
150
+ }
151
+
152
+ /**
153
+ * Validate that ciHints keys are a subset of ciSystems
154
+ */
155
+ function validateCiHintKeys(config: Config): ValidationResult {
156
+ const validCiSystems = new Set(config.ciSystems);
157
+ const errors: string[] = [];
158
+
159
+ const allItems = [
160
+ ...config.checklist.core,
161
+ ...config.checklist.recommended,
162
+ ...config.checklist.optionalEnhancements,
163
+ ];
164
+
165
+ for (const item of allItems) {
166
+ for (const ciKey of Object.keys(item.ciHints ?? {})) {
167
+ if (!validCiSystems.has(ciKey)) {
168
+ errors.push(
169
+ `Item "${item.id}" has ciHints key "${ciKey}" not in ciSystems`,
170
+ );
171
+ }
172
+ }
173
+ }
174
+
175
+ return { valid: errors.length === 0, errors };
176
+ }
177
+
178
+ /**
179
+ * Validate coverage threshold semantics: if unit is "ratio", threshold must be 0-1
180
+ */
181
+ function validateCoverageThreshold(config: Config): ValidationResult {
182
+ const threshold = config.meta?.defaultCoverageThreshold;
183
+ const unit = config.meta?.coverageThresholdUnit;
184
+
185
+ if (unit === "ratio" && threshold !== undefined) {
186
+ if (threshold < 0 || threshold > 1) {
187
+ return {
188
+ valid: false,
189
+ errors: [
190
+ `defaultCoverageThreshold is ${threshold} but coverageThresholdUnit is "ratio" (must be 0-1)`,
191
+ ],
192
+ };
193
+ }
194
+ }
195
+
196
+ return { valid: true, errors: [] };
197
+ }
198
+
199
+ /**
200
+ * Generate a normalized, deterministic string representation of the config.
201
+ * Uses deep stable key ordering at all depths.
202
+ */
203
+ export function normalizeConfig(config: unknown): string {
204
+ return stableStringify(config);
205
+ }
206
+
207
+ /**
208
+ * Run all validations and return combined result
209
+ */
210
+ export function validateStandardsConfig(
211
+ configRaw: string,
212
+ schemaRaw: string,
213
+ ): ValidationResult {
214
+ let config: Config;
215
+ let schema: unknown;
216
+
217
+ try {
218
+ config = JSON.parse(configRaw);
219
+ } catch {
220
+ return { valid: false, errors: ["Failed to parse standards.json as JSON"] };
221
+ }
222
+
223
+ try {
224
+ schema = JSON.parse(schemaRaw);
225
+ } catch {
226
+ return {
227
+ valid: false,
228
+ errors: ["Failed to parse standards.schema.json as JSON"],
229
+ };
230
+ }
231
+
232
+ const results: ValidationResult[] = [
233
+ validateSchema(config, schema),
234
+ validateUniqueIds(config),
235
+ validateFocusIdReferences(config),
236
+ validateStackReferences(config),
237
+ validateCiHintKeys(config),
238
+ validateCoverageThreshold(config),
239
+ ];
240
+
241
+ const allErrors = results.flatMap((r) => r.errors);
242
+ return {
243
+ valid: allErrors.length === 0,
244
+ errors: allErrors,
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Main entry point for CLI usage
250
+ */
251
+ export function validateStandardsSchema(): void {
252
+ if (!fs.existsSync(configPath)) {
253
+ console.error(`Config file not found: ${configPath}`);
254
+ process.exit(1);
255
+ }
256
+
257
+ if (!fs.existsSync(schemaPath)) {
258
+ console.error(`Schema file not found: ${schemaPath}`);
259
+ process.exit(1);
260
+ }
261
+
262
+ const configRaw = fs.readFileSync(configPath, "utf8");
263
+ const schemaRaw = fs.readFileSync(schemaPath, "utf8");
264
+
265
+ const result = validateStandardsConfig(configRaw, schemaRaw);
266
+
267
+ if (!result.valid) {
268
+ console.error("Schema validation failed:");
269
+ for (const error of result.errors) {
270
+ console.error(` - ${error}`);
271
+ }
272
+ process.exit(1);
273
+ }
274
+
275
+ console.log("✓ Schema validation passed");
276
+ console.log("✓ All checklist IDs are unique");
277
+ console.log("✓ All migrationGuide focusIds reference valid IDs");
278
+ console.log("✓ All appliesTo.stacks reference valid stack keys");
279
+ console.log("✓ All ciHints keys are valid ciSystems");
280
+ console.log("✓ Coverage threshold semantics are valid");
281
+ }
282
+
283
+ // CLI entry point
284
+ if (
285
+ import.meta.url.startsWith("file:") &&
286
+ process.argv[1]?.includes("validate-schema")
287
+ ) {
288
+ validateStandardsSchema();
289
+ }