diff --git a/.gitignore b/.gitignore index 0338461..4f494d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,79 +1,5 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -# RSR-compliant .gitignore - -# OS & Editor -.DS_Store -Thumbs.db -*.swp -*.swo -*~ -.idea/ -.vscode/ - -# Build -/target/ -/_build/ -/build/ -/dist/ -/out/ - -# Dependencies -/node_modules/ -/vendor/ -/deps/ -/.elixir_ls/ - -# Rust -# Cargo.lock # Keep for binaries - -# Elixir -/cover/ -/doc/ -*.ez -erl_crash.dump - -# Julia -*.jl.cov -*.jl.mem -/Manifest.toml - -# ReScript -/lib/bs/ -/.bsb.lock - -# Python (SaltStack only) -__pycache__/ -*.py[cod] -.venv/ - -# Ada/SPARK -*.ali -/obj/ -/bin/ - -# Haskell -/.stack-work/ -/dist-newstyle/ - -# Chapel -*.chpl.tmp.* - -# Secrets -.env -.env.* -*.pem -*.key -secrets/ - -# Test/Coverage -/coverage/ -htmlcov/ - -# Logs -*.log -/logs/ - -# Temp -/tmp/ -*.tmp -*.bak +lib/ +node_modules/ +.bsb.lock +*.res.js +.merlin diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..9fe8841 --- /dev/null +++ b/LICENCE @@ -0,0 +1,17 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2025 Hyperpolymath + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/README.adoc b/README.adoc index 0e79451..53ac8b4 100644 --- a/README.adoc +++ b/README.adoc @@ -1,219 +1,50 @@ -= RSR Template Repository +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Hyperpolymath -image:https://img.shields.io/badge/license-AGPL--3.0-blue.svg[AGPL-3.0,link="https://www.gnu.org/licenses/agpl-3.0"] image:https://img.shields.io/badge/philosophy-Palimpsest-purple.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-licence"] += rescript-env :toc: -:sectnums: +:toc-placement: preamble +:icons: font -// Badges -image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] -image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] -image:https://img.shields.io/badge/License-AGPL%20OR%20Palimpsest-blue[License] -image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] +**Type-safe environment variable access for ReScript with runtime detection.** -== Overview +Part of the https://github.com/hyperpolymath/rescript-full-stack[ReScript Full Stack] ecosystem. -**The canonical template for RSR (Rhodium Standard Repository) projects.** +== Features -This repository provides the standardized structure, configuration, and tooling for all 139 repos in the hyperpolymath ecosystem. Use it to: +* **Type-safe access** with proper option types +* **Runtime detection** for Deno, Bun, and Node.js +* **Type coercion** for int, float, and boolean values +* **Environment helpers** for development/production/test detection +* **Zero dependencies** beyond ReScript core -* Bootstrap new projects with RSR compliance -* Reference the standard directory structure -* Copy configuration templates (justfile, STATE.scm, etc.) - -== Quick Start - -[source,bash] ----- -# Clone the template -git clone https://github.com/hyperpolymath/RSR-template-repo my-project -cd my-project - -# Remove template git history -rm -rf .git -git init - -# Customize -sed -i 's/RSR-template-repo/my-project/g' justfile guix.scm README.adoc - -# Enter development environment -guix shell -D -f guix.scm - -# Validate compliance -just validate-rsr ----- - -== What's Included - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|`.editorconfig` -|Editor configuration (indent, charset) - -|`.gitignore` -|Standard ignore patterns - -|`.guix-channel` -|Guix channel definition - -|`.well-known/` -|RFC-compliant metadata (security.txt, ai.txt, humans.txt) - -|`docs/` -|Documentation directory - -|`guix.scm` -|Guix package definition - -|`justfile` -|Task runner with 50+ recipes - -|`LICENSE.txt` -|AGPL + Palimpsest dual license - -|`README.adoc` -|This file - -|`RSR_COMPLIANCE.adoc` -|Compliance tracking - -|`STATE.scm` -|Project state checkpoint -|=== - -== Justfile Features - -The template justfile provides: - -* **~10 billion recipe combinations** via matrix recipes -* **Cookbook generation**: `just cookbook` → `docs/just-cookbook.adoc` -* **Man page generation**: `just man` → `docs/man/project.1` -* **RSR validation**: `just validate-rsr` -* **STATE.scm management**: `just state-touch`, `just state-phase` -* **Container support**: `just container-build`, `just container-push` -* **CI matrix**: `just ci-matrix [stage] [depth]` - -=== Key Recipes +== Installation [source,bash] ---- -just # Show all recipes -just help # Detailed help -just info # Project info -just combinations # Show matrix options - -just build # Build (debug) -just test # Run tests -just quality # Format + lint + test -just ci # Full CI pipeline - -just validate # RSR + STATE validation -just docs # Generate all docs -just cookbook # Generate justfile docs - -just guix-shell # Guix dev environment -just container-build # Build container ----- - -== Directory Structure - -[source] +deno add jsr:@hyperpolymath/rescript-env ---- -project/ -├── .editorconfig # Editor settings -├── .gitignore # Git ignore -├── .guix-channel # Guix channel -├── .well-known/ # RFC metadata -│ ├── ai.txt -│ ├── humans.txt -│ └── security.txt -├── config/ # Nickel configs (optional) -├── docs/ # Documentation -│ ├── generated/ -│ ├── man/ -│ └── just-cookbook.adoc -├── guix.scm # Guix package -├── justfile # Task runner -├── LICENSE.txt # Dual license -├── README.adoc # Overview -├── RSR_COMPLIANCE.adoc # Compliance -├── src/ # Source code -├── STATE.scm # State checkpoint -└── tests/ # Tests ----- - -== RSR Compliance - -=== Language Tiers - -* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, ReScript -* **Tier 2** (Silver): Nickel, Racket, Guile Scheme, Nix -* **Infrastructure**: Guix channels, derivations - -=== Required Files - -* `.editorconfig` -* `.gitignore` -* `justfile` -* `README.adoc` -* `RSR_COMPLIANCE.adoc` -* `LICENSE.txt` (AGPL + Palimpsest) -* `.well-known/security.txt` -* `.well-known/ai.txt` -* `.well-known/humans.txt` -* `guix.scm` OR `flake.nix` -=== Prohibited - -* Python outside `salt/` directory -* TypeScript/JavaScript (use ReScript) -* CUE (use Guile/Nickel) -* `Dockerfile` (use `Containerfile`) - -== STATE.scm - -The STATE.scm file tracks project state: +== Quick Start -[source,scheme] ----- -(define state - `((metadata - (project . "my-project") - (updated . "2025-12-10")) - (position - (phase . implementation) ; design|implementation|testing|maintenance|archived - (maturity . beta)) ; experimental|alpha|beta|production|lts - (ecosystem - (part-of . ("RSR Framework")) - (depends-on . ())))) +[source,rescript] ---- +// Get required configuration +let dbUrl = Env.getExn("DATABASE_URL") -== Badge Schema +// Get optional with default +let port = Env.getOr("PORT", "3000") -Generate badges from STATE.scm: +// Get typed values +let maxConnections = Env.getInt("MAX_CONNECTIONS") +let debugMode = Env.getBool("DEBUG") -[source,bash] ----- -just badges standard +// Check environment +if Env.isDevelopment() { + Console.log("Running in development mode") +} ---- -See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. - -== Ecosystem Integration - -This template is part of: - -* **STATE.scm Ecosystem**: Conversation checkpoints -* **RSR Framework**: Repository standards -* **Consent-Aware-HTTP**: .well-known compliance - -== License - -SPDX-License-Identifier: `AGPL-3.0-or-later OR LicenseRef-Palimpsest-0.5` - -== Links +== Licence -* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE.scm tooling -* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement -* https://rhodium.sh[Rhodium Standard] - RSR documentation +AGPL-3.0-or-later diff --git a/REVIEW.adoc b/REVIEW.adoc new file mode 100644 index 0000000..6ddb61c --- /dev/null +++ b/REVIEW.adoc @@ -0,0 +1,225 @@ += Repository Review: rsr-template-repo (rescript-env) +:toc: +:sectnums: +:date: 2026-01-04 + +== Executive Summary + +This repository is the **RSR (Rhodium Standard Repository) Template** - a canonical template for the hyperpolymath ecosystem's 139 repositories. It establishes standardized structure, configuration, and tooling for projects adhering to the RSR philosophy. + +*Overall Assessment*: The repository demonstrates strong architectural intent but is currently a **scaffolded template** rather than a functional ReScript environment. Several gaps exist between the stated purpose and implementation. + +== Strengths + +=== 1. Comprehensive Policy Enforcement + +The repository includes robust CI/CD workflows for enforcing coding standards: + +* **ts-blocker.yml** (`.github/workflows/ts-blocker.yml:1`) - Blocks TypeScript/JavaScript additions, enforcing ReScript adoption +* **npm-bun-blocker.yml** (`.github/workflows/npm-bun-blocker.yml:1`) - Blocks npm/bun artifacts, requiring Deno +* **rsr-antipattern.yml** (`.github/workflows/rsr-antipattern.yml:1`) - Comprehensive anti-pattern checks (no Go, no Python except SaltStack) +* **guix-nix-policy.yml** (`.github/workflows/guix-nix-policy.yml:1`) - Enforces Guix-primary package management + +=== 2. Well-Structured Machine-Readable Metadata + +The `.machine_readable/` directory contains structured Scheme files: + +* **STATE.scm** - Project state tracking +* **META.scm** - Architecture decisions and practices +* **ECOSYSTEM.scm** - Ecosystem positioning +* **AGENTIC.scm** - AI agent interaction patterns +* **NEUROSYM.scm** - Neurosymbolic integration config +* **PLAYBOOK.scm** - Operational runbook + +=== 3. Security-First Approach + +* Comprehensive SECURITY.md template +* Secret scanning workflows +* TruffleHog and Trivy integration in CI +* Scorecard and CodeQL workflows + +=== 4. Dual Licensing Strategy + +MIT OR AGPL-3.0-or-later with Palimpsest philosophical overlay provides flexibility while encouraging consent-based practices. + +== Issues and Gaps + +=== Critical: Missing Core Files + +[cols="1,2,1"] +|=== +|Missing File |Expected Per README.adoc |Impact + +|`.guix-channel` +|Listed as included +|Guix channel functionality broken + +|`guix.scm` +|Listed as included +|Cannot enter Guix dev environment + +|`.well-known/` +|Listed as required for RSR compliance +|RSR validation will fail + +|`RSR_COMPLIANCE.adoc` +|Listed as required +|Cannot track compliance status + +|`STATE.scm` (root) +|Listed as included +|Only exists in `.machine_readable/` +|=== + +=== High: Justfile Recipe Placeholders + +The justfile (`.justfile:1`) contains many TODO placeholders: + +[source,bash] +---- +# Line 56-59: Build placeholder +# TODO: Add build command for your language +# Rust: cargo build {{args}} +# ReScript: npm run build # <-- Should use Deno, not npm! +---- + +*Problems*: +1. References `npm run build` despite npm being banned +2. No ReScript-specific toolchain setup +3. No Deno configuration provided + +=== Medium: SECURITY.md Template Not Customized + +File still contains `{{PLACEHOLDER}}` values: + +* `{{PROJECT_NAME}}` +* `{{OWNER}}` +* `{{REPO}}` +* `{{SECURITY_EMAIL}}` +* `{{PGP_FINGERPRINT}}` + +=== Medium: CONTRIBUTING.md Template Not Customized + +Contains uncustomized placeholders: +* `{{FORGE}}` +* `{{OWNER}}` +* `{{REPO}}` +* `{{MAIN_BRANCH}}` + +=== Low: Empty STATE.adoc + +Root `STATE.adoc` file is empty (0 bytes) while `.machine_readable/STATE.scm` contains content. + +=== Low: Inconsistent Documentation + +README.adoc and CLAUDE.md have some inconsistencies: +* README.adoc mentions "AGPL + Palimpsest dual license" +* LICENSE.txt states "MIT OR AGPL-3.0-or-later" + +== Recommendations + +=== Immediate Actions + +1. **Create missing RSR-required files**: ++ +[source,bash] +---- +# Create .well-known directory and files +mkdir -p .well-known +touch .well-known/security.txt .well-known/ai.txt .well-known/humans.txt + +# Create RSR_COMPLIANCE.adoc +touch RSR_COMPLIANCE.adoc + +# Create guix.scm for dev environment +touch guix.scm +---- + +2. **Update justfile for ReScript+Deno**: ++ +[source,just] +---- +# Build the project (ReScript via Deno) +build *args: + @echo "Building {{project}}..." + deno task build {{args}} + +# Run tests +test *args: + @echo "Running tests..." + deno task test {{args}} +---- + +3. **Create deno.json configuration**: ++ +[source,json] +---- +{ + "tasks": { + "build": "rescript build", + "clean": "rescript clean", + "test": "deno test" + }, + "imports": { + "@rescript/core": "npm:@rescript/core@^1.0.0" + } +} +---- + +=== Template Customization Required + +Replace placeholders in: + +* [ ] SECURITY.md +* [ ] CONTRIBUTING.md +* [ ] docs/CITATIONS.adoc (if using) + +=== Recommended File Structure + +[source] +---- +rescript-env/ +├── .claude/ +│ └── CLAUDE.md ✅ Present +├── .github/ +│ └── workflows/ ✅ Present (comprehensive) +├── .machine_readable/ ✅ Present +├── .well-known/ ❌ Missing +│ ├── ai.txt +│ ├── humans.txt +│ └── security.txt +├── docs/ ✅ Present +├── src/ ❌ Missing (no ReScript source) +│ └── Main.res Example ReScript entry +├── deno.json ❌ Missing +├── rescript.json ❌ Missing +├── guix.scm ❌ Missing +├── RSR_COMPLIANCE.adoc ❌ Missing +├── justfile ⚠️ Needs ReScript adaptation +└── README.adoc ⚠️ Minor inconsistencies +---- + +== Validation Results + +Running `just validate-rsr` would fail due to: + +1. Missing `.well-known/` directory +2. Missing `.well-known/security.txt` +3. Missing `.well-known/ai.txt` +4. Missing `.well-known/humans.txt` +5. Missing `RSR_COMPLIANCE.adoc` +6. Missing `guix.scm` OR `flake.nix` + +== Conclusion + +This repository provides an excellent **policy framework** for RSR-compliant projects but lacks the actual implementation files to be a functional ReScript development environment. It should either: + +1. **As Template**: Add clear instructions for users to add missing files post-clone +2. **As Working Environment**: Include minimal working ReScript+Deno setup + +The CI/CD workflows and machine-readable metadata are well-designed and demonstrate thoughtful architecture for the RSR ecosystem. + +--- + +_Review generated: 2026-01-04_ +_Reviewer: Claude Code (claude-opus-4-5-20251101)_ diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..30c1777 --- /dev/null +++ b/deno.json @@ -0,0 +1,13 @@ +{ + "name": "@hyperpolymath/rescript-env", + "version": "0.1.0", + "exports": "./src/Env.res.js", + "tasks": { + "build": "rescript build", + "clean": "rescript clean", + "dev": "rescript build -w" + }, + "compilerOptions": { + "lib": ["deno.ns", "deno.unstable"] + } +} diff --git a/rescript.json b/rescript.json new file mode 100644 index 0000000..8fad5ff --- /dev/null +++ b/rescript.json @@ -0,0 +1,22 @@ +{ + "name": "@hyperpolymath/rescript-env", + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "esmodule", + "in-source": true + } + ], + "suffix": ".res.js", + "bs-dependencies": [ + "@rescript/core" + ], + "bsc-flags": [ + "-open RescriptCore" + ] +} diff --git a/src/Env.res b/src/Env.res new file mode 100644 index 0000000..0c97700 --- /dev/null +++ b/src/Env.res @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Hyperpolymath + +@@uncurried + +/** + * Type-safe environment variable access for ReScript. + * Works with Deno, Bun, and Node.js runtimes. + */ + +/** Get an environment variable, returns None if not set */ +@val @scope(("Deno", "env")) +external denoGet: string => option = "get" + +/** Set an environment variable */ +@val @scope(("Deno", "env")) +external denoSet: (string, string) => unit = "set" + +/** Delete an environment variable */ +@val @scope(("Deno", "env")) +external denoDelete: string => unit = "delete" + +/** Get all environment variables as an object */ +@val @scope(("Deno", "env")) +external denoToObject: unit => Dict.t = "toObject" + +/** Check if running in Deno */ +@val @scope("globalThis") +external denoExists: option<{..}> = "Deno" + +/** Node.js process.env */ +@val @scope("process") +external nodeEnv: Dict.t = "env" + +/** Check if running in Node */ +@val @scope("globalThis") +external processExists: option<{..}> = "process" + +/** + * Get an environment variable. + * Returns None if the variable is not set. + */ +let get = (name: string): option => { + switch denoExists { + | Some(_) => denoGet(name) + | None => + switch processExists { + | Some(_) => nodeEnv->Dict.get(name) + | None => None + } + } +} + +/** + * Get an environment variable with a default value. + * Returns the default if the variable is not set. + */ +let getOr = (name: string, default: string): string => { + get(name)->Option.getOr(default) +} + +/** + * Get an environment variable, throwing if not set. + * Use this for required configuration. + */ +exception MissingEnvVar(string) + +let getExn = (name: string): string => { + switch get(name) { + | Some(value) => value + | None => raise(MissingEnvVar(name)) + } +} + +/** + * Set an environment variable. + */ +let set = (name: string, value: string): unit => { + switch denoExists { + | Some(_) => denoSet(name, value) + | None => + switch processExists { + | Some(_) => nodeEnv->Dict.set(name, value) + | None => () + } + } +} + +/** + * Delete an environment variable. + */ +let delete = (name: string): unit => { + switch denoExists { + | Some(_) => denoDelete(name) + | None => + switch processExists { + | Some(_) => nodeEnv->Dict.delete(name) + | None => () + } + } +} + +/** + * Get all environment variables as a dictionary. + */ +let all = (): Dict.t => { + switch denoExists { + | Some(_) => denoToObject() + | None => + switch processExists { + | Some(_) => nodeEnv + | None => Dict.make() + } + } +} + +/** + * Check if an environment variable is set. + */ +let has = (name: string): bool => { + get(name)->Option.isSome +} + +/** + * Get an environment variable as an integer. + * Returns None if not set or not a valid integer. + */ +let getInt = (name: string): option => { + get(name)->Option.flatMap(Int.fromString(_, ~radix=10)) +} + +/** + * Get an environment variable as a float. + * Returns None if not set or not a valid float. + */ +let getFloat = (name: string): option => { + get(name)->Option.flatMap(Float.fromString) +} + +/** + * Get an environment variable as a boolean. + * Recognises: "true", "1", "yes", "on" as true + * Recognises: "false", "0", "no", "off" as false + * Returns None if not set or not a recognised boolean value. + */ +let getBool = (name: string): option => { + get(name)->Option.flatMap(value => { + let lower = value->String.toLowerCase + switch lower { + | "true" | "1" | "yes" | "on" => Some(true) + | "false" | "0" | "no" | "off" => Some(false) + | _ => None + } + }) +} + +/** + * Check if running in development mode. + * Checks NODE_ENV, DENO_ENV, or ENV for "development" or "dev". + */ +let isDevelopment = (): bool => { + let env = getOr("NODE_ENV", getOr("DENO_ENV", getOr("ENV", "production"))) + env == "development" || env == "dev" +} + +/** + * Check if running in production mode. + * Checks NODE_ENV, DENO_ENV, or ENV for "production" or "prod". + */ +let isProduction = (): bool => { + let env = getOr("NODE_ENV", getOr("DENO_ENV", getOr("ENV", "production"))) + env == "production" || env == "prod" +} + +/** + * Check if running in test mode. + * Checks NODE_ENV, DENO_ENV, or ENV for "test". + */ +let isTest = (): bool => { + let env = getOr("NODE_ENV", getOr("DENO_ENV", getOr("ENV", ""))) + env == "test" +} diff --git a/src/Env.resi b/src/Env.resi new file mode 100644 index 0000000..abecc5e --- /dev/null +++ b/src/Env.resi @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Hyperpolymath + +/** + * Type-safe environment variable access for ReScript. + */ + +exception MissingEnvVar(string) + +/** Get an environment variable, returns None if not set */ +let get: string => option + +/** Get an environment variable with a default value */ +let getOr: (string, string) => string + +/** Get an environment variable, throws MissingEnvVar if not set */ +let getExn: string => string + +/** Set an environment variable */ +let set: (string, string) => unit + +/** Delete an environment variable */ +let delete: string => unit + +/** Get all environment variables as a dictionary */ +let all: unit => Dict.t + +/** Check if an environment variable is set */ +let has: string => bool + +/** Get an environment variable as an integer */ +let getInt: string => option + +/** Get an environment variable as a float */ +let getFloat: string => option + +/** Get an environment variable as a boolean (true/false/1/0/yes/no/on/off) */ +let getBool: string => option + +/** Check if running in development mode */ +let isDevelopment: unit => bool + +/** Check if running in production mode */ +let isProduction: unit => bool + +/** Check if running in test mode */ +let isTest: unit => bool