diff --git a/.claude/hooks/decision-nudge.js b/.agents/hooks/decision-nudge.js similarity index 100% rename from .claude/hooks/decision-nudge.js rename to .agents/hooks/decision-nudge.js diff --git a/.agents/settings.json b/.agents/settings.json new file mode 100644 index 000000000..b7cf33d92 --- /dev/null +++ b/.agents/settings.json @@ -0,0 +1,70 @@ +{ + "permissions": { + "allow": [ + "Bash(cat:*)", + "Bash(deno bench:*)", + "Bash(deno check:*)", + "Bash(deno fmt:*)", + "Bash(deno lint:*)", + "Bash(deno run:*)", + "Bash(deno task:*)", + "Bash(deno test:*)", + "Bash(docker:*)", + "Bash(docker compose:*)", + "Bash(find:*)", + "Bash(gh:*)", + "Bash(head:*)", + "Bash(git diff:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(git status:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(make:*)", + "Bash(sort:*)", + "Bash(tail:*)", + "Bash(test:*)", + "Bash(tree:*)", + "Bash(uniq:*)", + "Bash(wc:*)", + "mcp__plugin_context7_context7__get-library-docs", + "mcp__plugin_context7_context7__query-docs", + "mcp__plugin_context7_context7__resolve-library-id", + "Skill(architecture-guidelines)", + "Skill(coding-practices)", + "Skill(design-principles)", + "Skill(enrichment)", + "Skill(eser-rules-manager)", + "Skill(go-practices)", + "Skill(javascript-practices)", + "Skill(release-and-ci)", + "Skill(security-practices)", + "Skill(tooling-standards)", + "Skill(workflow-practices)", + "WebFetch(domain:api.github.com)", + "WebFetch(domain:github.com)", + "WebFetch(domain:raw.githubusercontent.com)", + "WebSearch" + ], + "deny": [ + "Bash(git add:*)", + "Bash(git branch:*)", + "Bash(git checkout:*)", + "Bash(git commit:*)", + "Bash(git fetch:*)", + "Bash(git merge:*)", + "Bash(git pull:*)", + "Bash(git push:*)", + "Bash(git rebase:*)", + "Bash(git reset:*)", + "Bash(git revert:*)", + "Bash(git stash:*)", + "Bash(git tag:*)" + ] + }, + "enabledPlugins": { + "context7@claude-plugins-official": true, + "frontend-design@claude-plugins-official": true + }, + "alwaysThinkingEnabled": true +} diff --git a/.agents/skills/architecture-guidelines/SKILL.md b/.agents/skills/architecture-guidelines/SKILL.md new file mode 100644 index 000000000..df913d2c9 --- /dev/null +++ b/.agents/skills/architecture-guidelines/SKILL.md @@ -0,0 +1,35 @@ +--- +name: architecture-guidelines +description: "System design in eserstack: double-layered hexagonal architecture, explicit adapter composition, public API and CLI surface, request metadata, testing strategy, ADRs. Use when designing a package or service, adding options or flags, planning tests or writing an ADR. Not for file naming (use tooling-standards)." +--- + +# Architecture Guidelines + +How packages and services are structured, composed, tested and documented as +decisions. + +## Always + +- Domain logic and its port types live together in `domain/`; adapters live in + `adapters/`. No `ports/` directory. +- Callers import adapters explicitly and pass them in. No string-selected + adapters, no convenience factories, no dynamic imports inside a package. +- Library code reaches the runtime through `@eserstack/standards/cross-runtime`, + never `Deno.*`. +- Every option has a safe default; add an option only for a named case; no + positional booleans. +- Request ids and trace state travel in the request context, never as extra + parameters. +- Unit tests with fakes for logic, integration tests for adapters; failure paths + tested; coverage never drops. +- Validator and codebase-tool design lives in + `pkg/@eserstack/codebase/AGENTS.md` +- A lasting architectural decision gets an ADR in `docs/adr/`. + +## References + +| File | Read when | +| ----------------------------------------------------------------- | --------------------------------------------------------------------------- | +| [hexagonal-architecture.md](references/hexagonal-architecture.md) | Structuring a package, adding an adapter, adding options, flags or env vars | +| [testing.md](references/testing.md) | Deciding what and how to test, choosing the runner, coverage | +| [adrs.md](references/adrs.md) | Recording an architectural decision | diff --git a/.agents/skills/architecture-guidelines/evals/triggers.json b/.agents/skills/architecture-guidelines/evals/triggers.json new file mode 100644 index 000000000..a28c83c79 --- /dev/null +++ b/.agents/skills/architecture-guidelines/evals/triggers.json @@ -0,0 +1,62 @@ +[ + { + "query": "design a new package for background jobs, where do ports and adapters go", + "should_trigger": true + }, + { + "query": "should this CLI flag be public or hidden", + "should_trigger": true + }, + { + "query": "write an ADR for switching the bundler", + "should_trigger": true + }, + { + "query": "what should we unit test vs integration test for the http package", + "should_trigger": true + }, + { + "query": "how do request ids travel between the noskills server and worker", + "should_trigger": true + }, + { + "query": "yeni bir servis tasarlıyorum, katmanları nasıl ayırayım", + "should_trigger": true + }, + { + "query": "we're adding an option to the public API of @eserstack/config, is that ok", + "should_trigger": true + }, + { + "query": "the Go tests fail with a data race", + "should_trigger": false + }, + { + "query": "use namespace import here", + "should_trigger": false + }, + { + "query": "bump all packages to the next minor", + "should_trigger": false + }, + { + "query": "write a README for laroux", + "should_trigger": false + }, + { + "query": "set the GitHub workflow timeout", + "should_trigger": false + }, + { + "query": "should this function take a pointer or a value", + "should_trigger": false + }, + { + "query": "secret key is in the logs, fix it", + "should_trigger": false + }, + { + "query": "commit and push", + "should_trigger": false + } +] diff --git a/.agents/skills/architecture-guidelines/references/adrs.md b/.agents/skills/architecture-guidelines/references/adrs.md new file mode 100644 index 000000000..8e3855b15 --- /dev/null +++ b/.agents/skills/architecture-guidelines/references/adrs.md @@ -0,0 +1,29 @@ +# Architectural Decision Records + +When to write an ADR and what it contains. + +--- + +## Architectural Decision Records + +Scope: Decisions that change a package's architecture, a public contract, a +dependency with lasting cost, or a product target + +Rule: Record the decision in `docs/adr/NNNN-kebab-title.md`, numbered after the +highest existing file (`0001-http3-and-webtransport.md` through +`0004-bundler-external-import-specifiers.md` exist). An ADR is written before or +with the change, not after. It states: + +- `## Status`: Proposed, Accepted, or Superseded by a named ADR. +- `## Context and Problem Statement`: the forces and the question being decided. +- `## Decision Drivers`: what the decision optimizes for. +- `## Considered Options`, with the trade-offs of each. +- `## Decision Outcome`: the choice and why it beat the others. +- `## Consequences`: what becomes easier and what becomes harder. +- `## Links` and related files, when there are any. + +An accepted ADR is not rewritten to match a later decision. A new ADR supersedes +it, and the old one's status names the new one. + +**Why:** the reasoning behind a structure is what a later change needs, and it +is the part the code cannot show. diff --git a/.agents/skills/architecture-guidelines/references/hexagonal-architecture.md b/.agents/skills/architecture-guidelines/references/hexagonal-architecture.md new file mode 100644 index 000000000..3373f4c1e --- /dev/null +++ b/.agents/skills/architecture-guidelines/references/hexagonal-architecture.md @@ -0,0 +1,175 @@ +# Hexagonal Architecture and Public Surface + +How packages separate domain logic from adapters, how callers compose them, and +how public APIs and request metadata are shaped. + +## Contents + +- Hexagonal Architecture (Double-Layered) +- Runtime Access Through cross-runtime +- Public API and CLI Surface +- Request-Scoped Metadata Across Boundaries + +--- + +## Hexagonal Architecture (Double-Layered) + +Scope: Packages with external dependencies (TypeScript; the Go form is in +go-practices) + +Rule: The domain layer holds business logic and its port types together; there +is no separate `ports/` directory. Adapters implement the domain types and live +in `adapters/`. The caller composes by importing the adapters it wants and +passing them in as parameters. No string-based adapter selection, no convenience +factories that pick adapters for the caller, no dynamic imports inside the +package. + +``` +package/ +├── domain/ # Business logic + port types +│ ├── mod.ts +│ ├── bundler.ts # Bundler type + BundleData +│ ├── framework-plugin.ts # FrameworkPlugin type + noopPlugin +│ └── build-cache.ts # BuildCache type + implementation +├── adapters/ +│ ├── react/ # implements FrameworkPlugin +│ ├── tailwindcss/ # implements CssPlugin +│ └── prebuilt-bundler/ # implements Bundler +└── mod.ts +``` + +Correct: + +```typescript +import * as bundler from "@eserstack/laroux-bundler"; +import * as react from "@eserstack/laroux-bundler/adapters/react"; +import * as tailwind from "@eserstack/laroux-bundler/adapters/tailwindcss"; +import * as prebuilt from "@eserstack/laroux-bundler/adapters/prebuilt-bundler"; + +await bundler.bundle(config, { + framework: react.reactPlugin, + css: tailwind.tailwindPlugin, + bundler: new prebuilt.PrebuiltBundler(bundlerConfig), +}); +``` + +Incorrect: + +```typescript +await bundle({ framework: "react", css: "tailwindcss" }); // dynamic imports inside + +const plugins = await createReactTailwindPlugins(); // hides which adapters load + +import type { Plugin } from "./ports/plugin.ts"; // ports split from the domain +``` + +A domain type ships with a noop implementation for callers that do not need the +capability: + +```typescript +// domain/framework-plugin.ts +export type FrameworkPlugin = { + name: string; + analyzeClientComponents?: (srcDir: string) => Promise; +}; + +export const noopPlugin: FrameworkPlugin = { + name: "noop", + analyzeClientComponents: () => Promise.resolve([]), +}; +``` + +**Why:** explicit imports keep the dependency graph visible and tree-shakable, +and let a caller swap one adapter without touching the others. + +--- + +## Runtime Access Through cross-runtime + +Scope: All `@eserstack/*` library code + +Rule: Filesystem, process, exec and environment access goes through +`@eserstack/standards/cross-runtime`, never `Deno.*` directly. The runtime is an +adapter like any other, so the same code runs on Deno, Node and Bun and can be +tested with a fake. CLI-only code may call `Deno.*` when cross-runtime does not +expose the API it needs (for example `Deno.makeTempDir()`). + +Correct: + +```typescript +import { runtime } from "@eserstack/standards/cross-runtime"; + +const content = await runtime.fs.readTextFile(path); +``` + +Incorrect: + +```typescript +const content = await Deno.readTextFile(path); // ties the library to Deno +``` + +--- + +## Public API and CLI Surface + +Scope: Exported functions and types, CLI commands and flags, config keys, +environment variables + +Rule: + +- The common case works with no configuration. Every option has a default that + is safe and correct for production. +- A fixed policy value a caller may need to change (a timeout, a size limit, a + path, a retry count) is a parameter, flag or environment variable with that + default, not a hardcoded constant. +- Do not add options for hypothetical needs. Each new option names the case that + requires it; an edge case gets documentation, not a required setting. +- No positional boolean parameters. Use an options object or a string union. +- Defaults come from parameter defaults and noop adapters, not from convenience + factories. + +Correct: + +```typescript +export function connect(url: string, options: { timeoutMs?: number } = {}) { + const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS; +} + +render(tree, { mode: "stream" }); +``` + +Incorrect: + +```typescript +export function connect(url: string) { + const timeoutMs = 5000; // caller cannot change it +} + +render(tree, true, false); // what are true and false? +``` + +--- + +## Request-Scoped Metadata Across Boundaries + +Scope: HTTP and RPC servers and clients, the noskills daemon, services that call +each other + +Rule: Metadata that identifies a request (trace and span ids, a request or +correlation id, the authenticated principal) travels with the request's context +(`context.Context` in Go, the request or an explicit context object in +TypeScript), not as extra function parameters. Business inputs and dependencies +never travel that way: inputs are parameters, dependencies come from the +composition root. + +- At an entry point, middleware reads incoming metadata or creates it, and + stores it in the context. +- On every outbound call, the client writes it back out, so logs and traces on + both sides join up. +- Trace state uses OpenTelemetry propagation (the `traceparent` header), not + hand-written headers. Other ids use typed accessors owned by one package. +- Logs include these ids (coding-practices: Structured Logging). + +**Why:** a request that loses its ids at one hop cannot be followed across the +system, and a dependency hidden in a context is invisible in every signature +that needs it. diff --git a/.agents/skills/architecture-guidelines/references/testing.md b/.agents/skills/architecture-guidelines/references/testing.md new file mode 100644 index 000000000..c938670a8 --- /dev/null +++ b/.agents/skills/architecture-guidelines/references/testing.md @@ -0,0 +1,115 @@ +# Testing Strategy + +What gets tested, at which level, with which runner, and how coverage is +tracked. Language details: javascript-practices and go-practices. + +## Contents + +- What to Test +- Runner, Files and Coverage +- Table-Driven Tests + +--- + +## What to Test + +Scope: All code changes + +Rule: + +- Business logic has unit tests that use fake adapters. Adapters have + integration tests against the real dependency. Critical CLI and daemon flows + get an end-to-end test. +- Tests cover failure paths (invalid input, missing file, timeout, dependency + error) as well as the happy path. +- Assert observable behavior: return values, emitted output, state changes. A + test that only checks a function was called, or still passes when the behavior + breaks, is not a test. +- New code ships with its tests. A change never lowers the coverage of the + packages it touches. +- A skipped test (`ignore: true`, `.skip`, `t.Skip`) carries a comment with the + reason and a backlog task id. + +Correct: + +```typescript +import * as assert from "@std/assert"; + +const fakeRepo = { + findUser: (id: string) => Promise.resolve({ id, name: "Test" }), +}; + +Deno.test("getUser returns the stored user", async () => { + const service = createUserService(fakeRepo); + const user = await service.getUser("test-id"); + assert.assertEquals(user.name, "Test"); +}); +``` + +Incorrect: + +```typescript +// calculateDiscount ships with no test at all +export function calculateDiscount(price: number, percent: number): number { + return price * (1 - percent / 100); +} +``` + +--- + +## Runner, Files and Coverage + +Scope: TypeScript tests + +Rule: + +- Assertions come from `@std/assert`, imported as a namespace. Never add a + second runner or assertion library (chai, `node:assert`, vitest). +- Both `Deno.test` and `@std/testing/bdd` (`describe`/`it`) are in use. In an + existing file follow its style; do not mix the two in one file. +- Test files are co-located with the source as `*.test.ts`. Some older files use + `_test.ts` (for example in `pkg/@eserstack/workflows`); leave them as they are + unless asked, and name new files `*.test.ts`. +- Run tests with `deno task cli test`, which runs in parallel with + `--trace-leaks` and writes coverage to `etc/coverage/`. `deno task cli ok` + runs them as part of the precommit workflow. The Build workflow converts + coverage to LCOV and uploads it to Codecov. + +Because tests run in parallel in one process, a test that sets a process-wide +value (an environment variable, the working directory) races the others. Pass +the value as a parameter instead, or restore it in a `finally`. + +--- + +## Table-Driven Tests + +Scope: Unit tests with several input cases + +Rule: Put the cases in a table and loop over it. Each case has a name that +describes the behavior, not the implementation. The Go form (subtests with +`t.Run`) is in go-practices. + +Correct: + +```typescript +import * as assert from "@std/assert"; + +const cases = [ + { name: "handles zero", input: 0, expected: "zero" }, + { name: "handles negative numbers", input: -1, expected: "negative" }, + { name: "handles positive numbers", input: 42, expected: "positive" }, +]; + +for (const { name, input, expected } of cases) { + Deno.test(name, () => { + assert.assertEquals(classifyNumber(input), expected); + }); +} +``` + +Incorrect: + +```typescript +Deno.test("test1", () => assert.assertEquals(classifyNumber(0), "zero")); +Deno.test("test2", () => assert.assertEquals(classifyNumber(-1), "negative")); +``` diff --git a/.agents/skills/coding-practices/SKILL.md b/.agents/skills/coding-practices/SKILL.md new file mode 100644 index 000000000..10dba9719 --- /dev/null +++ b/.agents/skills/coding-practices/SKILL.md @@ -0,0 +1,39 @@ +--- +name: coding-practices +description: "Language-neutral code quality rules for eserstack, TypeScript and Go. Covers errors, logging, explicit checks, naming and comments, timeouts and resource bounds, streaming, background work, input and config sources, and performance claims. Use when writing or reviewing code, handling errors, adding logs or calling external services. Not for syntax (use javascript-practices or go-practices)." +--- + +# Coding Practices + +Rules that hold in every language in this repository. Language syntax lives in +javascript-practices and go-practices; design patterns in design-principles. + +## Always + +- Handle every failure that can happen; catch narrowly and let the rest + propagate. No handling for states the types rule out. +- An error is handled or returned, never both: return with context, log once + where it is handled. +- Messages name the operation, the value that failed and the fix. "failed", + "invalid" and "Something went wrong" are defects. +- Log through `@eserstack/logging` or `logfx` with ids attached, never + `console.*` (lint: `no-console`). +- Implicit truthy/falsy checks only on booleans; `??` for defaults, never `||`. +- Compare entities by id, never by slug, name or email. +- Every external call has a timeout, every pool, queue and cache a limit, and + every started promise or goroutine an owner. +- Release resources in the scope that acquired them (`defer`, `finally`). +- No absolute paths or machine-specific values; paths come from + `import.meta.dirname` or a caller-supplied root. +- Configuration precedence: environment, then config file, then default. +- Performance changes come with before and after measurements. + +## References + +| File | Read when | +| ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| [errors.md](references/errors.md) | Raising, wrapping, matching or reporting errors; `Result` types | +| [logging.md](references/logging.md) | Adding or reviewing log calls, levels and context fields | +| [readability.md](references/readability.md) | Naming, comments, abstraction, checks, switches, early returns, line breaks | +| [resources-and-io.md](references/resources-and-io.md) | Network calls, subprocesses, retries, limits, cleanup, streaming, async work, benchmarks | +| [inputs-and-config.md](references/inputs-and-config.md) | Parsing input, resolving paths, loading configuration | diff --git a/.agents/skills/coding-practices/evals/triggers.json b/.agents/skills/coding-practices/evals/triggers.json new file mode 100644 index 000000000..0eddc4d56 --- /dev/null +++ b/.agents/skills/coding-practices/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "this catch block swallows the error, fix it", + "should_trigger": true + }, + { + "query": "improve the error message when the config file is missing", + "should_trigger": true + }, + { + "query": "add logging to the sync job", + "should_trigger": true + }, + { + "query": "the external API call has no timeout", + "should_trigger": true + }, + { + "query": "config should come from env, then file, then default", + "should_trigger": true + }, + { + "query": "this queue grows forever under load", + "should_trigger": true + }, + { + "query": "hata mesajları kullanıcıya ne yapacağını söylemiyor", + "should_trigger": true + }, + { + "query": "clean up these magic numbers and the nested ifs", + "should_trigger": true + }, + { + "query": "how do I use errors.AsType in Go 1.26", + "should_trigger": false + }, + { + "query": "which import style for @std/path", + "should_trigger": false + }, + { + "query": "tag the release", + "should_trigger": false + }, + { + "query": "add a Kubernetes deployment", + "should_trigger": false + }, + { + "query": "write the README usage example", + "should_trigger": false + }, + { + "query": "validate the webhook URL against SSRF", + "should_trigger": false + }, + { + "query": "what's our branching strategy", + "should_trigger": false + }, + { + "query": "create a new skill for UI rules", + "should_trigger": false + } +] diff --git a/.agents/skills/coding-practices/references/errors.md b/.agents/skills/coding-practices/references/errors.md new file mode 100644 index 000000000..4397f6ac3 --- /dev/null +++ b/.agents/skills/coding-practices/references/errors.md @@ -0,0 +1,259 @@ +# Errors + +How code in this repository raises, wraps, reports and matches errors, in +TypeScript and Go. Go idioms (`%w`, `errors.Is`, `errors.AsType`) are in +go-practices: Error Handling; user-facing sanitization is in security-practices: +Error Sanitization. + +## Contents + +- Error Handling +- Error Wrapping +- Actionable Error Messages +- Ignored Errors +- Handle Each Error Once +- Result Objects + +--- + +## Error Handling + +Scope: All code, in TypeScript and Go + +Rule: Handle every failure that can actually happen: I/O, network, parsing, user +input, a dependency returning an error. Do not add handling for states the types +or an invariant already rule out; a check for the impossible hides the real +contract and never runs in a test. Catch narrowly: handle the error types you +expect and let the rest propagate. A normal outcome (a missing optional file, a +404 for a lookup) is a return value, not an exception. + +Correct: + +```typescript +export async function fetchManifest( + url: string, + signal: AbortSignal, +): Promise { + const response = await fetch(url, { signal }); + if (response.status === 404) { + return null; // no manifest is a normal answer for this registry + } + if (!response.ok) { + throw new ManifestFetchError(url, response.status); + } + return await response.json() as Manifest; +} +``` + +Incorrect: + +```typescript +try { + return await fetchManifest(url, signal); +} catch { + return null; // an outage, a bad URL and a bug all look like "no manifest" +} +``` + +**Why:** a catch-all turns every failure into the same silent answer, and the +caller cannot tell a missing value from a broken system. + +--- + +## Error Wrapping + +Scope: Errors raised or propagated in TypeScript + +Rule: Throw `Error` subclasses named for the domain failure (`ValidationError`, +`ManifestFetchError`), with the identifying data as properties. When +propagating, wrap with `{ cause: error }` and a message naming the step that +failed, so the chain reads from the outermost operation to the root cause. Never +throw strings, never rethrow a new error that copies only `.message`, and never +build messages by string concatenation. + +Correct: + +```typescript +export class ManifestFetchError extends Error { + constructor( + readonly url: string, + readonly status: number, + options?: ErrorOptions, + ) { + super(`fetch manifest ${url}: HTTP ${status}`, options); + this.name = "ManifestFetchError"; + } +} + +try { + await installPackage(spec); +} catch (error) { + throw new InstallError(`install ${spec.name}@${spec.version}`, { + cause: error, + }); +} +``` + +Incorrect: + +```typescript +throw "install failed"; // not an Error, no stack (lint: no-throw-literal) +throw new Error(error.message); // loses the stack and the cause chain +throw new Error("Something went wrong"); // nothing to act on +``` + +In Go, the same rule is `fmt.Errorf("step: %w", err)` with sentinel errors and +custom types; see go-practices: Error Handling. + +**Why:** the type lets callers match without parsing text, and the cause chain +keeps the root error that a copied message throws away. + +--- + +## Actionable Error Messages + +Scope: Every error a user or developer can see: thrown errors, CLI output, HTTP +and daemon error responses + +Rule: An error message states what went wrong, why, and what to do next. Include +the value, path or identifier that failed. A validation error says what a valid +value looks like. "Something went wrong", "failed" and a bare "invalid" are +defects. Messages name the operation ("install foo@1.2.0: ...") instead of +starting with "Failed to". + +Correct: + +```typescript +throw new ValidationError( + `email "${email}" has no "@"; expected an address like name@example.com`, + "email", +); +``` + +Incorrect: + +```typescript +throw new Error("Invalid email"); +``` + +**Why:** the person reading the message usually has only the message; one that +names the value and the fix saves a trip into the code. + +--- + +## Ignored Errors + +Scope: All languages + +Rule: Never ignore returned errors or rejected promises. An empty catch is +allowed only on a cleanup path where a second failure must not hide the first, +and it carries a comment saying why. + +Correct: + +```typescript +try { + await runtime.fs.remove(tempDir, { recursive: true }); +} catch { + // Best-effort cleanup after the real error was already reported. +} +``` + +Incorrect: + +```typescript +try { + await saveSession(session); +} catch { + // ignored +} +``` + +```go +result, _ := operation() // error discarded +``` + +**Why:** an ignored error surfaces later as corrupted state, far from the call +that could have explained it. + +--- + +## Handle Each Error Once + +Scope: All error handling, in TypeScript and Go + +Rule: An error is either handled or returned, never both. Code that returns an +error adds context and passes it up without logging it. The code that handles it +(a request handler, a CLI command, a worker loop, a cleanup path that decides to +continue) logs it once with full context and turns it into a response, a retry +or a fallback. + +Correct: + +```go +func (s *SessionService) Load(ctx context.Context, id string) (*Session, error) { + session, err := s.store.Get(ctx, id) + if err != nil { + return nil, fmt.Errorf("load session %q: %w", id, err) // returned, not logged + } + return session, nil +} + +func (h *Handler) GetSession(w http.ResponseWriter, r *http.Request) { + session, err := h.sessions.Load(r.Context(), r.PathValue("id")) + if err != nil { + h.logger.ErrorContext(r.Context(), "get session", "error", err) // handled here + writeError(w, err) + return + } + writeJSON(w, session) +} +``` + +Incorrect: + +```go +if err != nil { + log.Error("store get failed", "error", err) + return nil, err // logged here and again by every caller +} +``` + +**Why:** logging and then returning writes the same failure several times, each +entry with less context than the last. + +--- + +## Result Objects + +Scope: TypeScript functions whose failure is an expected outcome the caller must +branch on (parsing, validation, lookups) + +Rule: Use the `Result` type from `@eserstack/primitives/results` (`ok`, `fail`, +`isOk`, `isFail`, `map`, `flatMap`) instead of a hand-rolled union. Throw for +programming errors and unexpected failures; return a `Result` when failing is +part of the function's normal contract. + +Correct: + +```typescript +import * as results from "@eserstack/primitives/results"; + +export function parsePort( + input: string, +): results.Result { + const port = Number(input); + if (!Number.isInteger(port) || port < 1 || port > 65_535) { + return results.fail( + new ValidationError( + `port "${input}" is not an integer in 1..65535`, + "port", + ), + ); + } + return results.ok(port); +} +``` + +**Why:** one shared type keeps the combinators and type guards consistent across +packages. diff --git a/.agents/skills/coding-practices/references/inputs-and-config.md b/.agents/skills/coding-practices/references/inputs-and-config.md new file mode 100644 index 000000000..094983e24 --- /dev/null +++ b/.agents/skills/coding-practices/references/inputs-and-config.md @@ -0,0 +1,151 @@ +# Inputs and Configuration + +Where values come from: validated input, configuration sources and paths, in +TypeScript and Go. Validation at trust boundaries (HTTP, CLI, files, LLM output) +is owned by security-practices: Input Validation. + +## Contents + +- Input Validation +- No Machine-Specific Assumptions +- Configuration Precedence + +--- + +## Input Validation + +Scope: Functions that receive data they did not produce + +Rule: Validate data where it enters the program, following security-practices: +Input Validation, and turn it into a typed value there. Code past that boundary +trusts the types and does not re-validate. Report a rejected value with an +actionable message (errors.md: Actionable Error Messages), naming the value and +what a valid one looks like. + +Correct: + +```typescript +export function parseSessionId(input: string): SessionId { + if (!SESSION_ID_PATTERN.test(input)) { + throw new ValidationError( + `session id "${input}" must be 1-64 letters, digits, "-" or "_"`, + "sessionId", + ); + } + return input as SessionId; +} +``` + +Incorrect: + +```typescript +export function loadSession(id: string) { + return runtime.fs.readTextFile(`${root}/${id}.json`); // "../" reaches any file +} +``` + +**Why:** one check at the boundary is easy to audit; checks scattered through +the code get skipped on some path. + +--- + +## No Machine-Specific Assumptions + +Scope: Source, scripts, tests and config committed to the repo + +Rule: No absolute paths, user names, home directories or local ports from one +machine. Read machine settings from environment variables with defaults, and +derive every path from one of two anchors: + +- Files that ship with the package (templates, defaults, fixtures) resolve + relative to the module itself: `import.meta.dirname` in TypeScript, `embed` or + the package directory in Go. +- Files that belong to the user's project resolve against a root the caller + passes in (a `projectRoot` or `root` parameter) that defaults to the working + directory. + +Correct: + +```typescript +import * as path from "@std/path"; +import { runtime } from "@eserstack/standards/cross-runtime"; + +const templateDir = path.join(import.meta.dirname!, "templates", name); + +export async function analyzeComponents( + srcDir: string, + options: { projectRoot?: string } = {}, +): Promise { + const root = options.projectRoot ?? runtime.process.cwd(); + // ... +} +``` + +```go +func LoadManifest(root string) (*Manifest, error) { + return readManifest(filepath.Join(root, ".eser", "manifest.yml")) +} +``` + +Incorrect: + +```typescript +const root = "/Users/me/projects/app"; // one machine only +const config = path.join(Deno.cwd(), "config.ts"); // hidden global, untestable +``` + +**Why:** taking the root as a parameter lets a test point the function at a +temporary directory without changing global state, and the code runs on any +machine and in CI. + +--- + +## Configuration Precedence + +Scope: Loading configuration in any package or binary + +Rule: Resolve each setting in this order, highest first: environment variable, +config file, built-in default. Start from the defaults, apply the file, then +apply the environment. A failure to read or parse a config file that exists is +an error, not a silent fall back to defaults. Settings that decide where +credentials are sent or stored come from the process environment only, never +from a file in the working directory (security-practices: Secrets Come From the +Environment). + +Correct: + +```go +func LoadConfig(root string) (Config, error) { + cfg := defaultConfig() + + data, err := os.ReadFile(filepath.Join(root, "config.json")) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return cfg, fmt.Errorf("read config: %w", err) + } + if err == nil { + if err := json.Unmarshal(data, &cfg); err != nil { + return cfg, fmt.Errorf("parse config.json: %w", err) + } + } + + if port := os.Getenv("API_PORT"); port != "" { + cfg.Port = port // environment wins + } + return cfg, nil +} +``` + +Incorrect: + +```go +file, _ := os.Open("config.json") +json.NewDecoder(file).Decode(&cfg) // error ignored, file never closed +``` + +In Go services, `pkg/ajan/configfx` loads files, env files and the system +environment in this order into structs tagged with `conf:"..."`; use it instead +of hand-written loading. + +**Why:** a fixed order lets an operator override any setting without editing +files, and a silently ignored broken file runs the program with settings nobody +chose. diff --git a/.agents/skills/coding-practices/references/logging.md b/.agents/skills/coding-practices/references/logging.md new file mode 100644 index 000000000..2e8b195cf --- /dev/null +++ b/.agents/skills/coding-practices/references/logging.md @@ -0,0 +1,58 @@ +# Logging + +Where and how code logs, in TypeScript (`@eserstack/logging`) and Go +(`pkg/ajan/logfx`). Secrets and personal data rules are in security-practices: +Secrets and Personal Data Never Reach Output. + +--- + +## Structured Logging + +Scope: Services, adapters and command handlers (the noskills daemon, HTTP and +CLI handlers, repository adapters). Pure domain functions and library utilities +do not log; they return values or errors, and the caller logs. + +Rule: Log through the project loggers with structured properties, never with +`console.*` (lint: `no-console`) or `fmt.Println`. Pick the level by layer and +attach correlation context. + +- Repository and data layer: `debug` and `trace`, `warn` for recoverable + anomalies. +- Service layer: `info` for completed operations that matter to an operator. +- Handlers: `error` once, where the failure is handled (errors.md: Handle Each + Error Once). +- Every entry carries the ids needed to follow one operation: trace or request + id, plus the entity id. Attach them once with `logger.with(...)` or + `logging.context.withContext(...)`. Extra arguments to `info(...)` land in a + single `args` attribute, so put fields in `with(...)`, not after the message. + +Correct: + +```typescript +import * as logging from "@eserstack/logging"; + +const logger = logging.logger.getLogger(["noskills", "sessions"]); + +export async function createSession(input: SessionInput, requestId: string) { + const log = logger.with({ requestId, projectId: input.projectId }); + const session = await store.insert(input); + await log.with({ sessionId: session.id }).info("session created"); + return session; +} +``` + +```go +logger.InfoContext(ctx, "session created", "sessionId", session.ID) +logger.DebugContext(ctx, "fetching session from store", "sessionId", id) +``` + +Incorrect: + +```typescript +console.log("session created"); // no structure, no context, fails lint +await logger.info("db query executed"); // wrong level for the repository layer +await logger.error("error"); // no context +``` + +**Why:** an operator diagnosing a failure has only the log; entries without +level discipline and ids cannot be filtered or joined into one story. diff --git a/.agents/skills/coding-practices/references/readability.md b/.agents/skills/coding-practices/references/readability.md new file mode 100644 index 000000000..ddf202521 --- /dev/null +++ b/.agents/skills/coding-practices/references/readability.md @@ -0,0 +1,379 @@ +# Readability + +Naming, comments, abstraction, checks and control flow, in TypeScript and Go. +Formatting itself belongs to `deno fmt` and `gofmt`; what they enforce is not +repeated here. + +## Contents + +- Self-Documenting Code +- Comments +- Don't Repeat Yourself +- Magic Values +- Explicit Checks (CRITICAL) +- Exhaustive Branching +- Identity Comparison +- Early Returns +- Line Length and Breaking +- Prefix Extraction +- No Runtime Code Generation or Global Patching +- Circular Dependencies + +--- + +## Self-Documenting Code + +Scope: All languages + +Rule: Name similar operations the same way within a package and across packages: +all loaders are `load*`, not a mix of `load*`, `fetch*` and `get*`. Use full +words, not abbreviations (`sessionRegistry`, not `sessReg`). Casing follows the +language and lint (`camelcase` in deno lint). + +**Why:** consistent verbs let a reader and a search find every operation of one +kind; a mix hides some of them. + +--- + +## Comments + +Scope: All languages + +Rule: Comments explain why: a trade-off, a constraint, a non-obvious algorithm. +Every deliberate shortcut or exception (a lint ignore, a validator exclude, a +hardcoded limit, a skipped case) carries a comment giving the reason and what +would remove it. Do not commit commented-out code; git history keeps it. TODOs +follow deno lint `ban-untagged-todo` (`TODO(owner): ...`). + +Correct: + +```typescript +// The FFI bridge returns untyped JSON; the schema check below narrows it. +// Remove the ignore when the bridge emits typed results. +// deno-lint-ignore no-explicit-any +const raw: any = bridge.call("status"); +``` + +Incorrect: + +```typescript +// deno-lint-ignore no-explicit-any +const raw: any = bridge.call("status"); +``` + +**Why:** an unexplained exception looks like an oversight, so the next person +either copies it or removes it without knowing what breaks. + +--- + +## Don't Repeat Yourself + +Scope: All languages + +Rule: Extract repeated logic at its third real use, or at the second when the +logic is complex or must stay in sync (a validation rule, a path layout, a wire +format). Lines that only look alike but encode different decisions are not +repetition; keep them apart. Prefer a plain function over a configurable +abstraction; add parameters when a caller needs them, not in advance. + +**Why:** duplicated logic drifts apart when one copy is fixed; a premature +abstraction couples unrelated callers and is harder to undo than a copy. + +--- + +## Magic Values + +Scope: All languages + +Rule: A number or string with meaning beyond its value (a limit, a timeout, a +status name, a path segment) is a named constant, and its unit is in the name +(`API_TIMEOUT_MS`, `maxPooledBuffer`). The comment on it says why that value. + +**Why:** the name says what the value means and gives one place to change it. + +--- + +## Explicit Checks (CRITICAL) + +Scope: TypeScript and JavaScript + +Rule: Use implicit truthy/falsy checks only on booleans. Compare everything else +explicitly: `=== null`, `=== undefined`, `=== ""`, `.length === 0`, `=== 0`, +`!== -1`. Defaults use `??`, never `||`. Equality is always `===` (lint: +`eqeqeq`). + +Correct: + +```typescript +if (value === null) {} +if (name === "") {} +if (items.length === 0) {} +if (index !== -1) {} +if (!response.ok) {} // boolean +const port = config.port ?? 8000; +``` + +Incorrect: + +```typescript +if (!value) {} // also true for 0, "" and false +if (!items.length) {} +if (user) {} +const port = config.port || 8000; // replaces a valid 0 +``` + +**Why:** `0`, `""` and `false` are often valid values; an implicit check treats +them as missing and the bug shows up only with those inputs. + +--- + +## Exhaustive Branching + +Scope: `switch` or `if` chains over unions and enums, in TypeScript and Go + +Rule: Handle every case. In TypeScript, end the switch with a `never` check so a +new member fails compilation. In Go, a `switch` over an enum-like type has a +`default` that returns an error. + +Correct: + +```typescript +switch (state.kind) { + case "idle": + return renderIdle(); + case "running": + return renderRunning(state); + default: { + const unreachable: never = state; + throw new Error(`unhandled state ${JSON.stringify(unreachable)}`); + } +} +``` + +```go +default: + return fmt.Errorf("%w: %q", ErrUnknownPhase, phase) +``` + +Incorrect: + +```typescript +switch (state.kind) { + case "idle": + return renderIdle(); +} // "running" silently returns undefined +``` + +**Why:** the compiler then finds every switch a new state must be added to. + +--- + +## Identity Comparison + +Scope: All languages + +Rule: Compare entities by their stable ids, never by display strings such as +slugs, usernames, emails or titles. + +Correct: + +```typescript +const isOwnProfile = membership.profileId === profile.id; +``` + +Incorrect: + +```typescript +const isOwnProfile = membership.profileSlug === profile.slug; // renames break it +``` + +**Why:** display strings change and differ in case and whitespace; ids do not. + +--- + +## Early Returns + +Scope: All languages + +Rule: Handle errors and edge cases first and return early, so the happy path +stays at the lowest indentation and reads straight down. Each guard checks one +condition and leaves; the main work comes after all of them, not in an `else`. + +Correct: + +```go +func LoadSession(root, sessionID string) (*Session, error) { + if !ValidSessionID(sessionID) { + return nil, fmt.Errorf("load session %q: %w", sessionID, ErrInvalidSessionID) + } + + data, err := os.ReadFile(filepath.Join(root, sessionID+".json")) + if err != nil { + return nil, fmt.Errorf("read session %q: %w", sessionID, err) + } + + return decodeSession(data) +} +``` + +Incorrect: + +```typescript +function sessionStatus(session: Session | null): Status { + if (session !== null) { + if (session.active) { + return loadStatus(session.id); + } else { + return "inactive"; + } + } else { + return "missing"; + } +} +``` + +**Why:** nested branches make the reader hold every condition at once; guards +retire them one by one. + +--- + +## Line Length and Breaking + +Scope: All code; TypeScript, JavaScript and Markdown are wrapped by `deno fmt`, +so this governs Go and anything the formatter does not wrap + +Rule: There is no fixed column limit, but a line past about 120 characters is +broken. Break where the structure already has a seam (between arguments, after +an operator, before a chained call), not at whatever column the line reaches. A +call with four or more arguments puts each argument on its own line, with the +closing parenthesis on its own line. This holds for code written on request too; +asking for a one-liner does not make a long call acceptable. Where `deno fmt` +owns the layout, keep its output rather than hand-wrapping against it. + +A signature that is too long to read usually has too many parameters. The fix is +an options struct or object (see architecture-guidelines: Public API and CLI +Surface), not cleverer wrapping. + +Correct: + +```go +handleSessions( + w, + r, + registry, + cfg, + logger, +) + +type StartOptions struct { + Root string + Timeout time.Duration + Logger *logfx.Logger +} + +func StartSession(ctx context.Context, opts StartOptions) (*Session, error) +``` + +Incorrect: + +```go +mux.HandleFunc("/api/sessions", func(w http.ResponseWriter, r *http.Request) { handleSessions(w, r, registry, cfg, logger) }) + +func StartSession(ctx context.Context, root string, timeout time.Duration, logger *logfx.Logger, retries int, dryRun bool) (*Session, error) +``` + +**Why:** a line broken at its seams can be read and diffed argument by argument. + +--- + +## Prefix Extraction + +Scope: Taking a value after a known prefix: HTTP headers, URIs, file paths, +protocol strings, in TypeScript and Go + +Rule: Check that the value starts with the prefix, then cut exactly the prefix +length. Never use a search-and-replace for this: it matches the first occurrence +anywhere in the string, so `"xBearer token"` or `"s3://file://bucket"` are +silently rewritten. Return "no value" when the prefix is missing or nothing +follows it. + +Correct: + +```typescript +function extractAfterPrefix( + value: string | null, + prefix: string, +): string | null { + if (value === null || !value.startsWith(prefix)) { + return null; + } + const extracted = value.slice(prefix.length); + return extracted.length > 0 ? extracted : null; +} + +const token = extractAfterPrefix(authHeader, "Bearer "); +``` + +```go +token, ok := strings.CutPrefix(authHeader, "Bearer ") +if !ok || token == "" { + return "", ErrMissingToken +} +``` + +Incorrect: + +```typescript +const token = authHeader?.replace("Bearer ", "") ?? null; // matches anywhere +const token2 = authHeader?.slice(7) ?? null; // never checks the prefix +``` + +```go +token := strings.Replace(authHeader, "Bearer ", "", 1) // matches anywhere +``` + +**Why:** a replace that matches mid-string accepts malformed input as valid, +which in a header parser is an authentication bug. + +--- + +## No Runtime Code Generation or Global Patching + +Scope: All languages + +Rule: Do not build and run code at runtime, and do not change shared built-ins +or other packages' internals. In TypeScript that means no `eval` (lint: +`no-eval`), no `new Function`, no prototype changes and no +`Object.defineProperty` on shared objects. In Go it means no `unsafe` or +`reflect` writes into another package's unexported state. + +Correct: + +```typescript +const handlers: Record Output> = { add, remove }; +const handler = handlers[action]; +``` + +Incorrect: + +```typescript +eval(`${action}(input)`); // any action string becomes code +Array.prototype.last = function () {}; // changes every array in the process +``` + +**Why:** both break static analysis, and any input that reaches them becomes +code. + +--- + +## Circular Dependencies + +Scope: Imports between TypeScript modules and packages + +Rule: Module imports form a graph without cycles. The `validate-circular-deps` +validator in `@eserstack/codebase` detects them; it is not part of the precommit +workflow, so run it after moving code between modules. Break a cycle by moving +the shared part into a module both sides import. + +**Why:** a cycle makes evaluation order decide which side sees `undefined` +exports, and the failure depends on which module is imported first. diff --git a/.agents/skills/coding-practices/references/resources-and-io.md b/.agents/skills/coding-practices/references/resources-and-io.md new file mode 100644 index 000000000..d0a3c1070 --- /dev/null +++ b/.agents/skills/coding-practices/references/resources-and-io.md @@ -0,0 +1,223 @@ +# Resources and I/O + +Timeouts, limits, cleanup, streaming, background work and performance claims, in +TypeScript and Go. Go specifics: go-practices context, data-structures and +performance references. + +## Contents + +- External Calls Have Timeouts +- Bound Every Resource +- Release Resources Where They Are Acquired +- Stream Large Data +- Owned Asynchronous Work +- Performance Claims Need Measurement + +--- + +## External Calls Have Timeouts + +Scope: Every network request, subprocess and other call that leaves the process, +in TypeScript and Go + +Rule: Every external call has a timeout, so one slow dependency cannot hang the +caller. Retry only operations that are safe to repeat (reads, idempotent +writes), with exponential backoff and a maximum number of attempts, and never +retry a validation or authorization failure. Between attempts, check whether the +caller has cancelled (Go `ctx.Err()`, TypeScript `signal.aborted`) and wait with +a timer that cancellation interrupts, so a retry loop never outlives its caller. +A caller that makes many calls to the same dependency bounds its concurrency. + +Correct: + +```typescript +import * as shellExec from "@eserstack/shell/exec"; + +const response = await fetch(url, { + signal: AbortSignal.any([signal, AbortSignal.timeout(10_000)]), +}); +await shellExec.exec`git fetch origin`.timeout(60_000).text(); +``` + +```go +client := httpclient.NewClient(httpclient.WithConfig(cfg)) // pkg/ajan/httpclient: timeouts and retries from config +``` + +Incorrect: + +```typescript +const response = await fetch(url); // waits forever on a stalled server +``` + +**Why:** without a bound, the slowest dependency sets the latency of everything +that calls it, and a stuck call holds its connection and memory indefinitely. + +--- + +## Bound Every Resource + +Scope: Pools, queues, buffers, caches, concurrency, request and file sizes, in +TypeScript and Go + +Rule: Everything that can grow has a limit: connection and worker pools, queue +and channel depths, in-memory caches, concurrent goroutines or promises, and the +size of what is read from a request, a file or a subprocess. Pick the limit on +purpose, make it a setting with a safe default when callers may need another +value, and decide what happens at the limit: wait, reject with an error, or drop +the oldest entry. + +Correct: + +```go +jobs := make(chan Job, 100) // producers block when 100 jobs are waiting +g.SetLimit(4) // at most 4 concurrent syncs +body := http.MaxBytesReader(w, r.Body, 1<<20) +``` + +Incorrect: + +```go +for _, p := range projects { + go sync(p) // one goroutine per project, however many there are +} +``` + +**Why:** an unbounded resource grows until the process runs out of memory or +file handles, usually under the load where it matters most. + +--- + +## Release Resources Where They Are Acquired + +Scope: Files, sockets, locks, timers, subprocesses, in TypeScript and Go + +Rule: Release a resource in the same scope that acquired it, set up right after +the acquisition succeeds: `defer` in Go, `try`/`finally` or `await using` in +TypeScript. A failure to close a writer can mean lost data, so report it. + +Correct: + +```typescript +const lock = await acquireLock(lockPath); +try { + return await updateState(state); +} finally { + await lock.release(); +} +``` + +Incorrect: + +```typescript +const lock = await acquireLock(lockPath); +const result = await updateState(state); // a throw here keeps the lock forever +await lock.release(); +``` + +**Why:** cleanup at the end of a function is skipped by the first early return +or thrown error someone adds later. + +--- + +## Stream Large Data + +Scope: Reading, transforming or transferring data whose size is not small and +fixed, in TypeScript and Go + +Rule: Process large or unbounded data as a stream: rows one at a time, files and +responses through streams, results through lazy iterators. Load all at once only +when the size is small and known. + +Correct: + +```typescript +const response = await fetch(logUrl, { signal }); +if (response.body === null) { + throw new Error(`fetch ${logUrl}: response has no body`); +} +// readLines: an async generator over a byte stream, as in +// pkg/@eserstack/noskills-client/attach.ts +for await (const line of readLines(response.body)) { + handle(line); +} +``` + +Incorrect: + +```typescript +const text = await runtime.fs.readTextFile(logPath); // the whole log in memory +text.split("\n").forEach(handle); +``` + +**Why:** memory stays constant however large the input grows, and the first +result arrives before the last byte is read. + +--- + +## Owned Asynchronous Work + +Scope: Promises in TypeScript, goroutines in Go + +Rule: Every asynchronous operation that is started is either awaited or owned by +something that waits for it and handles its error. Work that is deliberately +detached goes through a named helper that logs or reports failures. + +Correct: + +```typescript +await Promise.all(files.map((f) => processFile(f))); + +runInBackground("refresh cache", () => refreshCache()); // helper catches + logs +``` + +```go +g, ctx := errgroup.WithContext(ctx) +for _, f := range files { + g.Go(func() error { return processFile(ctx, f) }) +} +if err := g.Wait(); err != nil { + return fmt.Errorf("process files: %w", err) +} +``` + +Incorrect: + +```typescript +files.forEach((f) => processFile(f)); // rejections are lost +``` + +```go +go processFile(ctx, f) // nobody waits, the error is dropped +``` + +**Why:** an unowned promise loses its rejection; an unowned goroutine leaks and +hides its failure. + +--- + +## Performance Claims Need Measurement + +Scope: Changes made for performance, and any code or review comment claiming one + +Rule: A performance claim comes with a measurement: a `*.bench.ts` run with +`deno bench`, or `go test -bench`, before and after, on the same machine. Report +the numbers and the command. Without a measurement, write the plain version. + +- Profile before choosing what to change. If most of the time is spent waiting + on a database, an upstream service or the disk, fix that component; faster + code in the process will not show. +- Fix the algorithm before the constants: replacing a linear search in a loop + with a map lookup beats any tuning of the loop. +- Change one thing at a time and repeat each run several times; a single run + before and after cannot tell a small gain from noise. +- Comment an optimization that makes the code less obvious with what it avoids + and the measured gain, so nobody reverts it as needless complexity. +- Check new loops over I/O for per-item calls that can be batched (lint: + `no-await-in-loop` flags the TypeScript case), and check that buffers and + caches have a bound. + +The Go procedure (`benchstat`, pprof) is in go-practices: Benchmark, Change, +Compare. + +**Why:** intuition about bottlenecks is usually wrong, and an unmeasured +optimization costs readability for a gain nobody can confirm. diff --git a/.agents/skills/design-principles/SKILL.md b/.agents/skills/design-principles/SKILL.md new file mode 100644 index 000000000..69e0ddf8f --- /dev/null +++ b/.agents/skills/design-principles/SKILL.md @@ -0,0 +1,31 @@ +--- +name: design-principles +description: "Function and module design for eserstack in TypeScript and Go. Covers pure functions with thin effectful wrappers, readonly data, small injected interfaces, plain data objects, one clock read, cancellation handles, collections by access pattern, and side-effect-free modules. Use when shaping signatures, modules, state or collections. Not for system layout (use architecture-guidelines)." +--- + +# Design Principles + +How functions, modules, data and state are shaped. System structure lives in +architecture-guidelines; code-level quality rules in coding-practices. + +## Always + +- Logic lives in pure functions with explicit parameters; a thin wrapper reads + configuration and performs the effect. +- Return new values instead of mutating inputs; mark unmutated parameters and + fields `readonly`. +- Accept small interfaces and pass dependencies in; no inheritance for reuse. +- Data is plain objects plus functions; classes only for stateful services, no + getters or setters. +- Read the clock once per operation and pass the value down. +- One cancellation handle (`ctx`, `AbortSignal`) flows through the whole + operation; never start a fresh one mid-chain. +- Pick maps and sets for lookups, arrays and slices for sequences; hand out + copies of internal collections. +- Importing a module does no I/O and reads no environment; compute lazily. + +## References + +| File | Read when | +| ----------------------------------------- | -------------------------------------------------------------------------- | +| [principles.md](references/principles.md) | Designing a function, module, type or collection; reviewing state handling | diff --git a/.agents/skills/design-principles/evals/triggers.json b/.agents/skills/design-principles/evals/triggers.json new file mode 100644 index 000000000..2f1990e26 --- /dev/null +++ b/.agents/skills/design-principles/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "this function reads the clock three times, is that a problem", + "should_trigger": true + }, + { + "query": "make this config object immutable", + "should_trigger": true + }, + { + "query": "should this module have side effects at import", + "should_trigger": true + }, + { + "query": "split this function so the logic is testable without the filesystem", + "should_trigger": true + }, + { + "query": "how should cancellation be passed to this long-running task in TS", + "should_trigger": true + }, + { + "query": "which data structure for looking up sessions by id", + "should_trigger": true + }, + { + "query": "bu interface çok büyük, nasıl küçültürüz", + "should_trigger": true + }, + { + "query": "replace this class with getters and setters by something simpler", + "should_trigger": true + }, + { + "query": "add a GitHub Actions job", + "should_trigger": false + }, + { + "query": "fix the failing release run", + "should_trigger": false + }, + { + "query": "which package registry do we prefer", + "should_trigger": false + }, + { + "query": "write the ADR", + "should_trigger": false + }, + { + "query": "check if CONTRIBUTING.md is up to date", + "should_trigger": false + }, + { + "query": "add a new package to the monorepo", + "should_trigger": false + }, + { + "query": "the JWT secret is hardcoded", + "should_trigger": false + }, + { + "query": "ask me what you need before you start", + "should_trigger": false + } +] diff --git a/.agents/skills/design-principles/references/principles.md b/.agents/skills/design-principles/references/principles.md new file mode 100644 index 000000000..b15488121 --- /dev/null +++ b/.agents/skills/design-principles/references/principles.md @@ -0,0 +1,348 @@ +# Design Principles + +How functions, modules, data and state are shaped in eserstack, in TypeScript +and Go. Architecture-level structure (ports, adapters, composition roots) is in +architecture-guidelines: Hexagonal Architecture (Double-Layered). + +## Contents + +- Pure Functions +- Immutability +- Small Interfaces +- Data as Plain Objects +- One Clock Read per Operation +- Cancellation Flows With the Work +- Choose Data Structures by Access Pattern +- Module-Level Side Effects + +--- + +## Pure Functions + +Scope: All languages + +Rule: Put logic in pure functions that take everything they need as parameters +and return a result without touching outside state. Keep pure logic in modules +that do not import environment or framework state (env variables, build-tool +globals, UI context, process-wide singletons). When a module needs both, put the +logic in its own file with explicit parameters and let a thin wrapper inject the +configuration. A function that must have an effect keeps it at the edge: compute +first, then perform one effect with the result. + +Correct: + +```typescript +// discount.ts: pure, testable without setup +export function applyDiscount(order: Order, rate: number): Order { + return { ...order, total: order.total * (1 - rate) }; +} + +// checkout.ts: thin wrapper that reads configuration and performs the effect +export async function checkout(order: Order, deps: CheckoutDeps) { + const priced = applyDiscount(order, deps.config.discountRate); + await deps.orders.save(priced); +} +``` + +Incorrect: + +```typescript +let appliedDiscount = 0; // hidden state shared by every caller + +export function applyDiscount(order: Order): Order { + appliedDiscount = order.total * runtime.env.get("DISCOUNT_RATE"); // reads env + return { ...order, total: order.total - appliedDiscount }; +} +``` + +**Why:** a pure function is tested with plain values, and a change in the +environment cannot change its result. + +--- + +## Immutability + +Scope: All languages + +Rule: Do not mutate inputs or shared data; return a new value. In TypeScript, +mark properties and array or object parameters that are not mutated as readonly +(`readonly T[]`, `Readonly`, `as const` for literal tables), so the compiler +enforces it. Mutation of a local value that nobody else can see is fine. + +Correct: + +```typescript +export function addTag( + session: Readonly, + tag: string, +): Session { + return { ...session, tags: [...session.tags, tag] }; +} +``` + +Incorrect: + +```typescript +export function addTag(session: Session, tag: string): Session { + session.tags.push(tag); // the caller's object changes too + return session; +} +``` + +**Why:** a caller that passed a value does not expect it to change; readonly +types turn that expectation into a compile error. + +--- + +## Small Interfaces + +Scope: Interfaces and the functions that accept them, in TypeScript and Go + +Rule: Define small, purpose-specific interfaces, ideally in the package that +uses them. Public functions accept an interface, not a concrete type, so callers +can pass any implementation and tests can pass a fake. Pass dependencies in +(parameters, a deps object, a constructor) instead of inheriting behavior from a +base class. Do not add a wrapper that only forwards to another method; call the +method directly. + +Correct: + +```typescript +type Clock = { now(): number }; + +export function isExpired(session: Session, clock: Clock): boolean { + return session.expiresAt <= clock.now(); +} +``` + +```go +type SessionReader interface { + Get(ctx context.Context, id string) (*Session, error) +} + +func Summarize(ctx context.Context, r SessionReader, id string) (Summary, error) +``` + +Incorrect: + +```go +type EverythingStore interface { + Read() error + Write() error + Delete() error + Update() error + Migrate() error // every caller depends on all of it +} +``` + +**Why:** a small interface is cheap to fake in a test and leaves each caller +coupled only to what it uses. + +--- + +## Data as Plain Objects + +Scope: TypeScript types and classes + +Rule: Model data as plain objects with `readonly` fields and put behavior in +functions that take them. Use a class only for a stateful service that owns +resources or a cache. Do not use getters or setters: a property access that runs +code (logging, validation, lazy loading) surprises the caller. An update is a +function that returns a new object. + +Correct: + +```typescript +type User = { readonly id: string; readonly email: string }; + +export function changeEmail(user: User, email: string): User { + return { ...user, email }; +} + +export class SessionRegistry { + readonly #sessions = new Map(); + // ... +} +``` + +Incorrect: + +```typescript +class User { + #email = ""; + get email(): string { + this.logAccess(); // a read with a side effect + return this.#email; + } +} +``` + +**Why:** plain data serializes, compares and copies without surprises; state +belongs in the few objects that manage it. + +--- + +## One Clock Read per Operation + +Scope: Code that timestamps or compares times + +Rule: Read the clock once per logical operation and pass the value down. Do not +call `new Date()`, `Date.now()` or `time.Now()` inside a loop or in several +steps that must agree on the same time. Taking the clock as a parameter also +makes the code testable. + +Correct: + +```typescript +const now = Date.now(); +const expired = sessions.filter((s) => s.expiresAt <= now); +``` + +Incorrect: + +```typescript +const expired = sessions.filter((s) => s.expiresAt <= Date.now()); +``` + +**Why:** two reads in one operation can straddle a boundary and give the steps +different answers about the same moment. + +--- + +## Cancellation Flows With the Work + +Scope: Any operation that can be abandoned: requests, CLI commands, daemon RPCs, +background jobs, in TypeScript and Go + +Rule: An operation carries one cancellation handle from the point it starts to +every call it makes: a `context.Context` in Go, an `AbortSignal` in TypeScript. +Pass the caller's handle down instead of creating a new one, so when the caller +gives up (a client disconnects, a user presses Ctrl-C, a deadline passes) +everything the operation started stops. A step may shorten the remaining time +for its own part, never extend it. Work that must finish after the caller leaves +is detached on purpose, gets its own time limit and an owner that reports its +failure. + +Correct: + +```typescript +export async function syncAll( + projects: readonly Project[], + signal: AbortSignal, +): Promise { + for (const project of projects) { + signal.throwIfAborted(); + await syncOne( + project, + AbortSignal.any([signal, AbortSignal.timeout(30_000)]), + ); + } +} +``` + +```go +func SyncAll(ctx context.Context, projects []Project) error { + for _, p := range projects { + if err := syncOne(ctx, p); err != nil { + return fmt.Errorf("sync %s: %w", p.Name, err) + } + } + return nil +} +``` + +Incorrect: + +```typescript +await syncOne(project, new AbortController().signal); // caller cannot stop it +``` + +```go +_ = syncOne(context.Background(), p) // detached from the caller's deadline +``` + +The Go form is in the go-practices context reference (`context.md`). + +**Why:** a chain that drops the handle at one link keeps consuming CPU, +connections and quota for a caller that is no longer there. + +--- + +## Choose Data Structures by Access Pattern + +Scope: Collections in TypeScript and Go + +Rule: Pick the structure from how the data is read and written: + +- Keyed lookup or membership: a map or set (`Map`/`Set` in TypeScript, `map` in + Go), not a linear search through an array. +- Ordered data read in sequence: a contiguous array or slice. Use linked + structures only for frequent insertion in the middle, and only after + measuring. +- Known or estimable size: allocate once up front instead of growing in a loop. +- Shared state handed to a caller: return a copy or a read-only view, so the + caller cannot change the owner's data. + +Correct: + +```typescript +const activeById = new Map(sessions.map((s) => [s.id, s])); +const session = activeById.get(sessionId); + +export function listIds(): readonly string[] { + return [...ids]; +} +``` + +Incorrect: + +```typescript +const session = sessions.find((s) => s.id === sessionId); // O(n) per lookup in a loop + +export function listIds(): string[] { + return ids; // caller can push into the registry's own array +} +``` + +The Go form is in the go-practices data structures reference +(`data-structures.md`). + +**Why:** the wrong structure turns a lookup loop quadratic, and a shared +internal array lets any caller corrupt the owner's state. + +--- + +## Module-Level Side Effects + +Scope: All TypeScript modules + +Rule: Importing a module runs no I/O, reads no environment, starts no timers and +does no heavy computation. Module scope holds constants, types, and function and +class declarations. State that must be computed goes behind a lazy getter that +computes on first call and caches the result (or its promise). Mutable +module-level variables exist only as that cache. Top-level `await` is banned by +lint (`no-top-level-await`). + +Correct: + +```typescript +let settingsPromise: Promise | null = null; + +export const getSettings = (): Promise => { + if (settingsPromise === null) { + settingsPromise = loadConfig().then(processConfig); + } + return settingsPromise; +}; +``` + +Incorrect: + +```typescript +const env = runtime.env.toObject(); // environment read on import +export const settings = processConfig(await loadConfig()); // I/O on import +export let currentUser: User | null = null; // global mutable state +``` + +**Why:** code at module scope runs on every import, in import order, before a +test can intercept it; one failure there breaks every importer. diff --git a/.agents/skills/documentation-conventions/SKILL.md b/.agents/skills/documentation-conventions/SKILL.md new file mode 100644 index 000000000..63a4c2eeb --- /dev/null +++ b/.agents/skills/documentation-conventions/SKILL.md @@ -0,0 +1,28 @@ +--- +name: documentation-conventions +description: "Covers package READMEs, project docs and plain prose: namespace imports in examples, emoji and detail preservation, where docs live, docs that change with the code, and no empty qualifiers, unbacked claims, em dashes or generated rhythm. Use when writing or editing READMEs, docs, skill files, ADR text, backlog items, changelog entries, commit messages or code comments." +--- + +# Documentation Conventions + +Rules for what the repository's documents say and how they say it. + +## Always + +- README examples use namespace imports of the package root + (`import * as functions from "@eserstack/functions"`), never sub-path imports. +- Never remove existing emojis (🧱 or ⚡ titles, 🔗 links footer) or descriptive + details such as "(LIFO cleanup)". +- The documentation diff ships in the same change as the code it describes; edit + JSDoc, never the generated `docs/api/`. +- No empty qualifiers (comprehensive, seamless, robust), no claim, number or + quote that is not in the repo, no em dashes in new or edited text. +- Before delivering, read the text as a stranger: what looks generated, and + which fact has no source? Fix both. + +## References + +| File | Read when | +| ----------------------------------- | --------------------------------------------------------------------- | +| [readmes.md](references/readmes.md) | Writing a README, adding docs, changing a documented surface | +| [prose.md](references/prose.md) | Writing or reviewing any prose: docs, skills, commits, backlog, notes | diff --git a/.agents/skills/documentation-conventions/evals/triggers.json b/.agents/skills/documentation-conventions/evals/triggers.json new file mode 100644 index 000000000..01144a814 --- /dev/null +++ b/.agents/skills/documentation-conventions/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "write a README for @eserstack/cache", + "should_trigger": true + }, + { + "query": "this doc sounds generated, make it plain", + "should_trigger": true + }, + { + "query": "update the docs after changing the CLI flags", + "should_trigger": true + }, + { + "query": "write a backlog item for the flaky test", + "should_trigger": true + }, + { + "query": "the README example imports from a sub-path, fix it", + "should_trigger": true + }, + { + "query": "commit mesajını daha sade yaz", + "should_trigger": true + }, + { + "query": "keep the emojis but fix the README structure", + "should_trigger": true + }, + { + "query": "write the JSDoc for this exported function", + "should_trigger": true + }, + { + "query": "write an ADR structure", + "should_trigger": false + }, + { + "query": "cut the release notes automatically", + "should_trigger": false + }, + { + "query": "add a CI docs job", + "should_trigger": false + }, + { + "query": "fix the Go comment lint", + "should_trigger": false + }, + { + "query": "which test runner to use", + "should_trigger": false + }, + { + "query": "add a new package", + "should_trigger": false + }, + { + "query": "clarify what the user wants", + "should_trigger": false + }, + { + "query": "security review of the README endpoint", + "should_trigger": false + } +] diff --git a/.agents/skills/documentation-conventions/references/prose.md b/.agents/skills/documentation-conventions/references/prose.md new file mode 100644 index 000000000..8b7816e5a --- /dev/null +++ b/.agents/skills/documentation-conventions/references/prose.md @@ -0,0 +1,212 @@ +# Plain Prose + +Writing rules for every sentence a person reads in this repository. + +## Contents + +- Plain Prose +- Empty Qualifiers +- Claims Need Evidence +- No Em Dashes +- Borrowed Rhythm +- Filler and Hedging +- Name the Actor +- Neutral Terms +- Formatting That Shouts +- Audit Before Delivering + +--- + +## Plain Prose + +Scope: Every sentence a person reads: READMEs, skill docs, ADRs, backlog items, +changelog entries, commit messages, code comments + +Rule: Documentation says what the code does, in the words a maintainer would use +out loud. Text that reads as generated (empty qualifiers, borrowed rhythm, +claims with nothing behind them) is a defect even when every fact in it is true, +because readers stop trusting the page. The rules below name the patterns that +give it away. When a sentence trips one, fix the sentence; do not pad it back up +with a different pattern. + +--- + +## Empty Qualifiers + +Rule: Cut words that sound like praise and carry no information: comprehensive, +seamless, robust, powerful, elegant, cutting-edge, next-level, world-class, +effortless, streamline, leverage, harness (as a verb), unlock, elevate, empower, +delve, showcase, testament, game-changer. Replace the word with what the package +actually does, or with nothing. + +Correct: + +```markdown +`@eserstack/parsing` is a parsing library for analyzing and tokenizing strings. + +The HTTP adapter provides HTTP connections with built-in resilience features: +``` + +Incorrect: + +```markdown +`@eserstack/parsing` is a comprehensive parsing library designed to analyze and +tokenize strings. + +The HTTP adapter provides robust, production-ready HTTP connections with +built-in resilience features: +``` + +--- + +## Claims Need Evidence + +Rule: A statement about quality, scale or adoption is either backed by something +in the repository (a benchmark with its date and command, a test, a measured +size) or it is not written. This covers "production-ready", "battle-tested", +"blazing fast", "trusted by", "industry-leading", and any number, quote or name +that did not come from a source the reader can open. When you do not know a +fact, leave it out; never fill the gap with a plausible guess. + +Correct: + +```markdown +Measured on 2026-09-17 over this repository (1964 files, best of 4 warm runs): +Go 104.5 ms, TypeScript 126.1 ms. +``` + +Incorrect: + +```markdown +The Go validator is significantly faster. + + Used by thousands of teams. + +``` + +--- + +## No Em Dashes + +Rule: New or edited prose uses no em dash (`—`), spaced or not, and no double +hyphen standing in for one. Replace each with, in order of preference, a period, +a comma, a colon, or parentheses; restructure when none fits. Text you did not +touch keeps its punctuation, and so do verbatim quotes of program output, step +names and titles. Convert the sentences you are already editing; do not reformat +a file you were not asked to change. + +Correct: + +```markdown +Every publish step is idempotent: `deno publish` skips versions already on JSR, +and asset upload clobbers. The chain can therefore be run again for the same +tag. +``` + +Incorrect: + +```markdown +Every publish step is idempotent — `deno publish` skips versions already on JSR, +asset upload clobbers — so the chain can be run again for the same tag. +``` + +--- + +## Borrowed Rhythm + +Rule: Avoid the sentence shapes that models reach for by default: + +- "It's not just X, it's Y" and "not only X but also Y" +- A run of clipped fragments for drama ("No config. No setup. No surprises.") +- Every list forced to three items when the content has two or five +- "From X to Y, and everything in between" where X and Y are not a scale +- Aphorisms ("Simplicity is the language of trust") +- Signposting ("Let's dive in", "Here's what you need to know", "In this section + we'll explore") +- Chatbot closers ("I hope this helps", "Let me know if you have questions") +- Upbeat send-offs ("Exciting times ahead", "The future looks bright") + +Say the thing directly, in as many items as there are. + +Correct: + +```markdown +The dashboard shows the metrics you select. Options come from the selected item. +``` + +Incorrect: + +```markdown +It's not just a dashboard, it's a command center. No guessing. No noise. +``` + +--- + +## Filler and Hedging + +Rule: "In order to" is "to"; "due to the fact that" is "because"; "it is +important to note that" and "it is worth noting" are nothing. One qualifier per +claim: "may" or "usually", never "could potentially possibly". + +Correct: + +```markdown +Spawning it would mean shipping and installing a program to talk to a struct +already in memory. +``` + +Incorrect: + +```markdown +Spawning it would mean shipping and installing a program in order to talk to a +struct already in memory. +``` + +--- + +## Name the Actor + +Rule: When the actor is known, the sentence names it. "The pipeline publishes +the platform packages first", not "the platform packages are published first". +Passive is fine when the actor is unknown, irrelevant or deliberately withheld +("the server was restarted at 03:00"). Do not give abstractions a mind: a +resolver probes, a validator reports, a bundle imports; none of them +"understands", "decides" or "wants". + +--- + +## Neutral Terms + +Rule: Use neutral technical terms in new code, flags and docs: allowlist and +denylist, primary and replica, main. Do not assume the reader's gender, +operating system or access to a paid tool. + +--- + +## Formatting That Shouts + +Rule: Emphasis is written into the sentence, not into the markup. + +- No inline-header bullets that restate the item + (`- **Performance:** it is + faster`). Changelog scope labels + (`**codebase**:`) are a commit convention and stay. +- No bold on every key term; bold at most the one phrase the reader must not + miss. +- No quotation marks as emphasis or hedging; quotes are for real quotes, titles + and program output. +- No all-caps clauses inside paragraphs. +- Emojis only where the Emoji Preservation rule already puts them: package + titles and the links footer. Not as bullet decoration in body text. + +--- + +## Audit Before Delivering + +Rule: Before handing over any documentation, read it once as a stranger and +answer two questions: what in this text makes it look generated, and does it +state any fact, number, name, date or quote that is not in the source or in the +repository. Fix both. Then search the diff for `—`; a hit means the text is not +done. This is the same loop the antislop copywriting skill describes; the +patterns above are its documentation-specific subset, adapted to this +repository's own conventions (emoji titles and footers, changelog scope labels). diff --git a/.agents/skills/documentation-conventions/references/readmes.md b/.agents/skills/documentation-conventions/references/readmes.md new file mode 100644 index 000000000..406945e5a --- /dev/null +++ b/.agents/skills/documentation-conventions/references/readmes.md @@ -0,0 +1,212 @@ +# READMEs and Project Docs + +What package READMEs and project documents contain, where they live, and when +they change. + +## Contents + +- README Import Pattern +- Emoji Preservation +- Descriptive Text Preservation +- Package README Structure +- Cross-Package References +- Docs Change With the Code +- Where Documentation Lives +- Contributor Setup Stays Documented + +--- + +## README Import Pattern + +Scope: All package README.md files + +Rule: README code examples must use the **namespaced import pattern** +(`import * as pkg from "@eserstack/pkg"`) and access everything through the +namespace. Never show sub-path direct imports in README examples. Sub-path +exports exist in deno.json for advanced users, but READMEs use the primary +namespace pattern. + +Correct: + +```typescript +import * as functions from "@eserstack/functions"; + +// Access through namespace +const result = await functions.run(async function* () {/* ... */}); +const pipeline = functions.collect(); +const myTask = functions.task.fromPromise(() => fetch("/api")); +const data = await functions.resources.bracket(acquire, use, release); +``` + +Incorrect: + +```typescript +// ❌ Sub-path imports in README examples +import { collect } from "@eserstack/functions"; +import * as resources from "@eserstack/functions/resources"; +import * as task from "@eserstack/functions/task"; +``` + +--- + +## Emoji Preservation + +Scope: All documentation files + +Rule: Never remove existing emojis from files. Package README titles use emojis +(e.g. 🧱, ⚡) and footers use 🔗. Preserve them when editing. + +Correct: + +```markdown +# 🧱 @eserstack/primitives + +... + +## 🔗 Links +``` + +Incorrect: + +```markdown +# @eserstack/primitives + +... + +## Links +``` + +--- + +## Descriptive Text Preservation + +Scope: All documentation files + +Rule: Never strip descriptive details, comments, or explanatory text when +rewriting documentation. If the original says "Safe acquire-use-release patterns +with guaranteed cleanup", do not shorten it to "Safe acquire-use-release +patterns". Preserve all descriptive context: details like "(LIFO cleanup)" and +"in reverse order" exist for a reason. + +Correct: + +```markdown +Safe acquire-use-release patterns with guaranteed cleanup (LIFO order) +``` + +Incorrect: + +```markdown +Safe acquire-use-release patterns +``` + +--- + +## Package README Structure + +Scope: All package README.md files + +Rule: Each package README should include: + +1. **Title** with emoji and package name +2. **Vision/description**: what the package does and its design philosophy +3. **Quick Start**: the most common use case first, in the fewest lines that run + as pasted, with namespace imports. Options, adapters and advanced composition + come in the API sections, not in the first example +4. **API sections**: grouped by category with examples +5. **Links footer** with 🔗 emoji + +--- + +## Cross-Package References + +Scope: Documentation that explains how packages relate + +Rule: State what each layer provides and how the layers differ, and show each +import from the package that owns it, still as a namespace import of the package +root. + +Correct: + +```typescript +// Types and constructors come from @eserstack/primitives +import * as primitives from "@eserstack/primitives"; +// Pure FP utilities come from @eserstack/fp +import * as fp from "@eserstack/fp"; + +const ok = primitives.results.ok(42); +``` + +Incorrect: + +```typescript +import * as results from "@eserstack/primitives/results"; // sub-path import +``` + +--- + +## Docs Change With the Code + +Scope: Any change to a public export, CLI command or flag, config key, +environment variable, daemon endpoint, or install and first-run steps + +Rule: The documentation diff ships in the same change as the code diff; it is +not a follow-up. Before marking the task done, find each doc that describes the +changed surface and make it describe the new state: + +- Exported function or type: its JSDoc in the package, which feeds the generated + reference in `docs/api/`. Edit the JSDoc, never `docs/api/`, and run + `deno task cli docs-lint`. +- Package usage: the package README Quick Start and API sections. +- CLI command or flag: the command's help text and the README that shows it. +- Install, configuration or first run: the root README and CONTRIBUTING.md. +- Environment variable: the package README lists its name, purpose, whether it + is required, its default and an example value. Environment-specific URLs come + from configuration or environment variables, never from literals in code. +- Removed or changed behavior: delete or rewrite the sentences that describe the + old behavior. Descriptive Text Preservation protects details that are still + true, not text that has become false. + +README examples run as pasted: imports included, no elided setup the reader must +guess. + +**Why:** a feature whose docs lag the code does not exist for the reader who +finds it later, and stale instructions cost more than missing ones. + +--- + +## Where Documentation Lives + +Scope: Adding or moving project documentation + +Rule: Put a document where its kind already lives, and do not create a new +top-level docs file when one of these fits: + +| Location | Holds | +| --------------------------------- | ----------------------------------------------------- | +| `README.md` | Project overview, install, first run | +| `pkg/@eserstack//README.md` | Package usage (Package README Structure) | +| `.github/ARCHITECTURE.md` | Repository architecture overview | +| `.github/SECURITY.md` | Security policy and reporting | +| `CONTRIBUTING.md` | Contributor setup and workflow | +| `docs/adr/` | Architectural decisions (architecture-guidelines) | +| `docs/api/` | Generated by `deno task cli docs`; never edit by hand | +| `CHANGELOG.md` | Generated by `deno task cli changelog` at release | + +CONTRIBUTING.md exists both at the root and in `.github/`, and the two differ. +Until one is chosen as canonical, a change to contributor setup updates both. + +--- + +## Contributor Setup Stays Documented + +Scope: Changes that add a tool, a required environment variable, a service, or a +setup step + +Rule: A first-time contributor goes from `git clone` to a passing +`deno task cli ok` using only CONTRIBUTING.md. A change that adds a prerequisite +or a step updates CONTRIBUTING.md in the same change, and prefers folding the +step into `deno task cli init` over documenting another manual step. Changes to +noskills-server install or first run stay within the time-to-hello-world target +in `docs/adr/0002-magical-moment-and-tthw-target.md`. A setup failure prints +what is missing and the command that fixes it. diff --git a/.agents/skills/enrichment/SKILL.md b/.agents/skills/enrichment/SKILL.md new file mode 100644 index 000000000..6ff82b3a0 --- /dev/null +++ b/.agents/skills/enrichment/SKILL.md @@ -0,0 +1,42 @@ +--- +name: enrichment +description: "Interviews the user about a plan, decision or idea in rounds, level by level down a decision tree, until both share one understanding; facts are researched by sub-agents, decisions stay with the user. Use when the user asks to enrich, grill or stress-test a plan, and offer it when drafting a PRD, spec, ADR or backlog item whose content has open decisions. Not for a one-off comparison." +--- + +# Enrichment + +A deliberate interview about a plan, decision or idea that continues until no +decision is left open; nothing is built during it. It starts in two ways: the +user asks for it, or the agent offers it while drafting a plan (a PRD, spec, ADR +or backlog item) whose content has open decisions. The short clarification +before a normal task stays in workflow-practices: Ask, Assume or Proceed. + +## Always + +- When drafting a plan with more open decisions than one round of clarification + settles, offer an enrichment session instead of guessing; start it only when + the user accepts +- Model the topic as a design tree: each decision branches into the decisions + that depend on it, level by level from goals down to details +- When a level of the tree is settled, stop and ask whether to go into the next + level of detail or finish at this depth +- Each round asks the whole frontier, every open decision whose prerequisites + are settled, numbered, each with a recommended answer and its reason +- Never ask a question whose answer depends on another question still open in + the same round; it waits for a later round +- Facts are the agent's job: look up anything the repository, tools or docs can + answer, with sub-agents in parallel, and never ask the user for it +- A pending lookup blocks only the questions downstream of it; ask the rest of + the frontier now +- Decisions are the user's: put each one to them, and wait for the answers + before the next round +- The session ends when the frontier is empty, or when the user stops or + declines the next level; then summarize the settled tree, list what is still + open, and write the result into the artifact being drafted +- Do not act on the outcome until the user confirms the summary + +## References + +| File | Read when | +| ----------------------------------------------------- | ---------------------------------------------------------------- | +| [interview-rounds.md](references/interview-rounds.md) | Running a session: starting it, levels, rounds, facts, ending it | diff --git a/.agents/skills/enrichment/evals/triggers.json b/.agents/skills/enrichment/evals/triggers.json new file mode 100644 index 000000000..64a371d06 --- /dev/null +++ b/.agents/skills/enrichment/evals/triggers.json @@ -0,0 +1,74 @@ +[ + { + "query": "enrich this plan for the notification system", + "should_trigger": true + }, + { + "query": "grill me on the caching design before we build anything", + "should_trigger": true + }, + { + "query": "bu fikri enrich edelim", + "should_trigger": true + }, + { + "query": "stress-test my migration plan to pgx", + "should_trigger": true + }, + { + "query": "interrogate this idea until nothing is left open", + "should_trigger": true + }, + { + "query": "I want every open decision on the plugin system surfaced before we start", + "should_trigger": true + }, + { + "query": "planımı sorgula, tüm kararları tek tek çıkaralım", + "should_trigger": true + }, + { + "query": "poke holes in this release strategy and ask me everything you need", + "should_trigger": true + }, + { + "query": "add caching to the API", + "should_trigger": false + }, + { + "query": "what does the release command do", + "should_trigger": false + }, + { + "query": "fix the failing Go test", + "should_trigger": false + }, + { + "query": "write a README for the cache package", + "should_trigger": false + }, + { + "query": "which skills apply to this task", + "should_trigger": false + }, + { + "query": "review this PR and rank the findings", + "should_trigger": false + }, + { + "query": "make the CLI faster", + "should_trigger": false + }, + { + "query": "summarize the security report", + "should_trigger": false + }, + { + "query": "can we discuss the pros and cons of Deno vs Node here", + "should_trigger": false + }, + { + "query": "plan the implementation of TASK-23", + "should_trigger": false + } +] diff --git a/.agents/skills/enrichment/references/interview-rounds.md b/.agents/skills/enrichment/references/interview-rounds.md new file mode 100644 index 000000000..57f42c7bd --- /dev/null +++ b/.agents/skills/enrichment/references/interview-rounds.md @@ -0,0 +1,178 @@ +# Interview Rounds + +How an enrichment session runs, round by round. + +## Contents + +- Starting a Session +- The Design Tree +- Levels and Checkpoints +- The Frontier +- Round Format +- Facts and Sub-Agents +- Processing Answers +- Ending the Session + +--- + +## Starting a Session + +Scope: When an enrichment session begins + +Rule: A session starts in one of two ways. + +- **The user asks** ("enrich this", "grill me on the plan"): start with the + first round. +- **The agent offers it** while drafting a plan: a PRD, a spec, an ADR, the + content of a backlog item, or any other artifact whose content decides what + gets built. Offer it when the draft has more open decisions than one round of + clarification settles, or when a guess would change the scope. Say how many + decisions are open and which area they are in, and ask whether to run a + session; if the user declines, list the open decisions as explicit assumptions + in the draft (workflow-practices: Stating Assumptions). + +Correct: + +``` +The spec for the notification system has about eight open decisions (delivery +channels, retry policy, storage, user preferences). Shall I run an enrichment +session before writing it, or draft it now with those as stated assumptions? +``` + +--- + +## The Design Tree + +Scope: The start of a session + +Rule: Before the first round, sketch the decisions the topic contains and how +they depend on each other: a storage decision depends on the data model, a +migration plan on the storage decision. Keep the tree in your working notes, not +in the reply; the user sees it only as rounds and, at the end, as the summary. +Leave out what the repository, the skills or the linters already decide; those +are facts, not decisions. + +**Why:** asking in dependency order is what keeps a round answerable without +guessing, and the tree is what shows when a branch is still unvisited. + +--- + +## Levels and Checkpoints + +Scope: Moving from one depth of the tree to the next + +Rule: Work the tree level by level: first the goals and scope, then the +approach, then the details of each part. When every decision of the current +level is settled, pause before going deeper: give a short summary of the level, +say which areas the next level would cover and roughly how many questions it +holds, and ask whether to continue into the details or stop at this depth. + +``` +Level 1 is settled: scope, channels (email and in-app) and storage (SQLite). +Level 2 would cover retry and backoff, the preference model and the delivery +API, around nine questions. Go into the details, or stop here and write the +spec at this depth? +``` + +A plan that is only needed at the level of scope stops here, and the remaining +levels become open items in the artifact. A user who wants the full picture +continues. + +**Why:** the right depth differs per plan; asking at each level keeps a backlog +item from turning into a design document, and a design from stopping before its +details. + +--- + +## The Frontier + +Scope: Choosing the questions of a round + +Rule: The frontier is every open decision of the current level whose +prerequisites are all settled. Ask all of it in one round, most consequential +first. A decision that depends on an answer you have not heard yet is not on the +frontier, even when it seems likely; asking it now forces the user to answer a +hypothetical. + +--- + +## Round Format + +Scope: Every round + +Rule: Number questions across the whole session (the second round continues at +Q5, not Q1), so an answer like "Q6: B" stays unambiguous. Each question has a +short title, the context the user needs, the options when there are discrete +ones, and your recommended answer with its reason. Use this text format rather +than a question tool, which limits how many questions and how much context fit. + +``` +❓ **Q1** - **Storage for session history**: Sessions are currently kept in +memory and lost on restart. Options: +A. SQLite file next to the daemon state +B. PostgreSQL through connfx's pgx adapter +C. Keep in memory, add an export command + +➡️ A: the daemon runs on developer machines where PostgreSQL is not a given, +and a pure-Go SQLite driver (`modernc.org/sqlite`) is already in `go.mod` +(used by connfx tests), so no cgo or new dependency is needed. + +--- + +❓ **Q2** - **Retention**: ... + +➡️ ... +``` + +Write questions in the conversation's language. Anything written into the +repository afterwards is English (workflow-practices: Language). + +--- + +## Facts and Sub-Agents + +Scope: Questions that need information from the environment + +Rule: When a frontier question needs a fact (what the code does today, which +dependency is already present, what a tool supports), look it up instead of +asking. For a lookup that takes more than a quick search, dispatch a sub-agent +and keep going: ask the frontier questions that do not depend on it now, and the +dependent ones in the round after the result arrives. Report the facts you found +in the round where they matter, with file:line, so the user decides on the same +information you have. + +**Why:** a user asked for something the code already answers loses time and may +answer wrong; a round held back for one lookup stalls every independent +decision. + +--- + +## Processing Answers + +Scope: Between rounds + +Rule: + +- Mark each answered decision settled and recompute the frontier: settled + decisions unblock the ones that depended on them. +- "Your recommendation" or "ok" settles a question with the recommended answer. +- An unclear or partial answer stays open and comes back, rephrased, in the next + round. +- An answer that contradicts an earlier decision reopens that branch: say which + earlier answer it conflicts with and ask which one holds. +- A new idea from the user adds a branch to the tree. + +--- + +## Ending the Session + +Scope: When the frontier is empty, or the user stops or declines the next level + +Rule: The session is complete when every branch of the chosen depth has been +visited and nothing is silently assumed. Then give a summary of the settled +tree: each decision and its answer, grouped by branch, followed by what is still +open. If the user stops early, the open decisions become explicit assumptions in +that summary. Ask the user to confirm it. After they do, write the result into +the artifact the session was for (the PRD, spec, ADR or backlog item), with the +open items listed in it; any code change after that follows the normal task +workflow (workflow-practices). diff --git a/.agents/skills/eser-rules-manager/SKILL.md b/.agents/skills/eser-rules-manager/SKILL.md new file mode 100644 index 000000000..8a4cb6f97 --- /dev/null +++ b/.agents/skills/eser-rules-manager/SKILL.md @@ -0,0 +1,50 @@ +--- +name: eser-rules-manager +description: "Picks the skills a task needs and maintains the rules in .agents/skills. Use when starting any task to choose skills, when the user states a preference or a rule to remember, or when creating, changing, splitting, validating or evaluating a skill." +--- + +# Skill Discovery and Rule Management + +Chooses which skills apply to a task, and says how rules are added and kept in +order. + +## Always + +- Load every skill that might apply before the first answer or question, and + name them: a skill loaded late cannot change the plan +- A new rule goes into the one skill that owns the topic; other skills point to + it as `skill-name: Exact Section Title` +- Write only what a strong model would not do by default here: project choices, + real paths and commands, and the reason for each rule +- Every example follows the other skills' rules +- SKILL.md stays an overview of at most 80 lines that links every reference file + with a "Read when" +- Run `node .agents/skills/eser-rules-manager/scripts/validate-skills.ts --fix` + after every skill change +- A change meant to alter behavior is evaluated against a snapshot of the old + skill + +## Available Skills + +| Skill | Covers | +| --------------------------- | ----------------------------------------------------- | +| `enrichment` | Round-based interview until no decision is left open | +| `handoff` | `/handoff`: a document a fresh session continues from | +| `workflow-practices` | Clarifying, roles, approvals, task flow, gates | +| `architecture-guidelines` | Hexagonal layers, API surface, testing, ADRs | +| `design-principles` | Pure functions, immutability, interfaces, clocks | +| `coding-practices` | Errors, logging, resources, config, readability | +| `javascript-practices` | TS/JS modules, types, async, React | +| `go-practices` | Go conventions, context, data structures, perf | +| `security-practices` | Secrets, input, SSRF, injection, LLM trust boundary | +| `tooling-standards` | Tooling, dependencies, scripts, naming, packages | +| `documentation-conventions` | READMEs, docs and prose style | +| `release-and-ci` | Versions, releases, publishing, workflows, deploys | + +## References + +| File | Read when | +| ----------------------------------------------------- | -------------------------------------------------- | +| [skill-discovery.md](references/skill-discovery.md) | Unsure which skills a task needs | +| [skill-format.md](references/skill-format.md) | Writing or restructuring a skill or a rule | +| [skill-evaluation.md](references/skill-evaluation.md) | Testing that a skill changes behavior and triggers | diff --git a/.agents/skills/eser-rules-manager/evals/triggers.json b/.agents/skills/eser-rules-manager/evals/triggers.json new file mode 100644 index 000000000..672bfd073 --- /dev/null +++ b/.agents/skills/eser-rules-manager/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "remember: never use em dashes in docs", + "should_trigger": true + }, + { + "query": "add a rule that Go files must not use init", + "should_trigger": true + }, + { + "query": "create a skill for UI conventions", + "should_trigger": true + }, + { + "query": "the go-practices skill is too long, split it", + "should_trigger": true + }, + { + "query": "bundan sonra commit mesajlarında scope zorunlu olsun, kural olarak ekle", + "should_trigger": true + }, + { + "query": "evaluate whether the security skill actually triggers", + "should_trigger": true + }, + { + "query": "which skills should I load for this task", + "should_trigger": true + }, + { + "query": "validate the skills after my edit", + "should_trigger": true + }, + { + "query": "write a README", + "should_trigger": false + }, + { + "query": "fix the lint error", + "should_trigger": false + }, + { + "query": "cut a release", + "should_trigger": false + }, + { + "query": "add a GitHub Action", + "should_trigger": false + }, + { + "query": "explain the hexagonal layers", + "should_trigger": false + }, + { + "query": "write a Go test", + "should_trigger": false + }, + { + "query": "commit these", + "should_trigger": false + }, + { + "query": "what's the SSRF rule", + "should_trigger": false + } +] diff --git a/.agents/skills/eser-rules-manager/references/skill-discovery.md b/.agents/skills/eser-rules-manager/references/skill-discovery.md new file mode 100644 index 000000000..78cd0bd21 --- /dev/null +++ b/.agents/skills/eser-rules-manager/references/skill-discovery.md @@ -0,0 +1,57 @@ +# Skill Discovery + +Which skills to load for a task, and in what order. + +--- + +## Load Before Responding + +Scope: Every task, including questions and clarifying questions + +Rule: Before the first answer, question or file read, load every skill that +might apply (a small chance is enough) and say which ones you are applying: +`Applying skills: workflow-practices, go-practices, security-practices`. A skill +loaded after the plan is made cannot change the plan. Loading one that turns out +not to matter costs little. + +Look past the obvious one. "Write a Go service for notifications" needs +go-practices, and also architecture-guidelines (layers), security-practices +(input and secrets) and workflow-practices (steps and gates). + +**Why:** agents tend to under-load skills; the ones missed are the ones whose +rules were broken. + +--- + +## Skill Trigger Keywords + +| Skill | Trigger keywords and patterns | +| --------------------------- | ---------------------------------------------------------------- | +| `enrichment` | enrich, grill, stress-test, interrogate a plan or idea | +| `handoff` | user-invoked only: `/handoff [next focus]` | +| `workflow-practices` | any file change, vague request, review, push, tag, delete | +| `architecture-guidelines` | structure, layers, hexagonal, ADR, test strategy | +| `design-principles` | pure function, immutability, interfaces, clock, cancel | +| `coding-practices` | errors, logging, timeouts, config, naming, comments | +| `javascript-practices` | .ts, .tsx, .js, React, imports, async, types | +| `go-practices` | .go, pkg/ajan, context, golangci-lint, benchmarks | +| `security-practices` | auth, secret, token, input, SSRF, injection, LLM input | +| `tooling-standards` | dependency, package.json, new package, file name, license header | +| `documentation-conventions` | README, docs, JSDoc, changelog prose, backlog text | +| `release-and-ci` | release, version bump, JSR or npm publish, workflow, CI, deploy | +| `eser-rules-manager` | new rule, preference, create or change a skill, evals | + +--- + +## Order + +When several skills apply, apply them in this order: + +1. Process: workflow-practices (clarifying, approvals, steps and gates). +2. Design and implementation: architecture-guidelines, design-principles, the + language skill, coding-practices, security-practices. +3. Delivery: release-and-ci. + +Process and safety skills are followed as written (commit policy, quality gates, +release steps). Design and language skills state defaults that the code around +the change may refine. diff --git a/.agents/skills/eser-rules-manager/references/skill-evaluation.md b/.agents/skills/eser-rules-manager/references/skill-evaluation.md new file mode 100644 index 000000000..7198e5c32 --- /dev/null +++ b/.agents/skills/eser-rules-manager/references/skill-evaluation.md @@ -0,0 +1,196 @@ +# Skill Evaluation + +How to show that a new or changed skill changes behavior, and that its +description makes it load when it should. + +## Contents + +- Test First +- When to Evaluate +- Behavior Evals +- Trigger Evals +- Improving From Results +- Pressure and Model Coverage +- Watch How Agents Use the Skill +- Before Sharing + +--- + +## Test First + +Scope: Every new skill and every rule meant to change behavior + +Rule: Observe the failure before writing the fix, as with code: + +1. **Red.** Run realistic tasks without the skill (or against a snapshot of the + old version). Record what went wrong and the agent's own words when it talked + itself out of a rule. +2. **Green.** Write the least text that addresses those failures, then rerun. +3. **Refactor.** When new excuses appear, answer them in the rule's reason or an + anti-rationalization note, and rerun until the runs comply. + +**Why:** a rule written for an imagined failure costs context in every session +and may not address what agents actually get wrong. + +--- + +## When to Evaluate + +Scope: Creating a skill, or a change to a skill that alters what an agent does + +Rule: Evaluate when the change is meant to change behavior: a new rule, a +reworded rule that agents were ignoring, a new or rewritten description. Skip it +for typo fixes, link fixes and formatting. Rules whose result is objectively +checkable (a file exists, a command ran, a pattern is absent from the diff) get +assertions. Rules about judgment or prose style get a human read of the outputs +instead of forced assertions. + +**Why:** a skill that reads well but does not change what an agent does is noise +in every context it loads into. + +--- + +## Behavior Evals + +Scope: Checking that the skill's rules are followed + +Rule: Write 2-3 realistic task prompts, the kind a person would type in this +repo, and run each one twice in the same turn with separate subagents: + +- **With skill:** the subagent reads the skill under test. +- **Baseline:** for a new skill, no skill at all. For a changed skill, the old + version: snapshot it before editing and point the baseline at the snapshot. + +```bash +cp -r .agents/skills/ etc/temp/_skill-evals//snapshot/ +``` + +Launch both runs together so they finish around the same time. While they run, +write the assertions. Each assertion is objectively verifiable and named so a +reader knows what it checks. After the runs, grade each assertion against the +output with a pass or fail and one line of evidence (a file path, a quoted line, +a command result). Check assertions with a script when possible. Then read the +transcripts, not only the final output. + +Keep the work outside the tracked tree, under +`etc/temp/_skill-evals//iteration-//{with_skill,baseline}/` +(`etc/temp/` is gitignored; the leading `_` keeps Go's `./...` from compiling Go +files an eval writes there). Commit only the eval definitions: +`.agents/skills//evals/evals.json`. + +```json +{ + "skill_name": "workflow-practices", + "evals": [ + { + "id": 1, + "name": "bugfix-writes-failing-test-first", + "prompt": "the config merge drops a timeout of 0 from the env, fix it", + "assertions": [ + "A test reproducing the zero-value case is added", + "The commit body names the root cause with file:line" + ] + } + ] +} +``` + +A skill passes when the with-skill runs pass assertions that the baseline fails. +An assertion that passes in both configurations does not discriminate; replace +it or drop it. + +--- + +## Trigger Evals + +Scope: A new skill, or any change to a `description` + +Rule: The description is what decides whether the skill loads at all. Write +16-20 realistic queries and store them in +`.agents/skills//evals/triggers.json`: + +- 8-10 should trigger: different phrasings of the same need, formal and casual, + some that never name the skill's topic directly. +- 8-10 should not trigger: near misses that share keywords but need a different + skill. "Write a fibonacci function" is not a useful negative for a release + skill; "bump the Go module tag for ajan" is, because Go modules do not use the + version-bump script. + +```json +[ + { "query": "cut v4.6.0 and push the tag", "should_trigger": true }, + { "query": "tag pkg/ajan v0.9.2 for the Go module", "should_trigger": false } +] +``` + +Queries must be substantive. An agent does not consult a skill for a one-step +request it can do directly, so "read file X" tests nothing. Check the +description against the set. When skill-creator is installed, its +`scripts/run_loop.py` automates this with a held-out test split; take its best +description by test score, not train score. + +--- + +## Improving From Results + +Scope: The iteration after a failed or mixed eval + +Rule: + +- Generalize. Fix the reason a run went wrong, not the specific prompt; a skill + tuned to three prompts fails on the fourth. +- Remove text that is not pulling its weight. If transcripts show agents + spending time on something the skill told them to do that did not help, cut it + and rerun. +- Explain the reason. When an agent ignored a rule, adding capital letters + rarely helps; saying why the rule exists usually does. +- Bundle repeated work. If every run wrote the same helper script, put it in the + skill's `scripts/` and tell the skill to run it. + +Rerun into a new `iteration-/` with the same baseline, and stop when the +with-skill runs pass, the owner is satisfied, or iterations stop improving. + +--- + +## Pressure and Model Coverage + +Scope: Discipline rules (tests, commits, quality gates) and skills used by +several models + +Rule: Test discipline rules under the pressure that breaks them, not in calm +conditions. Combine at least three pressures in one prompt: time ("we are three +hours in"), sunk cost, the user's insistence ("just make it work"), fatigue at +the end of a long session. A rule that holds only without pressure does not +hold. + +Run the evals on the smallest model that will use the skill as well as the +largest. A skill that works for Opus can be too terse for Haiku; one tuned for +Haiku can over-explain to Opus. + +--- + +## Watch How Agents Use the Skill + +Scope: After a skill has been in use + +Rule: Read transcripts of real tasks, not only eval outputs: + +- Files read in an unexpected order: the References table does not say clearly + when to read what. +- A reference file never opened: it is not needed, or its "Read when" is wrong. +- The same file opened on every task: its core belongs in `## Always`. +- A rule stated in the skill but not followed: give the reason, or move it + higher; do not add capitals. + +--- + +## Before Sharing + +- [ ] `validate-skills.ts` passes without errors +- [ ] The description says what and when, in the third person +- [ ] SKILL.md links every reference file with a "Read when" +- [ ] Every path, command and validator named in the skill exists +- [ ] Every example follows the other skills' rules +- [ ] Each rule says why it exists +- [ ] A baseline run failed an assertion that the with-skill run passes +- [ ] The description was checked against should-trigger and near-miss queries diff --git a/.agents/skills/eser-rules-manager/references/skill-format.md b/.agents/skills/eser-rules-manager/references/skill-format.md new file mode 100644 index 000000000..5c0d356af --- /dev/null +++ b/.agents/skills/eser-rules-manager/references/skill-format.md @@ -0,0 +1,280 @@ +# Skill Format + +How a skill in `.agents/skills/` is laid out and written. Based on Anthropic's +skill authoring guidance +(platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) and +the Claude Code skills reference, adjusted to this repository. + +## Contents + +- Layout +- Frontmatter +- SKILL.md Body +- Reference Files +- What Belongs in a Skill +- Rule Sections +- Scripts +- Validation +- Creating a Skill +- Changing a Skill + +--- + +## Layout + +``` +skill-name/ +├── SKILL.md # Required: overview and navigation, at most 80 lines +├── references/ # Topic files, each read only when its task needs it +│ ├── errors.md +│ └── logging.md +├── scripts/ # Optional: code the skill runs instead of re-deriving +└── evals/ # Optional: evals.json, triggers.json (skill-evaluation.md) +``` + +`.claude` is a symlink to `.agents`, so edit skills under `.agents/skills/` +only. Paths use forward slashes. + +**Why:** an agent sees only the name and description of every skill up front. It +reads SKILL.md when the skill loads and a reference file only when it opens it. +A skill costs context in proportion to what the task actually needs. + +--- + +## Frontmatter + +Scope: The YAML block at the top of SKILL.md + +Rule: Only `name` and `description` are required. The validator allows the +fields of the Agent Skills spec (`license`, `compatibility`, `metadata`, +`allowed-tools`) and two Claude Code fields that other agents can ignore safely: +`disable-model-invocation: true` for a command-style skill that only the user +starts (`/handoff`), and `argument-hint` for its argument. It rejects fields +that change when a skill loads, such as `paths` or `when_to_use`, because other +agents read the same files and would load it differently. + +A user-invoked skill's description is not shown to the model, so it needs no +"Use when" clause; write it for the person reading the `/` menu. + +- `name`: kebab-case, at most 64 characters, equal to the directory name, and + without the words "anthropic" or "claude". Existing names stay as they are: + AGENTS.md, `.agents/settings.json` and other skills refer to them. +- Quote every value in double quotes. An unquoted value containing `:` or `#` is + invalid YAML, and strict loaders such as `npx skills` skip the whole skill; + the validator reports it. +- `description`: third person, at most 1,024 characters, aiming for under 400. + First what the skill covers, then `Use when ...` with concrete triggers: + tasks, file types, commands, and phrasings that never name the topic. When a + nearby skill is the better fit for a near miss, add a "Not for X (use + other-skill)" clause. The description is the only thing an agent sees before + it decides to load the skill, so every "when" signal belongs there, not in the + body. + +Correct: + +```yaml +description: "Go conventions for pkg/ajan and the FFI bridge, covering errors, logfx logging, context, testing, data structures and performance. Use when writing or reviewing .go files, adding a Go business domain, or fixing golangci-lint findings. Not for Go module tags (use release-and-ci)." +``` + +Incorrect: + +```yaml +description: Helps with Go stuff # no scope, no triggers +``` + +--- + +## SKILL.md Body + +Scope: Everything below the frontmatter + +Rule: SKILL.md is an overview that sends the reader to the right file. Keep it +to at most 80 lines, in this order: + +1. `# Title`, then one or two sentences on scope and what the skill does not + cover. +2. `## Always`: 5 to 12 bullets that apply to every task in the domain. Only + project-specific rules, each with a short reason when the reason is not + obvious. +3. `## References`: a table that links every reference file directly and says + when to read it. + +```markdown +## References + +| File | Read when | +| ----------------------------------- | -------------------------------------------- | +| [errors.md](references/errors.md) | Writing error handling, messages or wrapping | +| [logging.md](references/logging.md) | Adding logs or changing log levels | +``` + +Loaded skill content stays in context for the rest of the session. A line that +does not change behavior costs tokens on every later turn. + +--- + +## Reference Files + +Scope: Files under `references/` + +Rule: + +- **One level deep.** SKILL.md links every reference file. An agent that follows + a link from one reference file to another tends to preview the second one + partially; links between reference files are fine as long as SKILL.md also + links the target. +- **Split by topic.** One file per task area (errors, logging, testing), named + for its content, kebab-case. A task should read one or two files, not the + whole skill. Aim for at most 400 lines per file; the validator warns past 500. +- **Contents list.** A file over 100 lines starts with `## Contents` after its + one-line scope, so a partial read still shows everything the file covers. + `validate-skills.ts --fix` writes and refreshes it. +- **Repository facts go in `AGENTS.md`.** Paths, package counts, generators and + the command list of this repository are loaded every session from `AGENTS.md`; + skills state conventions that hold in any repository on the toolchain, and + point to `AGENTS.md` for the concrete names. Keep that section short, since + every session pays for it. +- **One owner per topic.** A rule lives in exactly one skill. Other skills point + to it as `skill-name: Exact Section Title`; the validator checks that the + title exists. + +--- + +## What Belongs in a Skill + +Scope: Every rule and example + +Rule: Assume the reader is a strong model. Write down only what it would not do +by default in this repository: + +- project decisions and conventions that differ from common defaults; +- paths, commands, package names and validators that exist here; +- the reason behind each rule, so it carries over to cases the examples miss. + +Leave out textbook explanations, lists of benefits, API catalogues, and rules a +linter or formatter already enforces (name the tool in one line instead). Check +every path and command against the repository before writing it. Every example +must follow the other skills' rules too: an example that breaks another rule +teaches the break. + +Give one default rather than a menu of options, and add the exception that +changes it. Use one term per concept across a skill (for example always +"endpoint", never also "route" or "URL"). Do not write dates or "until version +X" into a rule. State the current rule, and put a replaced one under an "Old +patterns" note only if readers still meet it in the code. + +--- + +## Rule Sections + +Scope: `##` sections in reference files + +Rule: Each rule is one section, separated from the next by `---`: + +````markdown +## Rule Title + +Scope: where it applies + +Rule: the rule, in the imperative. + +Correct: + +```go +// example +``` + +Incorrect: + +```go +// counter-example +``` + +**Why:** the reason, in one or two sentences. +```` + +Keep capitalized MUST and NEVER for true invariants (security, data loss, commit +policy). An agent that knows the reason applies a rule better than one told to +obey it in capitals. Follow documentation-conventions: Plain Prose. + +Match specificity to fragility: + +- **Many valid approaches:** state the goal and the constraints in prose. +- **A preferred pattern:** give the pattern and say what may vary. +- **One safe sequence** (releases, migrations): give the exact commands and say + not to change them. + +A rule that agents break under pressure (skipping tests, committing unasked) may +add an anti-rationalization note: the excuse in quotes, then the answer. + +--- + +## Scripts + +Scope: `scripts/` in a skill + +Rule: When agents keep re-deriving the same steps (a validation, a generator, a +checklist run by hand), put the code in `scripts/` and tell the skill whether to +run it or read it. A script runs without loading into context and gives the same +result every time. + +Scripts use Node built-ins or the repo's own packages, carry the license header, +handle their own errors with messages that say what to fix, justify their +constants, and pass `deno task cli ok`. + +--- + +## Validation + +Validate after creating or changing a skill: + +```bash +node .agents/skills/eser-rules-manager/scripts/validate-skills.ts # all skills +node .agents/skills/eser-rules-manager/scripts/validate-skills.ts --fix # also rewrite Contents lists +``` + +Pass skill directories to check only those. Errors exit 1; warnings do not. + +| Check | Level | Requirement | +| -------------------- | ------- | ----------------------------------------------------- | +| Frontmatter | error | Present, only allowed keys | +| name | error | kebab-case, at most 64 chars, equals the directory | +| description | error | At most 1,024 chars, no `<` or `>`, has "Use when" | +| description length | warning | Aim for under 400 chars | +| SKILL.md length | error | At most 80 lines | +| SKILL.md links | error | Every relative link resolves | +| SKILL.md sections | warning | Has `## Always` and `## References` | +| Reference links | error | Every `references/*.md` is linked from SKILL.md | +| Contents list | error | Present and current in reference files over 100 lines | +| Reference length | warning | At most 500 lines | +| Cross-skill pointers | warning | `skill-name: Title` matches a heading in that skill | +| eser-rules-manager | error | Every skill is in both skill tables | + +--- + +## Creating a Skill + +1. Check whether an existing skill should own the topic. Add a new skill only + when no current scope fits. +2. Capture the intent from the conversation first: the steps taken, the owner's + corrections, the tools used. Ask only for the gaps: what the skill should + make an agent do, when it should load, what a good result looks like. +3. Write the evals before the rules (skill-evaluation.md), then write just + enough to pass them. +4. Create `SKILL.md` and the reference files in the format above. +5. Add the skill to the table in eser-rules-manager SKILL.md, to the trigger + table in skill-discovery.md, and to the skill map in AGENTS.md. +6. Validate, then run the behavior and trigger evals. + +--- + +## Changing a Skill + +1. Keep the name and directory. +2. For a change meant to alter behavior, snapshot the skill first + (`cp -r .agents/skills/ etc/temp/_skill-evals//snapshot/`) as the + eval baseline. +3. Change the existing rule rather than adding a near duplicate. When the rule + belongs to another skill, change it there. +4. Update `## Always` only when the change applies to every task in the domain. +5. Validate with `--fix`, then evaluate the change against the snapshot. diff --git a/.agents/skills/eser-rules-manager/scripts/validate-skills.ts b/.agents/skills/eser-rules-manager/scripts/validate-skills.ts new file mode 100644 index 000000000..49521188b --- /dev/null +++ b/.agents/skills/eser-rules-manager/scripts/validate-skills.ts @@ -0,0 +1,367 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +/** + * Validates every skill under `.agents/skills/` (or the skill directories + * given as arguments) against the format in references/skill-format.md, and + * checks that the eser-rules-manager tables list every skill. + * + * Uses only Node built-ins so it runs without an install: + * node .agents/skills/eser-rules-manager/scripts/validate-skills.ts + * deno run --allow-read .agents/skills/eser-rules-manager/scripts/validate-skills.ts + * + * `--fix` rewrites the `## Contents` list of every reference file longer than + * the table-of-contents threshold before validating. + * + * Exits 1 when any error is found. Warnings do not fail the run. + * + * @module + */ + +import { + existsSync, + readdirSync, + readFileSync, + statSync, + writeFileSync, +} from "node:fs"; +import { basename, dirname, join, resolve } from "node:path"; +import process from "node:process"; + +const MAX_SKILL_LINES = 80; +const MAX_DESCRIPTION = 1024; +// Every description sits in the context of every session; past this it +// usually repeats what the body says. +const TARGET_DESCRIPTION = 400; +// Anthropic's guidance: reference files over 100 lines start with a table of +// contents, so a partial read still shows everything the file covers. +const TOC_THRESHOLD = 100; +const TARGET_REFERENCE_LINES = 500; +const ALLOWED_KEYS = new Set([ + "name", + "description", + "license", + "allowed-tools", + "metadata", + "compatibility", + // Claude Code fields that other agents ignore without changing how the skill + // reads: a command-style skill the user starts, and its argument hint. + "disable-model-invocation", + "argument-hint", +]); + +type Finding = { skill: string; level: "error" | "warning"; message: string }; + +const skillsRoot = resolve(import.meta.dirname ?? ".", "../.."); + +/** Headings of the given levels outside fenced code blocks, in order. */ +const headingsAt = (text: string, prefix: RegExp): string[] => { + const headings: string[] = []; + let fence: string | null = null; + for (const line of text.split("\n")) { + const marker = line.match(/^\s*(`{3,}|~{3,})/); + if (marker !== null) { + if (fence === null) fence = marker[1]!; + else if (marker[1]!.startsWith(fence)) fence = null; + continue; + } + const match = fence === null ? line.match(prefix) : null; + if (match !== null) headings.push(line.slice(match[0].length).trim()); + } + return headings; +}; + +const sectionHeadings = (text: string): string[] => headingsAt(text, /^## /); + +const lineCount = (text: string): number => + text.replace(/\n$/, "").split("\n").length; + +const buildContents = (headings: readonly string[]): string => + `## Contents\n\n${ + headings.filter((h) => h !== "Contents").map((h) => `- ${h}`).join("\n") + }\n`; + +/** + * Inserts or refreshes the `## Contents` block, which sits after the H1 and + * its intro paragraph and ends at the next `---` or `## ` line. + */ +const withContents = (text: string): string => { + const headings = sectionHeadings(text); + const block = buildContents(headings); + const existing = text.match(/^## Contents\n[\s\S]*?(?=\n---\n|\n## )/m); + if (existing !== null) { + return text.replace(existing[0], block.replace(/\n$/, "")); + } + const firstSection = text.search(/^(---|## )/m); + if (firstSection < 0) return text; + return `${text.slice(0, firstSection)}${block}\n---\n\n${ + text.slice(firstSection).replace(/^---\n\n/, "") + }`; +}; + +const contentsMatches = (text: string): boolean => { + const block = text.match(/^## Contents\n([\s\S]*?)(?=\n---\n|\n## )/m); + if (block === null) return false; + const listed = [...block[1]!.matchAll(/^- (.+)$/gm)].map((m) => m[1]!.trim()); + const actual = sectionHeadings(text).filter((h) => h !== "Contents"); + return listed.length === actual.length && + listed.every((h, i) => h === actual[i]); +}; + +const referenceFiles = (dir: string): string[] => { + const refs = join(dir, "references"); + if (!existsSync(refs)) return []; + return readdirSync(refs).filter((f) => f.endsWith(".md")).sort(); +}; + +const parseFrontmatter = ( + text: string, +): Record | null => { + const match = text.match(/^---\n([\s\S]*?)\n---\n/); + if (match === null) return null; + const fields: Record = {}; + for (const line of match[1]!.split("\n")) { + const kv = line.match(/^([A-Za-z][\w-]*):\s*(.*)$/); + if (kv === null) continue; + fields[kv[1]!] = kv[2]!.replace(/^(["'])([\s\S]*)\1$/, "$2"); + } + return fields; +}; + +const validateSkill = (dir: string): Finding[] => { + const skill = basename(dir); + const findings: Finding[] = []; + const error = (message: string) => + findings.push({ skill, level: "error", message }); + const warn = (message: string) => + findings.push({ skill, level: "warning", message }); + + const skillFile = join(dir, "SKILL.md"); + if (!existsSync(skillFile)) { + error("SKILL.md not found"); + return findings; + } + const text = readFileSync(skillFile, "utf8"); + + const fm = parseFrontmatter(text); + if (fm === null) { + error("SKILL.md has no YAML frontmatter (--- name/description ---)"); + return findings; + } + + for (const line of text.split("\n").slice(1)) { + if (line === "---") break; + const kv = line.match(/^([A-Za-z][\w-]*):[ \t]+(.+)$/); + if (kv === null) continue; + const value = kv[2]!; + // An unquoted YAML scalar may not contain ": " or " #"; strict parsers + // (the skills CLI, the Agent Skills loader) reject the whole file. + if (!/^["'|>]/.test(value) && /: |:$| #/.test(value)) { + error( + `frontmatter "${ + kv[1] + }" is an unquoted value containing ": " or " #", which strict YAML parsers reject; wrap it in double quotes`, + ); + } + } + + for (const key of Object.keys(fm)) { + if (!ALLOWED_KEYS.has(key)) { + error( + `unexpected frontmatter key "${key}"; allowed: ${ + [...ALLOWED_KEYS].join(", ") + }`, + ); + } + } + + const name = fm["name"] ?? ""; + if (name === "") error("frontmatter is missing name"); + else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name) || name.length > 64) { + error(`name "${name}" must be kebab-case and at most 64 characters`); + } else if (name !== skill) { + error(`name "${name}" does not match the directory name "${skill}"`); + } + + const description = fm["description"] ?? ""; + if (description === "") error("frontmatter is missing description"); + else { + if (description.length > MAX_DESCRIPTION) { + error( + `description is ${description.length} characters; the limit is ${MAX_DESCRIPTION}`, + ); + } else if (description.length > TARGET_DESCRIPTION) { + warn( + `description is ${description.length} characters; aim for under ${TARGET_DESCRIPTION}`, + ); + } + if (/[<>]/.test(description)) { + error("description contains < or >, which some loaders reject"); + } + const userInvoked = /^(true|yes|on|1)$/i.test( + fm["disable-model-invocation"] ?? "", + ); + if (!userInvoked && !/\buse (it )?(when|for|before)\b/i.test(description)) { + error('description has no "Use when/for ..." trigger clause'); + } + } + + const lines = lineCount(text); + if (lines > MAX_SKILL_LINES) { + error( + `SKILL.md is ${lines} lines; the limit is ${MAX_SKILL_LINES}. Move detail to references/`, + ); + } + + for (const heading of ["## Always", "## References"]) { + if (!text.includes(`\n${heading}`)) warn(`SKILL.md has no "${heading}"`); + } + + for (const [, target] of text.matchAll(/\]\(([^)#\s]+)\)/g)) { + if (/^[a-z]+:/.test(target!)) continue; + if (!existsSync(join(dir, target!))) { + error(`SKILL.md links to ${target}, which does not exist`); + } + } + + for (const file of referenceFiles(dir)) { + const ref = `references/${file}`; + if (!text.includes(`](${ref})`)) { + error( + `${ref} is not linked from SKILL.md; link every reference file directly`, + ); + } + const body = readFileSync(join(dir, ref), "utf8"); + const refLines = lineCount(body); + if (refLines > TOC_THRESHOLD && !contentsMatches(body)) { + error( + `${ref} is ${refLines} lines and its "## Contents" list is missing or out of date; run with --fix`, + ); + } + if (refLines > TARGET_REFERENCE_LINES) { + warn( + `${ref} is ${refLines} lines; consider splitting it by topic (aim for under ${TARGET_REFERENCE_LINES})`, + ); + } + } + + return findings; +}; + +/** + * Cross-skill pointers are written as `skill-name: Section Title`. Each one + * must name a skill that exists and start with one of its section headings. + */ +const validateCrossReferences = (dirs: readonly string[]): Finding[] => { + const findings: Finding[] = []; + const headingsBySkill = new Map(); + for (const dir of dirs) { + const files = [ + "SKILL.md", + ...referenceFiles(dir).map((f) => `references/${f}`), + ]; + headingsBySkill.set( + basename(dir), + files.flatMap((f) => + headingsAt(readFileSync(join(dir, f), "utf8"), /^#{2,4} /) + ), + ); + } + const names = [...headingsBySkill.keys()].join("|"); + const pointer = new RegExp(`\\b(${names}): ([A-Za-z\`][^\\n]*)`, "g"); + for (const dir of dirs) { + const skill = basename(dir); + const files = [ + "SKILL.md", + ...referenceFiles(dir).map((f) => `references/${f}`), + ]; + for (const file of files) { + const text = readFileSync(join(dir, file), "utf8").replace( + /[ \t]*\n(?!\n)[ \t]*/g, + " ", + ); + for (const [, target, rest] of text.matchAll(pointer)) { + const headings = headingsBySkill.get(target!) ?? []; + if (!headings.some((h) => rest!.startsWith(h))) { + findings.push({ + skill, + level: "warning", + message: `${file} points to "${target}: ${ + rest!.slice(0, 50) + }", which matches no section heading in ${target}`, + }); + } + } + } + } + return findings; +}; + +const fixContents = (dirs: readonly string[]): void => { + for (const dir of dirs) { + for (const file of referenceFiles(dir)) { + const path = join(dir, "references", file); + const text = readFileSync(path, "utf8"); + if (lineCount(text) <= TOC_THRESHOLD || contentsMatches(text)) continue; + writeFileSync(path, withContents(text)); + console.log(`updated contents: ${basename(dir)}/references/${file}`); + } + } +}; + +/** Every skill must appear in both eser-rules-manager tables. */ +const validateInventory = (skills: readonly string[]): Finding[] => { + const findings: Finding[] = []; + const manager = join(skillsRoot, "eser-rules-manager"); + const tables: Array<[string, string]> = [ + ["SKILL.md", "Available Skills table"], + ["references/skill-discovery.md", "Skill Trigger Keywords table"], + ]; + for (const [file, label] of tables) { + const path = join(manager, file); + if (!existsSync(path)) continue; + const text = readFileSync(path, "utf8"); + for (const skill of skills) { + if (skill === "eser-rules-manager") continue; + if (!text.includes(`| \`${skill}\``)) { + findings.push({ + skill, + level: "error", + message: `missing from the ${label} in eser-rules-manager/${file}`, + }); + } + } + } + return findings; +}; + +const main = (): number => { + const fix = process.argv.includes("--fix"); + const args = process.argv.slice(2).filter((a) => a !== "--fix"); + const dirs = args.length > 0 + ? args.map((a) => resolve(a)) + : readdirSync(skillsRoot) + .map((d) => join(skillsRoot, d)) + .filter((d) => statSync(d).isDirectory()); + + if (fix) fixContents(dirs); + + const findings = dirs.flatMap(validateSkill); + if (args.length === 0) { + findings.push(...validateInventory(dirs.map((d) => basename(d)))); + findings.push(...validateCrossReferences(dirs)); + } + + for (const f of findings) { + console.log(`${f.level === "error" ? "✗" : "!"} ${f.skill}: ${f.message}`); + } + const errors = findings.filter((f) => f.level === "error").length; + const warnings = findings.length - errors; + console.log( + `${dirs.length} skill(s) checked in ${ + args.length > 0 ? dirname(dirs[0]!) : skillsRoot + }: ${errors} error(s), ${warnings} warning(s)`, + ); + return errors > 0 ? 1 : 0; +}; + +process.exit(main()); diff --git a/.agents/skills/go-practices/SKILL.md b/.agents/skills/go-practices/SKILL.md new file mode 100644 index 000000000..0a746e262 --- /dev/null +++ b/.agents/skills/go-practices/SKILL.md @@ -0,0 +1,49 @@ +--- +name: go-practices +description: "Go conventions for eserstack's ajan framework, services and FFI bridge. Covers package layout, hexagonal wiring, errors and logfx logging, context, tests, idioms, data structures, performance and Go 1.26 stdlib use. Use when writing or reviewing .go files, pkg/ajan packages, pkg/@eserstack/ajan, Go tests, benchmarks, profiling or allocation work, or fixing golangci-lint findings." +--- + +# Go Practices + +How Go code is written in this repository: one root module +`github.com/eser/stack` (framework in `pkg/ajan/`, binary in +`cmd/noskills-server/`) plus the FFI bridge module `github.com/eser/ajan` in +`pkg/@eserstack/ajan/`. + +## Always + +- Business packages under `pkg/ajan/api/business/` import only the standard + library; wiring happens in `pkg/ajan/api/adapters/appcontext/appcontext.go`. +- Files are snake_case. +- Check every error and wrap with a context prefix: + `fmt.Errorf("load session %q: %w", id, err)`. Lowercase messages, `%q` for + user input, `errors.Is` and `errors.AsType`, handle each error once. +- Log and trace through `logfx` with the `*Context` methods; never `fmt.Println` + or `log.Printf`. +- `ctx context.Context` first; pass the caller's context down and cancel every + context you derive. +- Pass by value unless the function mutates, nil means something, the type must + not be copied, or it is 80+ bytes (gocritic `hugeParam`). +- Dependencies arrive through constructors: no globals, no service locator, no + `init()`; functional options that validate; enums start at an Unknown. +- `defer Close()` on the line after a successful open; `panic` only for bugs. +- Use the standard library up to the `go 1.26` directive, nothing newer. +- Tests are table-driven with `t.Parallel()` and run with `-race`. +- `make ok` checks only the root module. `deno task cli ok` also vets, builds + and lints the bridge module, since `./...` never crosses a module boundary. + +## References + +| File | Read when | +| --------------------------------------------------------- | -------------------------------------------------------------------- | +| [architecture.md](references/architecture.md) | Adding a package or domain, dependency injection, constructors | +| [errors-and-logging.md](references/errors-and-logging.md) | Returning or matching errors, logging, spans, metrics | +| [context.md](references/context.md) | Passing, deriving or detaching a context; handlers; outbound calls | +| [language.md](references/language.md) | Strings, types, generics, parameters, enums, iterators, JSON, regexp | +| [testing.md](references/testing.md) | Writing Go tests, fakes for ports, or benchmarks | +| [data-structures.md](references/data-structures.md) | Choosing slices, maps, containers, buffers or generic types | +| [performance.md](references/performance.md) | Profiling, benchmarks, allocations, caching, I/O and batching | +| [modernization.md](references/modernization.md) | Replacing an old idiom, raising the `go` directive, Go tooling | + +General rules (errors, logging, timeouts, config, interfaces) live in +coding-practices and design-principles; commands in AGENTS.md. diff --git a/.agents/skills/go-practices/evals/triggers.json b/.agents/skills/go-practices/evals/triggers.json new file mode 100644 index 000000000..1dab45e1b --- /dev/null +++ b/.agents/skills/go-practices/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "add a Go service for notifications under pkg/ajan", + "should_trigger": true + }, + { + "query": "golangci-lint says hugeParam on this function", + "should_trigger": true + }, + { + "query": "this goroutine leaks when the request is cancelled", + "should_trigger": true + }, + { + "query": "write table-driven tests for the parser in formatfx", + "should_trigger": true + }, + { + "query": "should I use errors.As or errors.AsType here", + "should_trigger": true + }, + { + "query": "the FFI bridge in pkg/@eserstack/ajan needs a new export", + "should_trigger": true + }, + { + "query": "bu Go kodunda context'i doğru mu geçiriyorum", + "should_trigger": true + }, + { + "query": "profile the httpfx handler and reduce allocations", + "should_trigger": true + }, + { + "query": "bump the Go module tag for ajan", + "should_trigger": false + }, + { + "query": "write a TS function", + "should_trigger": false + }, + { + "query": "the CI Go job times out", + "should_trigger": false + }, + { + "query": "add a new npm package", + "should_trigger": false + }, + { + "query": "where do Go files go in the repo layout overview", + "should_trigger": false + }, + { + "query": "write the release notes", + "should_trigger": false + }, + { + "query": "ask before designing the notification feature", + "should_trigger": false + }, + { + "query": "our README for ajan is outdated", + "should_trigger": false + } +] diff --git a/.agents/skills/go-practices/references/architecture.md b/.agents/skills/go-practices/references/architecture.md new file mode 100644 index 000000000..24da57b09 --- /dev/null +++ b/.agents/skills/go-practices/references/architecture.md @@ -0,0 +1,262 @@ +# Go Architecture and Package Structure + +Where Go code lives in this repository, how it is wired, and how packages are +constructed. The principle is architecture-guidelines: Hexagonal Architecture +(Double-Layered); this file is its Go form. + +## Contents + +- Repository Layout +- Hexagonal Layers in Go +- Dependency Injection +- Adding a Business Domain +- File Naming +- Constructors and Options +- No `init()` +- Compile-Time Interface Checks +- Embedded Assets + +--- + +## Repository Layout + +Scope: All Go code + +Rule: The repository is one Go module, `github.com/eser/stack`, with `go.mod`, +`Makefile` and `.golangci.yaml` at the root. The cgo and WASI FFI bridge in +`pkg/@eserstack/ajan/` is a second module, `github.com/eser/ajan`, with its own +`go.mod`. There is deliberately no `go.work`; `deno task cli ok` checks each +module in its own directory. + +| Path | Holds | +| ----------------------------------- | --------------------------------------------------- | +| `pkg/ajan/fx/` | Framework packages (`logfx`, `httpfx`, `connfx`) | +| `pkg/ajan/api/business//` | Business logic and its ports | +| `pkg/ajan/api/adapters/` | Adapters that implement the ports | +| `pkg/ajan/api/adapters/appcontext/` | The composition root (`appcontext.go`) | +| `cmd/noskills-server/` | The server binary's entry point | +| `pkg/@eserstack/ajan/` | The FFI and WASM bridge consumed by the TS packages | + +Do not create `internal/`, `configs/` or `test/` trees. Framework packages are +imported from `pkg/ajan/`, never copied into a service. + +--- + +## Hexagonal Layers in Go + +Scope: Business logic and adapters under `pkg/ajan/api/` + +Rule: + +- A business package imports only the standard library and other business + packages. It defines its ports as interfaces next to the code that uses them. +- Adapters implement those ports with concrete dependencies (pgx, HTTP, Redis, + LLM providers). +- All wiring happens in the composition root, + `pkg/ajan/api/adapters/appcontext/appcontext.go`: it loads config, builds the + logger, creates adapters and hands them to business services. + +Correct: + +```go +// pkg/ajan/api/business/sessions/service.go +package sessions + +type Store interface { // port, owned by the business package + Get(ctx context.Context, id string) (*Session, error) +} + +type Service struct { + store Store +} + +func NewService(store Store) *Service { + return &Service{store: store} +} +``` + +Incorrect: + +```go +package sessions + +import "github.com/jackc/pgx/v5/pgxpool" // business code bound to a driver + +type Service struct { + pool *pgxpool.Pool +} +``` + +--- + +## Dependency Injection + +Scope: Services, adapters and the composition root + +Rule: A type receives everything it depends on through its constructor. The +constructor stores what it is given and returns a concrete type (a `*Service`, +not an interface); the parameters are the consumer's own port interfaces +(Hexagonal Layers in Go). + +- No hidden dependencies: a constructor does not open connections, read the + environment or reach for a package-level variable. What it needs is a + parameter, so the signature is the whole dependency list. +- No global registries or service locators. The composition root + (`appcontext.go`) is the only code that knows every service; it is never + passed on, and no service looks another one up by name. +- Stateful resources (the pgx pool, HTTP clients, caches, the logger) are + created once in the composition root and shared. Stateless helpers are plain + values or functions. +- Build everything at start-up, so a bad config or an unreachable dependency + fails before the process takes traffic. Defer creation (`sync.OnceValues`) + only for an optional adapter that is expensive and often unused, and say so in + a comment. +- Keep the graph shallow: a service depends on ports and config, not on a chain + of other services. A constructor with many parameters or a long chain is a + sign that the service does too much; split it before adding a container. +- Wiring stays manual. A DI container or code generator is a new dependency and + an architecture change: it needs tooling-standards: Adding a Dependency and an + ADR first. + +Correct: + +```go +// pkg/ajan/api/business/sessions/service.go +// Business code stays on the standard library: *slog.Logger, not *logfx.Logger. +func NewService(store Store, clock func() time.Time, logger *slog.Logger) *Service { + return &Service{store: store, clock: clock, logger: logger} +} + +// pkg/ajan/api/adapters/appcontext/appcontext.go +// Connections come from the connfx registry, loaded from cfg.Conn. +conn, ok := connRegistry.GetNamed("sessions").(*connfx.PgxConnection) +if !ok { + return nil, fmt.Errorf("%w (name=%q)", ErrSessionsConnectionMissing, "sessions") +} +store := postgres.NewSessionStore(conn.GetPool()) +sessionSvc := sessions.NewService(store, time.Now, logger.Logger) // logfx embeds *slog.Logger +``` + +Incorrect: + +```go +func NewService() *Service { + pool, _ := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) // hidden, unchecked + return &Service{pool: pool} +} + +func (s *Service) notify(ctx context.Context, id string) error { + mailer := registry.Get("mailer").(Mailer) // service locator + return mailer.Send(ctx, id) +} +``` + +**Why:** when every dependency is a constructor parameter, a test swaps any of +them for a fake, the wiring order is checked by the compiler, and a reader sees +what a service touches without reading its body. + +--- + +## Adding a Business Domain + +Scope: New business logic in the Go services + +Rule: + +1. Create `pkg/ajan/api/business//`. +2. Define ports (interfaces) for everything the domain needs from outside. +3. Write the logic against those ports, importing only the standard library. +4. Implement the ports in `pkg/ajan/api/adapters//`. +5. Wire them in `pkg/ajan/api/adapters/appcontext/appcontext.go`. +6. Validate with `deno task cli go-ok` (runs `make ok` on the root module). + +--- + +## File Naming + +Scope: Go files + +Rule: Go files are snake_case (`session_store.go`, `http_service.go`, +`logfx_coverage_test.go`). The repository validator enforces kebab-case +elsewhere and snake_case under `cmd/`, `pkg/ajan/` and `pkg/@eserstack/ajan/`. + +--- + +## Constructors and Options + +Scope: Constructors and functions with optional settings + +Rule: Give a constructor of a long-lived type (a client, a server, a pool) +functional options when it has several optional settings: one `With...` function +per setting, applied over safe defaults. New options then never break existing +callers. An option that can be invalid returns an error, and the constructor +reports it, so bad configuration fails at construction instead of at first use. +Settings loaded from config use a `Config` struct with `conf` tags instead (as +`httpclient.Config` does); a single call with many parameters takes an options +struct (coding-practices: Line Length and Breaking). + +Correct: + +```go +type Option func(*Client) error + +func WithTimeout(d time.Duration) Option { + return func(c *Client) error { + if d <= 0 { + return fmt.Errorf("timeout %s must be positive", d) + } + c.timeout = d + return nil + } +} + +func NewClient(baseURL string, opts ...Option) (*Client, error) { + c := &Client{baseURL: baseURL, timeout: 10 * time.Second} + for _, opt := range opts { + if err := opt(c); err != nil { + return nil, fmt.Errorf("new client: %w", err) + } + } + return c, nil +} +``` + +Incorrect: + +```go +func NewClient(baseURL string, timeout time.Duration, retries int, verbose bool) *Client +// every new setting changes the signature; a bad timeout surfaces later +``` + +--- + +## No `init()` + +Scope: All Go code + +Rule: Do not use `init()`. It runs implicitly at import time, cannot return an +error, and runs in every test that imports the package. Do the work in an +explicit constructor or setup function that the composition root calls and whose +error it handles. A package-level `var` with a constant value (a compiled +constant regexp, a lookup table) is fine; see design-principles: Module-Level +Side Effects. + +--- + +## Compile-Time Interface Checks + +Scope: Types that must satisfy an interface they are not passed as directly + +Rule: Assert the relationship next to the type, so a changed method signature +fails the build in the implementing package instead of at a distant call site: +`var _ ConfigLoader = (*ConfigManager)(nil)` (`configfx/manager.go`). + +--- + +## Embedded Assets + +Scope: Templates, schemas, default configs and other files a binary ships with + +Rule: Embed static files with `//go:embed` (as `noskillsserverfx/docs_embed.go` +does) instead of reading them from disk at runtime. The path then cannot be +wrong on another machine, and a missing file is a build error. diff --git a/.agents/skills/go-practices/references/context.md b/.agents/skills/go-practices/references/context.md new file mode 100644 index 000000000..930510465 --- /dev/null +++ b/.agents/skills/go-practices/references/context.md @@ -0,0 +1,202 @@ +# context.Context + +`context.Context` carries cancellation, deadlines and request-scoped values +through a unit of work: one HTTP request, one daemon RPC, one CLI command. Every +operation that belongs to that unit receives the same context, so when the +caller gives up, everything it started stops. The general principle is +design-principles: Cancellation Flows With the Work; this file is its Go form. + +## Contents + +- Summary +- Cancellation +- Listening for Cancellation +- HTTP Handlers +- Outbound Calls and Databases +- Context Values +- Trace Propagation + +--- + +## Summary + +1. Pass the caller's context through the whole chain: handler, service, + repository, outbound client. A link that starts a fresh context keeps working + after the client has gone. +2. `ctx context.Context` is the first parameter. Do not store it in a struct: a + struct outlives the request that filled it. +3. Never pass `nil`; use `context.TODO()` where the caller has no context yet, + which marks the gap to fix. +4. Call the `cancel` of every derived context on every path, normally with + `defer cancel()` on the next line (`go vet` reports `lostcancel`). +5. `context.Background()` only at entry points: `main`, the composition root, + and tests through `t.Context()`. +6. Context values carry request-scoped metadata under unexported key types, + never business parameters. +7. Work that must outlive the request uses `context.WithoutCancel` plus its own + timeout. + +--- + +## Cancellation + +For a group of goroutines, `errgroup.WithContext` gives each one the same +derived context and cancels it on the first error, which also satisfies +coding-practices: Owned Asynchronous Work. + +```go +func (s *SyncService) SyncAll(ctx context.Context, projects []Project) error { + g, ctx := errgroup.WithContext(ctx) + g.SetLimit(4) // bound concurrency against the remote + + for _, p := range projects { + g.Go(func() error { return s.syncOne(ctx, p) }) + } + + if err := g.Wait(); err != nil { + return fmt.Errorf("sync projects: %w", err) + } + return nil +} +``` + +--- + +## Listening for Cancellation + +A loop that waits on channels selects on `ctx.Done()` and returns `ctx.Err()`. A +CPU-bound loop checks `ctx.Err()` between items and says how far it got +(`stopped after %d of %d files: %w`). A timeout set low in the chain is only a +ceiling: a child context never outlives its parent's deadline. For cleanup when +the context ends, use `context.AfterFunc` and call the returned `stop` when it +is no longer needed. + +--- + +## HTTP Handlers + +An `httpfx` handler reads the request context from `ctx.Request.Context()`; it +ends when the client disconnects or the handler returns. Handlers never create +`context.Background()`. When the work failed because the client left, there is +nobody to answer, so do not log it as a server error. + +Work that must finish after the response (an audit entry, a notification) uses +`context.WithoutCancel`, which keeps the trace and request ids and drops the +cancellation and the deadline. Give it its own timeout and run it through an +owner that reports failures instead of a bare `go` (coding-practices: Owned +Asynchronous Work): + +```go +func (h *SpecHandler) Create(ctx *httpfx.Context) httpfx.Result { + reqCtx := ctx.Request.Context() + + spec, err := h.specs.Create(reqCtx, readSpecRequest(ctx)) + if err != nil { + if reqCtx.Err() != nil { + return ctx.Results.Abort() // client gone; no response to write + } + return h.errorResult(ctx, err) + } + + // The audit entry must be written even if the client disconnects now. + h.background.Run("audit spec created", func() error { + auditCtx, cancel := context.WithTimeout(context.WithoutCancel(reqCtx), 10*time.Second) + defer cancel() + return h.audit.SpecCreated(auditCtx, spec) + }) + + return ctx.Results.JSON(spec) +} +``` + +`context.Background()` for the audit would lose the trace id; `reqCtx` alone +would cancel the write as soon as the handler returns. + +--- + +## Outbound Calls and Databases + +Every call that leaves the process takes the context, so a disconnect cancels +the whole downstream chain and the call is bounded (coding-practices: External +Calls Have Timeouts). + +- HTTP: `http.NewRequestWithContext(ctx, ...)`, never `http.NewRequest` or + `http.Get`. +- `database/sql`: the `*Context` variants (`QueryContext`, `ExecContext`, + `QueryRowContext`). pgx methods take `ctx` first already. +- Subprocesses: `exec.CommandContext(ctx, ...)`. + +Correct: + +```go +func (c *RegistryClient) FetchIndex(ctx context.Context) (*Index, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/index.json", nil) + if err != nil { + return nil, fmt.Errorf("build index request: %w", err) + } + + resp, err := c.http.Do(req) + if err != nil { + return nil, fmt.Errorf("fetch index: %w", err) + } + defer resp.Body.Close() + + return decodeIndex(resp.Body) +} +``` + +Incorrect: + +```go +resp, err := http.Get(c.baseURL + "/index.json") // ignores cancellation and deadline +row := db.QueryRow("SELECT ...", id) // same for the query +``` + +--- + +## Context Values + +Context values carry request-scoped metadata that crosses API boundaries: +request and correlation ids, the authenticated principal, trace state. Keys are +an unexported type, and the package that owns a key exposes typed accessors: + +```go +type contextKey int + +const requestIDKey contextKey = iota + +func WithRequestID(ctx context.Context, id string) context.Context { + return context.WithValue(ctx, requestIDKey, id) +} + +func RequestIDFrom(ctx context.Context) (string, bool) { + id, ok := ctx.Value(requestIDKey).(string) + return id, ok +} +``` + +Incorrect: + +```go +ctx = context.WithValue(ctx, "request_id", id) // any package can collide with "request_id" +``` + +| Data | Context value? | +| --------------------------------------- | ------------------------------------------ | +| Request, correlation, trace, span ids | Yes | +| Authenticated principal or tenant | Yes | +| Database pool, HTTP client, stores | No: inject through the composition root | +| Configuration and feature flags | No: pass explicitly or inject | +| Business inputs (session id, spec data) | No: function parameters | +| Logger | Only a logger enriched with request fields | + +--- + +## Trace Propagation + +Trace state crosses service boundaries in the context. Use `logfx`'s propagator +(`logger.PropagatorExtract` in, `logger.PropagatorInject` out, W3C +`traceparent`) as errors-and-logging.md: Tracing and Metrics describes, instead +of copying ids into custom headers by hand. A request id that is not part of the +trace follows the context-value pattern above: read it in middleware, store it +with a typed accessor, and write it on outbound requests. diff --git a/.agents/skills/go-practices/references/data-structures.md b/.agents/skills/go-practices/references/data-structures.md new file mode 100644 index 000000000..550cfa829 --- /dev/null +++ b/.agents/skills/go-practices/references/data-structures.md @@ -0,0 +1,165 @@ +# Go Data Structures + +Which structure to use and the traps that come with slices, maps, buffers and +generic containers. The principle is design-principles: Choose Data Structures +by Access Pattern; performance claims follow coding-practices: Performance +Claims Need Measurement. + +## Contents + +- Summary +- Clone at the Boundary +- Maps +- Containers and Buffers +- Generic Containers +- `unsafe`, `weak` and `unique` +- Third-Party Collections +- Common Mistakes + +--- + +## Summary + +1. Preallocate when the size is known or estimable: `make([]T, n)` with indexed + writes for an exact size, `make([]T, 0, n)` or `slices.Grow` for an estimate, + `make(map[K]V, n)` for maps. The `prealloc` linter flags the obvious cases. +2. Arrays only for sizes fixed by the problem (`type Digest [32]byte`); slices + for everything that grows or comes from input. +3. Do not depend on when `append` reallocates; the growth policy is a runtime + detail. +4. Slices and maps share their data on assignment. Clone at the boundary where + independence matters. +5. A plain slice beats `container/list` and `container/ring` for most workloads; + use `container/heap` for priority queues. +6. `strings.Builder` to build a string; `bytes.Buffer` when the same bytes are + also read or reused. +7. Generic containers take the tightest constraint that works: `comparable` for + keys, `cmp.Ordered` for ordering, a method interface for domain behavior. +8. Avoid `unsafe` outside the FFI bridge. + +--- + +## Clone at the Boundary + +A function that returns internal state returns a copy, so a caller cannot change +it behind the owner's back. A struct holding a slice or map is copied shallowly: +both copies still share that slice or map. + +```go +func (r *Registry) IDs() []string { + r.mu.RLock() + defer r.mu.RUnlock() + return slices.Clone(r.ids) // callers get their own slice +} +``` + +When a sub-slice is handed out and must not be appended into (appending could +overwrite the owner's next elements), cap it with a full slice expression: +`head := s[:n:n]`. + +--- + +## Maps + +- Iteration order is unspecified; sort the keys when order matters + (`slices.Sorted(maps.Keys(m))`). +- Reading a map value copies it. For large values store pointers (`map[K]*V`); + for small values a value map is usually faster, since each pointer adds GC + work (language.md: Value vs Pointer Parameters). +- Maps do not shrink after deletes. Rebuild a long-lived map that has shrunk for + good. +- Go 1.24 changed the map implementation; do not rely on its internals. + +--- + +## Containers and Buffers + +`container/list` and `container/ring` store `any`; wrap them in a small generic +type rather than asserting at every use (language.md: Types: Narrow Conversions +and Generics). A fixed-size rolling window is a slice and an index, fully typed: + +```go +type Window struct { + values []float64 + next int + filled bool +} + +func (w *Window) Add(v float64) { + w.values[w.next] = v + w.next = (w.next + 1) % len(w.values) + w.filled = w.filled || w.next == 0 +} +``` + +- `bufio.Writer` holds data until `Flush`; call it and check its error, or the + tail of the output is lost. +- `bufio.Scanner` stops at tokens over 64 KiB. Set `scanner.Buffer` when lines + can be long, and check `scanner.Err()` after the loop. + +--- + +## Generic Containers + +```go +type Set[T comparable] map[T]struct{} + +func NewSet[T comparable](values ...T) Set[T] { + s := make(Set[T], len(values)) + for _, v := range values { + s[v] = struct{}{} + } + return s +} + +func (s Set[T]) Add(v T) { s[v] = struct{}{} } +func (s Set[T]) Contains(v T) bool { _, ok := s[v]; return ok } + +func InsertSorted[T cmp.Ordered](s []T, v T) []T { + i, _ := slices.BinarySearch(s, v) + return slices.Insert(s, i, v) +} +``` + +A type parameter earns its place when the same logic serves several types. When +a function has one concrete type, or the body type-switches on the parameter, +write it for that type or use an interface. + +--- + +## `unsafe`, `weak` and `unique` + +- `unsafe` is for the FFI bridge (`pkg/@eserstack/ajan`) and memory layout only. + Never keep a pointer in a `uintptr` across statements; use `unsafe.Add`, + `unsafe.Slice`, `unsafe.String` and `unsafe.SliceData`, which keep the value + visible to the collector. +- `unique.Make` (Go 1.23+) canonicalizes comparable values. +- `weak.Pointer` (Go 1.24+) is only for a cache whose entries may disappear; + pair it with `runtime.AddCleanup`, not `runtime.SetFinalizer`. + +--- + +## Third-Party Collections + +The standard library plus a small generic type covers sets, queues, heaps and +windows. A collection library is a new dependency and goes through +tooling-standards: Adding a Dependency first. + +--- + +## Common Mistakes + +| Mistake | Fix | +| --------------------------------------------------- | ---------------------------------------------------- | +| Appending in a loop with a known final size | `make([]T, 0, n)` or `slices.Grow` | +| Handing out an internal slice or map | Return `slices.Clone` or `maps.Clone` | +| Appending into a sub-slice that aliases another | Cap the view with `s[:n:n]` | +| Relying on map iteration order | Sort the keys | +| `container/list` where a slice would do | Use a slice; measure before switching | +| `bytes.Buffer` only to build a string | `strings.Builder` | +| Forgetting `Flush` on a `bufio.Writer` | Flush and return its error | +| Keeping a small sub-slice or substring of big input | `bytes.Clone`, `strings.Clone`, so the rest is freed | +| Keeping a pointer in a `uintptr` between statements | `unsafe.Add` in one expression, or avoid `unsafe` | + +Allocation and layout work for hot paths (reuse, pooling, field order) is in +[performance.md](performance.md). diff --git a/.agents/skills/go-practices/references/errors-and-logging.md b/.agents/skills/go-practices/references/errors-and-logging.md new file mode 100644 index 000000000..188baf58f --- /dev/null +++ b/.agents/skills/go-practices/references/errors-and-logging.md @@ -0,0 +1,154 @@ +# Go Errors, Logging and Tracing + +How Go code in this repository returns errors, logs through `logfx`, and records +spans. The general rules are in coding-practices (Error Wrapping, Handle Each +Error Once, Structured Logging); this file is their Go form. + +## Contents + +- Error Handling +- Logging Conventions +- Tracing and Metrics + +--- + +## Error Handling + +Scope: All Go code + +Rule: + +- Check every returned error; never discard one with `_`. +- Wrap with a context prefix and `%w` inside the module + (`fmt.Errorf("load session %q: %w", id, err)`), so callers can match the + cause. At a system boundary (a public API whose internals callers must not + depend on, a response to another service or a user) use `%v` or translate the + error, so the chain does not leak implementation details. +- Error strings are lowercase and have no trailing punctuation, because they are + joined into longer messages (`load session: open file: no such file`). This + applies to error values, not to text shown to users. Quote user-supplied + strings with `%q`, so an empty or odd value stays visible. +- Use a sentinel error (`var ErrSessionNotFound = errors.New(...)`) for an + expected condition callers branch on, and a custom error type when the error + carries data. Define both in the business package. When a caller must match a + sentinel and also needs the cause, join them: + `fmt.Errorf("%w: %w", ErrSessionNotFound, err)`. +- Match with `errors.Is` for sentinels and `errors.AsType[T](err)` (Go 1.26+) + for types. Never compare with `==` or use a bare type assertion on a wrapped + error. +- Combine independent failures (cleanup after an error, a batch) with + `errors.Join`. +- Return an error for anything a caller can expect or handle (bad input, missing + file, network failure). `panic` is only for a bug that should never happen, + such as an impossible state or a `Must*` helper on a constant. + +Correct: + +```go +var ErrSessionNotFound = errors.New("session not found") + +type ValidationError struct { + Field string + Value string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("invalid %s %q", e.Field, e.Value) +} + +func (s *SessionService) Load(ctx context.Context, id string) (*Session, error) { + session, err := s.store.Get(ctx, id) + if err != nil { + return nil, fmt.Errorf("load session %q: %w", id, err) + } + return session, nil +} + +if errors.Is(err, ErrSessionNotFound) { /* 404 */ } +if verr, ok := errors.AsType[*ValidationError](err); ok { /* 400 with verr.Field */ } + +return errors.Join(saveErr, closeErr) +``` + +Incorrect: + +```go +session, _ := s.store.Get(ctx, id) // error discarded +return errors.New("Failed to load session.") // capitalized, punctuated, cause lost +if err == ErrSessionNotFound { } // misses a wrapped sentinel +if verr, ok := err.(*ValidationError); ok {} // misses a wrapped type +``` + +--- + +## Logging Conventions + +Scope: All logging statements + +Rule: Log through `logfx` (`pkg/ajan/logfx`), which wraps `log/slog`, with +key-value pairs or typed `slog` attributes. Never use `fmt.Println`, +`log.Printf` or a third-party logger. Use the `*Context` methods (`InfoContext`, +`WarnContext`, `TraceContext`) so trace and request ids travel with the entry. +`logfx` adds `LevelTrace`, `LevelFatal` and `LevelPanic` around slog's Debug, +Info, Warn and Error. + +- HTTP servers log each request once through + `httpfx/middlewares.TracingMiddleware` (method, path, status, duration); + handlers do not log the request again. +- An error is logged once, where it is handled (coding-practices: Handle Each + Error Once). +- What never to log: security-practices: Secrets and Personal Data Never Reach + Output. +- In hot loops, see performance.md: Logging in Hot Paths. + +Correct: + +```go +logger.WarnContext(ctx, "session expired", slog.String("session_id", id)) +``` + +Incorrect: + +```go +log.Printf("session %s expired", id) // unstructured, no trace id +``` + +--- + +## Tracing and Metrics + +Scope: Spans and metrics in Go services + +Rule: Create spans and metrics through `logfx`, which holds the OpenTelemetry +providers, not through the global `otel` package: + +- Spans: `ctx, span := logger.StartSpan(ctx, "LoadSession", slog.String(...))`, + then `defer span.End()`, and `span.RecordError(err)` on failure. Pass the + returned `ctx` on, so child spans and log entries join the trace. +- Propagation across services: `logger.PropagatorExtract(ctx, r.Header)` on the + way in and `logger.PropagatorInject(ctx, req.Header)` on the way out (W3C + trace context). `TracingMiddleware` already extracts for HTTP servers. +- Metrics: `logger.NewMetricsBuilder("")`, then `Counter`, `Gauge` or + `Histogram`. +- Attributes carry ids and outcomes, never secrets or personal data. +- Spans live in adapters and handlers, which may import `logfx`. Business + packages stay on the standard library: they receive the traced `ctx` and a + `*slog.Logger`, and a business operation that needs its own span takes a small + tracer port instead of `*logfx.Logger`. + +Correct: + +```go +// pkg/ajan/api/adapters/postgres/session_store.go +func (s *SessionStore) Get(ctx context.Context, id string) (*sessions.Session, error) { + ctx, span := s.logger.StartSpan(ctx, "SessionStore.Get", slog.String("session_id", id)) + defer span.End() + + session, err := s.query(ctx, id) + if err != nil { + span.RecordError(err) + return nil, fmt.Errorf("get session %q: %w", id, err) + } + return session, nil +} +``` diff --git a/.agents/skills/go-practices/references/language.md b/.agents/skills/go-practices/references/language.md new file mode 100644 index 000000000..8c55f024f --- /dev/null +++ b/.agents/skills/go-practices/references/language.md @@ -0,0 +1,438 @@ +# Go Language Conventions + +Day-to-day Go idioms this repository settles on: scoping, iteration, strings, +types, parameters, enums, resources, iterators, regexp and JSON. + +## Contents + +- Shared State Synchronization +- Scope Variables to the `if` +- Prefer `range` for Iteration +- Strings: Conversion, Formatting and Building +- Types: Narrow Conversions and Generics +- Value vs Pointer Parameters +- Enums +- Release Resources Right After Acquiring +- Iterators and Streaming +- Regular Expressions +- JSON Encoding +- Use the Current Standard Library + +--- + +## Shared State Synchronization + +Scope: Go code with goroutines (daemon sessions, workers, caches) + +Rule: State that more than one goroutine reads or writes is guarded by a +`sync.Mutex`/`sync.RWMutex`, owned by a single goroutine behind a channel, or +held in `sync/atomic` types. Document which one guards each field. Passing +`go test -race` is required, but it does not prove the absence of races on paths +the tests do not run. + +Correct: + +```go +type registry struct { + mu sync.RWMutex + sessions map[string]*Session // guarded by mu +} +``` + +Incorrect: + +```go +type registry struct { + sessions map[string]*Session // written by the accept loop and every worker +} +``` + +--- + +## Scope Variables to the `if` + +Scope: All Go code + +Rule: When a value is only needed for one check, declare it in the `if` +statement itself (`if err := os.MkdirAll(dir, 0o750); err != nil {`), so it +cannot be reused by mistake further down. Declare it outside only when the code +after the check uses it. + +--- + +## Prefer `range` for Iteration + +Scope: All Go code + +Rule: Iterate with `range` rather than an index loop, and use `for i := range n` +(Go 1.22+) for plain counting. Reach for a three-part `for` only when the index +arithmetic is the point, such as stepping by two or walking backwards. + +--- + +## Strings: Conversion, Formatting and Building + +Scope: All Go code + +Rule: + +- One value to or from a string: `strconv`, not `fmt.Sprintf("%d", n)`. +- A host and port: `net.JoinHostPort`, which brackets IPv6; never + `fmt.Sprintf("%s:%d", ...)`. +- A user-supplied string in an error or log: `%q`, so empty values, spaces and + control characters are visible (`session ""` instead of a bare gap). +- A string built in a loop: `strings.Builder`. +- Bytes that are mutated or moved through I/O stay `[]byte`; convert to `string` + once at the boundary, not per call. + +Correct: + +```go +port := strconv.Itoa(cfg.Port) +addr := net.JoinHostPort(cfg.Host, port) // brackets IPv6: [::1]:8080 +label := fmt.Sprintf("%s (%d sessions)", name, count) + +if !ValidSessionID(id) { + return fmt.Errorf("session %q: %w", id, ErrInvalidSessionID) +} + +var b strings.Builder +for _, s := range sessions { + b.WriteString(s.ID) + b.WriteByte('\n') +} +``` + +Incorrect: + +```go +port := fmt.Sprintf("%d", cfg.Port) // strconv.Itoa is the direct tool + +return fmt.Errorf("session %s: %w", id, ErrInvalidSessionID) // "" is invisible + +var out string +for _, s := range sessions { + out += s.ID + "\n" // copies the whole string every iteration +} +``` + +--- + +## Types: Narrow Conversions and Generics + +Scope: All Go code + +Rule: Keep values in the narrowest type that fits and convert explicitly. + +- A type assertion uses the two-value form (`v, ok := x.(T)`) unless a failure + is a programming error that should panic. +- A conversion to a smaller integer type checks the range first; `int32(n)` + silently wraps on overflow. +- When a function works on several concrete types, write it with a type + parameter and a constraint, not with `any`. The compiler then checks every + call and the caller gets its own type back without an assertion. Use `any` + only for values whose type is truly open, such as decoded JSON. +- Check `slices`, `maps` and `cmp` before writing a helper. + +Correct: + +```go +type identified interface{ GetID() string } + +func findByID[T identified](items []T, id string) (T, bool) { + for _, item := range items { + if item.GetID() == id { + return item, true + } + } + var zero T + return zero, false +} + +session, ok := findByID(sessions, sessionID) // session is a Session + +if n > math.MaxInt32 || n < math.MinInt32 { + return fmt.Errorf("timeout %d does not fit in int32", n) +} +timeout := int32(n) +``` + +Incorrect: + +```go +func findByID(items []any, id string) any { /* ... */ } + +session := findByID(toAny(sessions), sessionID).(Session) // panics on a mismatch + +timeout := int32(n) // wraps silently +``` + +--- + +## Value vs Pointer Parameters + +Scope: Function parameters in Go (method receivers are a separate decision) + +Rule: Pass by value by default, and take a pointer only for a reason the +signature should communicate. A pointer parameter tells the reader that the +function may change the value or that nil is a valid input; using one without +that reason sends the wrong signal. + +Take a pointer when: + +- the function mutates the caller's value; +- nil means something (an optional field in a partial update); +- the type must not be copied: it contains a `sync.Mutex`, `sync.WaitGroup` or + similar (`go vet` reports these as copylocks), or its methods use pointer + receivers; +- the struct is 80 bytes or more, the threshold `hugeParam` enforces below. + +Pass by value when: + +- the type is a scalar or a small value type: `string`, `int`, `bool`, + `float64`, `time.Time`, `time.Duration`. A string is already a pointer and a + length, so `*string` only adds a nil case; +- the type is already a header over shared data: slice, map, channel, function + or interface. A pointer to them is needed only to replace the header itself, + and returning the new value is usually clearer; +- the function only reads a small struct. + +`golangci-lint` enforces the size case: gocritic's `hugeParam` check flags value +parameters and receivers of 80 bytes or more. Code written before the check was +enabled carries `//nolint:gocritic // hugeParam: ` on the flagged line. +New code either takes a pointer or adds the same comment with a real reason (a +copy on purpose, an interface or library signature it must match). + +Do not reach for a pointer "to save memory". Copying a small struct is cheap and +stays on the stack, while taking the address of a local value can make it escape +to the heap, which costs an allocation and GC work. Each dereference is also a +possible cache miss. For small read-only structs a value is usually faster; for +large structs or mutation a pointer wins. When the difference matters, measure +it (`go test -bench`, `go build -gcflags=-m` for escapes) as coding-practices: +Performance Claims Need Measurement requires. + +Correct: + +```go +// Small value types by value +func FormatSessionLine(id, phase string, lastActive time.Time) string + +// Pointer because the function fills in the caller's config +func ApplyDefaults(cfg *Config) + +// Pointer because nil means "leave the phase unchanged" +func UpdateSession(ctx context.Context, id string, phase *string) error + +// Pointer because Registry holds a sync.RWMutex and must not be copied +func ListStale(reg *Registry, now time.Time) []string + +// Slice by value: it is already a header over shared data +func CountActive(sessions []Session) int +``` + +Incorrect: + +```go +func DescribeSession(id *string) string // no mutation, nil is not meaningful + +func CountActive(sessions *[]Session) int // pointer to a slice header + +func ListStale(reg Registry, now time.Time) []string // copies the mutex +``` + +--- + +## Enums + +Scope: `iota` constants + +Rule: The zero value of a Go variable silently becomes the first enum member. +Start the enum at 1, or make 0 an explicit `Unknown`/`Unspecified` member, so an +unset value is detectable. Keep 0 as a real member only when it is the correct +default, and say so in a comment. Give the type a `String()` method, and switch +over it exhaustively (coding-practices: Exhaustive Branching). + +Correct: + +```go +type Phase int + +const ( + PhaseUnknown Phase = iota // zero value: not set + PhaseDiscovery + PhaseExecuting + PhaseCompleted +) + +type CircuitState int + +const ( + StateClosed CircuitState = iota // zero value on purpose: a new breaker is closed + StateOpen + StateHalfOpen +) +``` + +Incorrect: + +```go +const ( + PhaseDiscovery Phase = iota // an unset Phase reads as Discovery + PhaseExecuting +) +``` + +--- + +## Release Resources Right After Acquiring + +Scope: Files, response bodies, rows, locks, tickers, listeners + +Rule: Put `defer x.Close()` (or `Unlock`, `Stop`, `cancel`) on the line right +after a successful open, before any other code. A later edit that adds an early +return cannot skip it. For a writer, the error from `Close` or `Flush` can mean +lost data: return it instead of dropping it. Use `runtime.AddCleanup` rather +than `runtime.SetFinalizer` for the rare object whose cleanup the GC must +trigger. The general rule is coding-practices: Release Resources Where They Are +Acquired. + +Correct: + +```go +f, err := os.Open(path) +if err != nil { + return fmt.Errorf("open %s: %w", path, err) +} +defer f.Close() + +func writeSummary(path string, s Summary) (err error) { + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("create %s: %w", path, err) + } + defer func() { + if cerr := f.Close(); cerr != nil && err == nil { + err = fmt.Errorf("close %s: %w", path, cerr) + } + }() + return json.NewEncoder(f).Encode(s) +} +``` + +Incorrect: + +```go +f, _ := os.Open(path) +data := parse(f) // an early return added here later leaks f +f.Close() +``` + +--- + +## Iterators and Streaming + +Scope: Code that walks large or unbounded data + +Rule: Produce and consume large sequences lazily. Return an `iter.Seq`/ +`iter.Seq2` (Go 1.23+) instead of a fully built slice when a caller may stop +early or the data is large, and move bulk data with streams (`io.Copy`, +`bufio.Scanner`, `rows.Next`) instead of reading it all into memory. Memory then +stays constant however large the input grows. The general rule is +coding-practices: Stream Large Data. + +Correct: + +```go +func (l *Ledger) Entries() iter.Seq2[Entry, error] { + return func(yield func(Entry, error) bool) { + scanner := bufio.NewScanner(l.file) + for scanner.Scan() { + entry, err := decodeEntry(scanner.Bytes()) + if !yield(entry, err) { + return + } + } + if err := scanner.Err(); err != nil { + yield(Entry{}, err) + } + } +} +``` + +Incorrect: + +```go +data, err := os.ReadFile(ledgerPath) // loads the whole ledger to read the last entry +``` + +--- + +## Regular Expressions + +Scope: All Go code using `regexp` + +Rule: Compile a constant pattern once, in a package-level variable with +`regexp.MustCompile`; a panic there is a bug found at start-up, not an expected +error. Compile a pattern built from input with `regexp.Compile` and handle the +error. Never compile inside a loop or a hot function. The same holds for +`text/template` and `html/template`: parse once, at start-up or in a +`sync.OnceValues`, and execute many times. + +Correct: + +```go +var sessionIDPattern = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`) +``` + +Incorrect: + +```go +func valid(id string) bool { + return regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`).MatchString(id) // compiles every call +} +``` + +--- + +## JSON Encoding + +Scope: All Go JSON operations + +Rule: Use `encoding/json`. `encoding/json/v2` needs `GOEXPERIMENT=jsonv2` on the +Go 1.26 toolchain this module targets and does not build without it, and the +repository does not set that experiment. Use the `omitzero` tag (Go 1.24+) for +fields whose zero value should be left out, including `time.Time` and structs, +where `omitempty` does nothing. Stream large payloads with `json.NewDecoder` +instead of reading them into memory first. Moving to v2 is part of raising the +`go` directive, see [modernization.md](modernization.md). + +Correct: + +```go +type Session struct { + ID string `json:"id"` + LastActiveAt time.Time `json:"lastActiveAt,omitzero"` +} +``` + +Incorrect: + +```go +import "encoding/json/v2" // does not build without GOEXPERIMENT=jsonv2 + +type Session struct { + LastActiveAt time.Time `json:"lastActiveAt,omitempty"` // zero time is still written +} +``` + +--- + +## Use the Current Standard Library + +Scope: All Go code + +Rule: Use the standard library feature that replaced an older idiom or a +third-party package, up to the Go version in the module's `go` directive +(`go 1.26` here), and nothing newer. The replacement table, the JSON note and +the upgrade procedure are in [modernization.md](modernization.md). Run +`go fix ./...` to apply the automatic rewrites. diff --git a/.agents/skills/go-practices/references/modernization.md b/.agents/skills/go-practices/references/modernization.md new file mode 100644 index 000000000..c9648478e --- /dev/null +++ b/.agents/skills/go-practices/references/modernization.md @@ -0,0 +1,95 @@ +# Go Modernization + +Newer standard library features that replace older idioms or third-party +packages. The module's `go` directive decides what is allowed: this repository +targets `go 1.26`, so everything below up to Go 1.26 may be used, and nothing +newer until the directive is raised on purpose. Each item was checked against +the Go 1.26 toolchain. `go fix ./...` (rewritten in Go 1.26) applies many of +these rewrites automatically; review its diff like any other change. + +--- + +## Replace These Idioms + +| Instead of | Use | Since | +| ------------------------------------------------- | ----------------------------------------------------- | ----- | +| `interface{}` | `any` | 1.18 | +| `[]any` plus type assertions | a type parameter (language.md: Types) | 1.18 | +| multi-error libraries | `errors.Join` | 1.20 | +| `HasPrefix` then `TrimPrefix` | `strings.CutPrefix`, `strings.CutSuffix` | 1.20 | +| custom `minInt`/`maxInt` helpers | built-in `min`, `max` | 1.21 | +| a loop of `delete` calls | built-in `clear(m)` | 1.21 | +| `sort.Strings`, `sort.Slice` | `slices.Sort`, `slices.SortFunc` with `cmp.Compare` | 1.21 | +| hand-written search, clone and compare loops | `slices.Contains`, `slices.Clone`, `maps.Clone` | 1.21 | +| `sync.Once` plus a package variable | `sync.OnceValue`, `sync.OnceValues`, `sync.OnceFunc` | 1.21 | +| zap, logrus, zerolog | `log/slog`, through `logfx` here | 1.21 | +| `if v == "" { v = def }` | `cmp.Or(v, def)` | 1.22 | +| `for i := 0; i < n; i++` | `for i := range n` | 1.22 | +| `v := v` copies in loops | nothing: each iteration has its own variable | 1.22 | +| `math/rand` | `math/rand/v2` (`IntN`, `N`); `crypto/rand` for keys | 1.22 | +| gorilla/mux or chi for method and path routing | `http.ServeMux` patterns (`"GET /users/{id}"`) | 1.22 | +| `sql.NullString`, `sql.NullInt64` | `sql.Null[T]` | 1.22 | +| `reflect.TypeOf((*I)(nil)).Elem()` | `reflect.TypeFor[I]()` | 1.22 | +| building a slice only to range over it | iterators: `iter.Seq`, `slices.Collect`, `Chunk` | 1.23 | +| manual string interning maps | `unique.Make` | 1.23 | +| `strings.Split` only to iterate | `strings.SplitSeq`, `FieldsSeq`, `Lines` | 1.24 | +| `filepath.Clean` plus prefix checks on user paths | `os.OpenRoot` and `os.Root` methods | 1.24 | +| `omitempty` on `time.Time` and struct fields | `omitzero` | 1.24 | +| `context.Background()` in tests | `t.Context()` | 1.24 | +| `for i := 0; i < b.N; i++` in benchmarks | `for b.Loop()` | 1.24 | +| `runtime.SetFinalizer` | `runtime.AddCleanup` | 1.24 | +| `golang.org/x/crypto` sha3, hkdf, pbkdf2 | `crypto/sha3`, `crypto/hkdf`, `crypto/pbkdf2` | 1.24 | +| `tools.go` blank imports | `tool` directives in `go.mod` (already used here) | 1.24 | +| `wg.Add(1)` plus `defer wg.Done()` | `wg.Go(func() { ... })` for work that returns nothing | 1.25 | +| sleeping or polling in concurrency tests | `testing/synctest.Test` and `synctest.Wait` | 1.25 | +| `go.uber.org/automaxprocs` | nothing: `GOMAXPROCS` follows cgroup limits | 1.25 | +| `errors.As(err, &target)` | `errors.AsType[T](err)` | 1.26 | +| a `ptr[T](v)` helper | `new(v)`, which returns a pointer to that value | 1.26 | +| `rsa.EncryptPKCS1v15` for new encryption | RSA-OAEP or `crypto/hpke` | 1.26 | +| a third-party slog fan-out handler | `slog.NewMultiHandler` | 1.26 | +| writing test output to a temp dir by hand | `t.ArtifactDir()` | 1.26 | +| `ReverseProxy.Director` (deprecated) | `ReverseProxy.Rewrite` with `pr.SetURL` | 1.20 | +| `fmt.Sprintf("%s:%d", host, port)` | `net.JoinHostPort`, which brackets IPv6 | any | + +`wg.Go` fits only work that neither returns an error nor needs cancellation; use +`errgroup` for the rest (coding-practices: Owned Asynchronous Work). + +Since Go 1.23, a timer or ticker that goes out of scope is collected without +`Stop()`. Still call `Stop()` when the timer must not fire. + +--- + +## JSON: Stay on `encoding/json` for Now + +`encoding/json/v2` needs `GOEXPERIMENT=jsonv2` on Go 1.26; without it the +package does not build. This repository does not set the experiment, so use +`encoding/json` (with `omitzero`, which works there since Go 1.24). Move to v2 +when the module targets a Go release where it is the default, and test real +payloads first: v2 rejects duplicate object keys and invalid UTF-8 that v1 +accepted. + +--- + +## Tooling + +- `go fix ./...` applies the modernizers the toolchain ships; check coverage + with `go tool fix help`. +- Use `go doc`; Go 1.26 removed `go tool doc`. +- `govulncheck` reports vulnerabilities reachable from the code. It is pinned as + a `tool` in `go.mod`; run `go tool govulncheck ./...`. No CI step runs it yet. +- Profile-guided optimization: a `default.pgo` CPU profile in the main package + is applied automatically at build time. Add one only with a benchmark that + shows the gain ([performance.md](performance.md): Runtime Settings). +- `benchstat` (`golang.org/x/perf/cmd/benchstat`) compares benchmark runs; add + it as a `tool` directive when first needed. + +--- + +## Raising the `go` Directive + +Do not use an API newer than the module's `go` directive; `go vet` and newer +toolchains flag it. Raising the directive is a deliberate change of its own: +read the release notes for removed `GODEBUG` settings and behavior changes (the +JSON default is one), run the full suite and `go fix ./...`, then update this +file with the release's replacements after checking each one with `go doc` on +the new toolchain. diff --git a/.agents/skills/go-practices/references/performance.md b/.agents/skills/go-practices/references/performance.md new file mode 100644 index 000000000..11665c9a8 --- /dev/null +++ b/.agents/skills/go-practices/references/performance.md @@ -0,0 +1,440 @@ +# Go Performance + +Go tools and techniques for removing a measured bottleneck. The method (profile +first, rule out waiting on dependencies, fix the algorithm, change one thing at +a time, comment what an optimization buys) is coding-practices: Performance +Claims Need Measurement. Everything below is a candidate to try after a profile +points at the code, not a default style. + +## Contents + +- Find Where the Time Goes +- Benchmark, Change, Compare +- Algorithms Before Constants +- Allocations +- Cache Work That Repeats +- CPU +- I/O and Network +- Runtime Settings +- Production Signals +- Common Mistakes + +--- + +## Find Where the Time Goes + +| Symptom | Tool and signal | +| --------------------------------------- | ------------------------------------------------------------------ | +| High CPU | CPU profile: the function with the largest flat or cumulative time | +| Many allocations, high GC share | Heap profile with `-sample_index=alloc_objects` | +| Memory keeps growing | Heap profile with `inuse_space`; compare two snapshots | +| Latency high, CPU low | Goroutine profile: goroutines blocked in network or `database/sql` | +| Goroutine count rising | Goroutine profile: a stack that keeps accumulating | +| Throughput stops scaling with cores | Mutex and block profiles | +| Stages idle while waiting on each other | `go tool trace` | +| A value escapes to the heap | `go build -gcflags=-m` (`-m -m` prints the reason) | +| A small helper is not inlined | `go build -gcflags=-m`, look for `cannot inline` | + +When goroutines mostly wait, the time is off-CPU and the CPU profile misses it: +the goroutine profile, the trace and OpenTelemetry spans show which dependency +is slow. + +Getting profiles in this repository: + +```bash +go test -bench=BenchmarkRender -cpuprofile=etc/temp/cpu.out -memprofile=etc/temp/mem.out ./pkg/ajan/formatfx/ +go tool pprof -http=:0 etc/temp/cpu.out +``` + +A running `httpfx` service exposes `/debug/pprof/` through the profiling module +(`pkg/ajan/httpfx/modules/profiling`). The endpoints reveal memory contents and +stacks, so they stay behind `PPROF_TOKEN` and off public listeners +(security-practices: Secrets and Personal Data Never Reach Output). + +--- + +## Benchmark, Change, Compare + +Scope: Every change made for performance + +Rule: + +1. Name the metric you are improving: latency, throughput, memory or CPU. +2. Write a benchmark that exercises one function, using `for b.Loop()` (Go + 1.24+), and report allocations. +3. Record a baseline, run the same command after one change, compare: + +```bash +go test -run='^$' -bench=BenchmarkRender -benchmem -count=10 ./pkg/ajan/formatfx/ > etc/temp/bench-1.txt +# apply one change +go test -run='^$' -bench=BenchmarkRender -benchmem -count=10 ./pkg/ajan/formatfx/ > etc/temp/bench-2.txt +go tool benchstat etc/temp/bench-1.txt etc/temp/bench-2.txt +``` + +`benchstat` comes from `golang.org/x/perf/cmd/benchstat`; add it as a `tool` +directive the first time it is needed. Run benchmarks one after another on an +otherwise quiet machine: two benchmark runs sharing CPUs distort each other, +even when the variants were written in parallel. Keep the result files under +`etc/temp/` (gitignored) and put the `benchstat` table in the body of the +`perf:` commit. + +```go +func BenchmarkRender(b *testing.B) { + rows := sampleRows(1_000) + b.ReportAllocs() + for b.Loop() { + Render(rows) + } +} +``` + +--- + +## Algorithms Before Constants + +| Pattern | Cost | Replace with | +| ---------------------------------------- | --------------- | ------------------------------------- | +| `slices.Contains` inside a loop | O(n·m) | Build a `map[T]struct{}` once: O(n+m) | +| Nested loops to match two collections | O(n·m) | Index one side by key in a map | +| Repeated min, max or lookups on one list | O(n) per query | Sort once, `slices.BinarySearch` | +| `append` in a loop with a known size | Repeated growth | `make([]T, 0, n)` | +| `s += part` in a loop | O(n²) copying | `strings.Builder` | + +Test the growth: benchmark with 100, 1,000 and 10,000 items; ten times the input +taking a hundred times as long means the algorithm is quadratic. In a hot path +prefer one direct loop over a chain of iterator adapters, each of which adds a +closure call per element. + +--- + +## Allocations + +Preallocation, cloning and map sizing are covered in +[data-structures.md](data-structures.md). In hot paths, also: + +- **Reuse a slice's backing array** with `buf = buf[:0]` or + `buf = append(buf[:0], items...)`, when nothing else still holds the old + contents. +- **Take the value from `range`** (`for k, v := range m`) instead of indexing + the map again inside the loop. +- **Return sentinel errors** for expected failures on a hot path; `fmt.Errorf` + allocates on every call. Keep wrapping with context everywhere else + (errors-and-logging.md: Error Handling). +- **Avoid boxing into `any`.** Passing a concrete value through `any` or a + `...any` parameter usually allocates. Use a typed parameter or a type + parameter. +- **Convert between `string` and `[]byte` once.** Each conversion copies. The + `bytes` package mirrors most of `strings`, so work on `[]byte` directly when + that is what you have. + +### Pooling + +`sync.Pool` keeps short-lived objects between uses. Use it for objects created +on every request or record (encoding buffers, scratch structs), and only after a +heap profile shows that allocation near the top: + +```go +var bufferPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} + +const maxPooledBuffer = 64 << 10 + +func EncodeSession(s *Session) ([]byte, error) { + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer func() { + if buf.Cap() <= maxPooledBuffer { // a rare huge buffer is left to the GC + bufferPool.Put(buf) + } + }() + + if err := json.NewEncoder(buf).Encode(s); err != nil { + return nil, fmt.Errorf("encode session %q: %w", s.ID, err) + } + return bytes.Clone(buf.Bytes()), nil // pooled memory never leaves this function +} +``` + +- Reset an object when taking it out, and drop references it holds, so the pool + does not keep large graphs alive. +- Never return pooled memory to a caller; return a copy. +- Cap what goes back into the pool. One oversized buffer, once pooled, keeps its + memory for as long as the pool reuses it. +- Do not pool rarely created objects; the pool costs more than it saves. + +### Memory Held by Small Views + +A sub-slice or substring keeps its whole backing array alive; copy what you keep +from a large input (`bytes.Clone(frame[:16])`, `strings.Clone(line[:8])`). + +--- + +## Cache Work That Repeats + +Compile regular expressions and parse templates once (language.md: Regular +Expressions). For lazily computed values use `sync.OnceValue`. Any cache follows +coding-practices: Bound Every Resource. + +When many callers miss the same cache key at once, they all do the same +expensive fetch. `golang.org/x/sync/singleflight` lets one of them fetch while +the others wait for its result. Use `DoChan` so each caller still honors its own +context, and give the shared fetch a context of its own: it must not fail +because the first caller gave up. + +```go +type ManifestCache struct { + group singleflight.Group + fetcher ManifestFetcher + cache *BoundedCache[string, *Manifest] +} + +func (c *ManifestCache) Get(ctx context.Context, url string) (*Manifest, error) { + if m, ok := c.cache.Get(url); ok { + return m, nil + } + + ch := c.group.DoChan(url, func() (any, error) { + fetchCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second) + defer cancel() + + m, err := c.fetcher.Fetch(fetchCtx, url) + if err == nil { + c.cache.Add(url, m) + } + return m, err + }) + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case res := <-ch: + if res.Err != nil { + return nil, fmt.Errorf("fetch manifest %s: %w", url, res.Err) + } + return res.Val.(*Manifest), nil + } +} +``` + +The typed wrapper keeps the `any` assertion in one place. For eviction, a +generic LRU such as `hashicorp/golang-lru/v2` (already in the module graph as an +indirect dependency) still goes through tooling-standards: Adding a Dependency +before code imports it directly. + +--- + +## CPU + +### Inlining + +The compiler inlines small functions, which removes call overhead in tight +loops. A call to something that cannot be inlined, such as a logging call, can +push a small helper over the budget. Keep side effects out of hot helpers, and +check the decision: + +```bash +go build -gcflags=-m ./pkg/ajan/formatfx/ 2>&1 | grep -E "can inline|cannot inline" +``` + +Receiver kind does not decide inlining: methods with pointer receivers inline as +well. Choose receivers by language.md: Value vs Pointer Parameters, not to chase +inlining. + +### Logging in Hot Paths + +A disabled log call still evaluates its arguments, and `...any` arguments are +boxed before the level check runs. In a loop, check the level once and build +attributes only when it is enabled; `LogAttrs` avoids the boxing: + +```go +if logger.Enabled(ctx, logfx.LevelTrace) { + logger.LogAttrs(ctx, logfx.LevelTrace, "row rendered", + slog.Int("index", i), slog.Duration("took", time.Since(start))) +} +``` + +### Reflection + +`reflect` and `reflect.DeepEqual` inspect types at run time and allocate. In +production code compare with `slices.Equal`, `maps.Equal`, `bytes.Equal` or a +typed `Equal` method; keep `DeepEqual` for tests. Dispatch on a dynamic type +with one type switch instead of a series of type assertions. + +### Memory Layout and Locality + +- Order struct fields from largest to smallest alignment only for types held in + large slices or maps (`unsafe.Sizeof` confirms the saving). The + `fieldalignment` analyzer lists candidates when run on demand; it is not + enabled in `.golangci.yaml`, because elsewhere reordering only costs + readability. +- Keep hot data contiguous: one slice for a matrix, nodes in a slice linked by + index, a struct of slices when a hot loop reads one field of many structs. + +### Last-Resort Techniques + +Each of these makes code harder to read. Use one only with a benchmark on a +profiled hot path, and leave a comment with the numbers: + +- **False sharing.** Counters updated by different goroutines on the same 64 + byte cache line slow each other down. Pad them apart (`_ [56]byte` after an + `atomic.Int64`, or `cpu.CacheLinePad` from `golang.org/x/sys/cpu`). The sign + is a concurrent benchmark that gets slower as goroutines are added. +- **Several accumulators.** A tight arithmetic loop with one accumulator waits + on each previous addition; splitting it into four independent sums lets the + CPU run them in parallel. +- **SIMD and assembly.** Rely on the compiler's vectorization and `math/bits`. + `simd/archsimd` is a Go 1.26 experiment behind `GOEXPERIMENT=simd`; do not use + it here. Hand-written assembly needs an ADR. Pick a CPU-specific path in a + package-level variable initializer (`golang.org/x/sys/cpu`), never in + `init()`, and keep the portable version tested. +- **`unsafe`.** Only for FFI and layout (data-structures.md). + +### The Scheduler + +Goroutines are preempted asynchronously since Go 1.14. Do not add +`//go:noinline` or artificial calls to create preemption points. + +--- + +## I/O and Network + +### HTTP Clients and Servers + +The zero `http.Client` has no timeout, and `http.Transport` keeps only 2 idle +connections per host by default, so concurrent calls to one service keep opening +new connections. Use `pkg/ajan/httpclient`, whose transport defaults already +raise the pool (`max_idle_conns_per_host: 100`) and whose calls take a context, +rather than a bare `http.Client`. Servers go through `httpfx`, which sets +`ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout` and `IdleTimeout` from +config. A hand-built `http.Server` sets all four (coding-practices: External +Calls Have Timeouts). + +A connection returns to the pool only after its body is read to the end and +closed. When the body does not matter, drain a bounded amount: + +```go +defer resp.Body.Close() +_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64<<10)) // best effort: reuse the connection +``` + +### Streaming and Buffering + +Read large or unbounded input as a stream (coding-practices: Stream Large Data): +`io.Copy` between reader and writer, `bufio.Scanner` for lines, and +`json.NewDecoder` with `More()` for a large JSON array. `io.ReadAll` is fine for +input already bounded by `io.LimitReader` or `http.MaxBytesReader`. + +Many small reads or writes cost one system call each. Wrap files and sockets in +`bufio.Reader` or `bufio.Writer`, and check the error of the final `Flush`. + +### Batching + +One round trip per item multiplies latency by the item count. Send many items +per call: + +- PostgreSQL through `connfx`: `pgx.Batch` with `SendBatch` for many statements, + `CopyFrom` for bulk inserts. Both take `ctx` and return errors to check. +- HTTP: use the API's batch endpoint when it has one. +- Across the FFI bridge (`pkg/@eserstack/ajan`): each call from the host runtime + into Go costs far more than a Go call. Pass a whole list per call, not one + item. + +A consumer that batches a stream flushes on size and on time, and stops with its +context: + +```go +func (w *AuditWriter) Run(ctx context.Context, events <-chan AuditEvent) error { + batch := make([]AuditEvent, 0, w.batchSize) + ticker := time.NewTicker(w.flushInterval) + defer ticker.Stop() + + flush := func() error { + if len(batch) == 0 { + return nil + } + err := w.store.InsertEvents(ctx, batch) + batch = batch[:0] + return err + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case e, ok := <-events: + if !ok { + return flush() + } + batch = append(batch, e) + if len(batch) == w.batchSize { + if err := flush(); err != nil { + return fmt.Errorf("flush audit batch: %w", err) + } + } + case <-ticker.C: + if err := flush(); err != nil { + return fmt.Errorf("flush audit batch: %w", err) + } + } + } +} +``` + +### Concurrent Pipeline Stages + +Run stages concurrently only when each is limited by a different resource (CPU, +disk, network), a trace shows one idle while another is busy, and the sequential +version with batching was measured and is slower. Bound the channels between +stages (coding-practices: Bound Every Resource) and run the stages with +`errgroup.WithContext` (coding-practices: Owned Asynchronous Work). + +--- + +## Runtime Settings + +- `GOMAXPROCS` follows cgroup CPU limits since Go 1.25; do not set it by hand. +- Set `GOMEMLIMIT` for services with a container memory limit, to about 80-90% + of that limit, so the GC works harder before the container is killed. +- Leave `GOGC` at its default unless a profile shows GC time is the problem. +- Profile-guided optimization: a `default.pgo` CPU profile from production in + the main package is used by `go build` automatically. Commit one only with a + benchmark showing the gain, and refresh it when the code changes a lot. + +--- + +## Production Signals + +`runtime/metrics` exposes the runtime's own counters without a dependency, and +OpenTelemetry exports them through `logfx`'s meter provider. Watch trends, not +single values: + +| Metric | Watch for | +| ------------------------------------ | ---------------------------------------------- | +| `/gc/heap/allocs:bytes` | Allocation rate rising after a deploy | +| `/gc/cycles/total:gc-cycles` | GC frequency rising with steady load | +| `/cpu/classes/gc/total:cpu-seconds` | Share of CPU spent in the GC | +| `/memory/classes/heap/objects:bytes` | Live heap climbing under constant load: a leak | +| `/sched/goroutines:goroutines` | Goroutines growing independently of traffic | +| `/sched/latencies:seconds` | Goroutines waiting long for a CPU | +| `/sync/mutex/wait/total:seconds` | Lock contention | + +Request latency comes from the `httpfx` metrics and trace spans. Continuous +profiling services (Pyroscope, Parca and similar) add infrastructure and a +dependency; they need an ADR first. + +--- + +## Common Mistakes + +| Mistake | Fix | +| ------------------------------------------------- | --------------------------------------------------- | +| One benchmark run before and after | `-count=10` and `benchstat` | +| A bare `http.Client` or `http.Server` | `httpclient` and `httpfx`, or set timeouts and pool | +| Not draining response bodies | Bounded `io.Copy(io.Discard, ...)`, then close | +| `io.ReadAll` on unbounded input | Stream, or bound with `io.LimitReader` | +| Log calls with computed arguments in a hot loop | `Enabled` check and `LogAttrs` | +| `reflect.DeepEqual` in production code | Typed equality | +| `panic` and `recover` for control flow | Return errors | +| Returning pooled buffers to callers | Return a copy | +| Keeping a small sub-slice of a large buffer | `bytes.Clone` or `strings.Clone` | +| `//go:noinline` to help the scheduler | Nothing; preemption is asynchronous since Go 1.14 | +| `unsafe`, padding or assembly without a benchmark | Measure on the hot path, or leave it out | diff --git a/.agents/skills/go-practices/references/testing.md b/.agents/skills/go-practices/references/testing.md new file mode 100644 index 000000000..cc974bac3 --- /dev/null +++ b/.agents/skills/go-practices/references/testing.md @@ -0,0 +1,129 @@ +# Go Testing + +How Go tests are written and run in this repository. The testing strategy +(levels, coverage, what to test) is architecture-guidelines' testing reference. + +## Contents + +- Table-Driven, Parallel, Race-Checked +- Fakes at the Port +- Concurrency Tests +- Benchmarks + +--- + +## Table-Driven, Parallel, Race-Checked + +Scope: All Go tests + +Rule: Write table-driven tests with `t.Parallel()` at both the test and the +subtest level. Since Go 1.22 each loop iteration has its own variable, so the +old `tc := tc` copy is not needed. Use `t.Context()` (Go 1.24+) instead of +`context.Background()`. Tests run with the race detector: `make test` (or +`deno task cli go-test` for coverage). A test that cannot run in parallel (it +sets process environment or a global) says why in a comment. + +Correct: + +```go +func TestIsSessionStale(t *testing.T) { + t.Parallel() + + now := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) + cases := []struct { + name string + lastActive time.Time + want bool + }{ + {"active a minute ago", now.Add(-time.Minute), false}, + {"idle for two hours", now.Add(-2 * time.Hour), true}, + {"never active", time.Time{}, true}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := IsSessionStale(tc.lastActive, now); got != tc.want { + t.Errorf("IsSessionStale(%v) = %v, want %v", tc.lastActive, got, tc.want) + } + }) + } +} +``` + +Incorrect: + +```go +func TestIsSessionStale(t *testing.T) { + if !IsSessionStale(time.Time{}, time.Now()) { // one case, wall clock, serial + t.Error("failed") + } +} +``` + +--- + +## Fakes at the Port + +Scope: Tests of business services + +Rule: Test a service by passing hand-written fakes of its ports to the real +constructor. A fake is a small struct in the `_test.go` file that implements the +port with an in-memory map or a recorded call list; no mocking library. Pass a +real or no-op value for every parameter, never `nil` for a dependency the code +might call: a nil logger or clock turns a test failure into a panic far from the +cause. Keep the real adapters for integration tests, and fake only the boundary +under test (the database, an external API, the mailer). + +```go +type fakeStore struct { + sessions map[string]*Session +} + +func (f *fakeStore) Get(_ context.Context, id string) (*Session, error) { + s, ok := f.sessions[id] + if !ok { + return nil, ErrSessionNotFound + } + return s, nil +} + +func TestServiceLoad(t *testing.T) { + t.Parallel() + + store := &fakeStore{sessions: map[string]*Session{"s1": {ID: "s1"}}} + now := func() time.Time { return time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) } + svc := NewService(store, now, slog.New(slog.DiscardHandler)) + + got, err := svc.Load(t.Context(), "s1") + if err != nil { + t.Fatalf("Load(%q): %v", "s1", err) + } + if got.ID != "s1" { + t.Errorf("Load(%q).ID = %q, want %q", "s1", got.ID, "s1") + } +} +``` + +**Why:** a fake that implements the port breaks at compile time when the port +changes, and the test exercises the same constructor the composition root uses. + +--- + +## Concurrency Tests + +Scope: Tests of code with goroutines, timers or tickers + +Rule: Use `testing/synctest.Test` (Go 1.25+) with `synctest.Wait` instead of +sleeping or polling; time inside the bubble is virtual, so the test is fast and +deterministic. `go test -race` passing does not prove the absence of races on +paths the tests do not run. + +--- + +## Benchmarks + +Scope: `Benchmark*` functions + +Rule: Use `for b.Loop()` (Go 1.24+) and `b.ReportAllocs()`. How to compare runs +is in performance.md: Benchmark, Change, Compare. diff --git a/.agents/skills/handoff/SKILL.md b/.agents/skills/handoff/SKILL.md new file mode 100644 index 000000000..d4d3471f6 --- /dev/null +++ b/.agents/skills/handoff/SKILL.md @@ -0,0 +1,41 @@ +--- +name: handoff +description: "Writes a handoff document that lets a fresh agent session continue the current work, saved outside the repository. Takes an optional note on what the next session will focus on." +argument-hint: "[what the next session will work on]" +disable-model-invocation: true +--- + +# Handoff + +Compacts the current conversation into one document a fresh agent can start +from. The user starts it with `/handoff`; the argument, if any, is the focus of +the next session: $ARGUMENTS + +## Always + +- Write the document to the OS temporary directory (`$TMPDIR`, else `/tmp`) as + `handoff--.md`, never into the repository, and give the + user its full path +- Follow the section template in handoff-template.md; English, whatever the + conversation language +- Tailor it to the argument: keep what the next focus needs, drop what it does + not. Without an argument, cover the whole session +- Point to artifacts instead of copying them: specs, plans, ADRs, backlog task + ids, commits, PRs and changed files by path, id or URL +- Separate what was verified (with the command or evidence) from what was only + changed or assumed; never present an unverified step as done +- Record the user's decisions and constraints from this session that no file + holds yet, with the reason when one was given +- Name the uncommitted changes, so the next agent works with them and does not + revert them (workflow-practices: Never Revert User Changes) +- Include "Suggested skills": which skills the next agent should load first, and + why +- Redact secrets and personal data: no keys, tokens, passwords, emails or names; + refer to people by role +- Do not commit, push or open the file; stop after reporting the path + +## References + +| File | Read when | +| ----------------------------------------------------- | ------------------------------------ | +| [handoff-template.md](references/handoff-template.md) | Writing the document: sections, size | diff --git a/.agents/skills/handoff/references/handoff-template.md b/.agents/skills/handoff/references/handoff-template.md new file mode 100644 index 000000000..2b856d50e --- /dev/null +++ b/.agents/skills/handoff/references/handoff-template.md @@ -0,0 +1,94 @@ +# Handoff Template + +The sections of a handoff document and what goes in each. A fresh agent reads it +once, before anything else, so it states facts and pointers, not narrative. + +## Contents + +- Sections +- Size and Style +- Before Saving + +--- + +## Sections + +Scope: Every handoff document + +Rule: Use these sections in this order, and omit any that would be empty. + +```markdown +# Handoff: () + +## Next Focus + +What the next session is for (the /handoff argument, or the natural next step). + +## Where Things Stand + +- Done and verified: : +- Done, not verified: : +- In progress: + +## Decisions and Constraints + +- : + +## Open Questions + +- + +## Next Steps + +1. +2. ... + +## References + +- Changed files (uncommitted): +- Artifacts: + +## Pitfalls + +- + +## Suggested Skills + +- : +``` + +- **Decisions and Constraints** holds only what the session settled and no file + records yet. A rule already in `AGENTS.md`, a skill or the agent's memory is + not repeated. +- **Pitfalls** saves the next agent from repeating a dead end: a command that + hung, an API that does not exist, a check that flags generated files. +- **Suggested Skills** names skills from `.agents/skills/` by name, based on the + next focus (for example workflow-practices and go-practices for continuing a + Go change). + +--- + +## Size and Style + +Scope: The whole document + +Rule: Aim for one to two screens. A reader who needs more follows the +references. Write in short bullets with paths and ids, in the plain style of +documentation-conventions: Plain Prose. Quote an error message or a command +exactly when it matters; do not paste diffs, logs or file contents that the +references already reach. + +--- + +## Before Saving + +Scope: The last step + +Rule: Check the draft once more: + +- no secret, token, key, password, email or person's name is left in it; +- every "verified" line names its evidence; +- every path and id exists (`git status`, the backlog, the PR list); +- the next focus from the argument is what the document is centred on. + +Then write the file and reply with its path and a one-line summary. diff --git a/.agents/skills/javascript-practices/SKILL.md b/.agents/skills/javascript-practices/SKILL.md new file mode 100644 index 000000000..a5409a1eb --- /dev/null +++ b/.agents/skills/javascript-practices/SKILL.md @@ -0,0 +1,39 @@ +--- +name: javascript-practices +description: "TS and JS conventions for eserstack packages: namespace imports, mod.ts entries, cross-runtime APIs, explicit checks, async, tests and laroux React components. Use when writing or reviewing .ts, .tsx or .js files, Deno.* or process.env use in libraries, imports, truthy or null checks flagged in review, tests or React UI. Not for Deno config or dependencies (use tooling-standards)." +--- + +# JavaScript/TypeScript Practices + +How TypeScript is written in `pkg/@eserstack/*`: modules, runtime access, types, +tests and React UI. + +## Always + +- Always import as a namespace (`import * as path from "@std/path"`), in code, + tests, JSDoc `@example` blocks and READMEs alike; the only exception is + `runtime` from `@eserstack/standards/cross-runtime` +- Named exports on the declaration; no default exports except where laroux loads + a module's `default` +- Relative imports carry the `.ts` extension; a package's entry is `mod.ts`; no + `export *` +- File system, env and process access goes through `runtime`, never `Deno.*` or + `process.env` in library code +- Only booleans use implicit checks; defaults use `??`, never `||` +- `validate-code-conventions` (in `deno task cli ok`) enforces the import and + `||` rules on new code; existing violations sit in + `.eser/baselines/code-conventions.json`, which only ever shrinks +- `null` for absent values you define; `instanceof` for object types +- `return await` in async functions; every promise is awaited or owned +- Tests are co-located `*.test.ts`, run with `deno task cli test` +- `deno fmt` and `deno lint` enforce formatting and the basics; run them instead + of hand-checking + +## References + +| File | Read when | +| ----------------------------------------------------- | --------------------------------------------------------- | +| [modules.md](references/modules.md) | Adding imports or exports, creating a module or `mod.ts` | +| [syntax-and-types.md](references/syntax-and-types.md) | Runtime APIs, paths, checks, conversions, async functions | +| [testing.md](references/testing.md) | Writing or running TypeScript tests | +| [react.md](references/react.md) | Building React components, styles or UI states | diff --git a/.agents/skills/javascript-practices/evals/triggers.json b/.agents/skills/javascript-practices/evals/triggers.json new file mode 100644 index 000000000..e0d177694 --- /dev/null +++ b/.agents/skills/javascript-practices/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "write a TypeScript helper in @eserstack/primitives that parses durations", + "should_trigger": true + }, + { + "query": "convert this .tsx component to our conventions", + "should_trigger": true + }, + { + "query": "why is `if (!value)` flagged in review", + "should_trigger": true + }, + { + "query": "add a test for slug.ts", + "should_trigger": true + }, + { + "query": "make this async function return consistently", + "should_trigger": true + }, + { + "query": "fs access in this library uses Deno.readTextFile, is that ok", + "should_trigger": true + }, + { + "query": "bu TS dosyasındaki import'ları düzelt", + "should_trigger": true + }, + { + "query": "build a loading and error state for this React form", + "should_trigger": true + }, + { + "query": "add a dependency on zod", + "should_trigger": false + }, + { + "query": "the deno.json in pkg/@eserstack/cache is wrong", + "should_trigger": false + }, + { + "query": "write a Go handler", + "should_trigger": false + }, + { + "query": "add a CI job that runs deno test", + "should_trigger": false + }, + { + "query": "document the new API in the README", + "should_trigger": false + }, + { + "query": "cut a release", + "should_trigger": false + }, + { + "query": "which Makefile target runs Go lint", + "should_trigger": false + }, + { + "query": "should we write an ADR for this", + "should_trigger": false + } +] diff --git a/.agents/skills/javascript-practices/references/modules.md b/.agents/skills/javascript-practices/references/modules.md new file mode 100644 index 000000000..578134bef --- /dev/null +++ b/.agents/skills/javascript-practices/references/modules.md @@ -0,0 +1,139 @@ +# Modules and Imports + +How TypeScript modules in this repository export, import and name their paths. + +## Contents + +- Exports +- Imports +- Import Paths +- Avoid Re-exports + +--- + +## Exports + +Scope: All TypeScript modules + +Rule: Export with the `export` keyword on the declaration. No default exports, +except where a framework requires one (see react.md: Named Exports for +Components). No `export { a, b }` lists at the bottom of a file. + +Correct: + +```typescript +export function buildCommand() {} +export const CONFIG_PATH = "./config"; +export type BuildOptions = { readonly dryRun: boolean }; +``` + +Incorrect: + +```typescript +function buildCommand() {} +export { buildCommand }; // indirect export +export default buildCommand; // default export +``` + +**Why:** the export is visible where the symbol is defined, and every importer +uses the same name, so a rename is a single grep. + +--- + +## Imports + +Scope: All TypeScript imports + +Rule: Always import as a namespace with `import * as`. The +`validate-code-conventions` validator enforces it on new code. Name the alias +after the module: `path`, `fs`, `semver` for the standard library, the package's +short name (`shell`, `primitives`, `functions`, `standards`) for workspace +packages. When a package's `mod.ts` exposes sub-modules as namespaces, import +the package once and reach sub-modules with dot notation. + +The only exception: + +- `import { runtime } from "@eserstack/standards/cross-runtime";` + +Existing named imports in the code are not a precedent for new ones. + +Correct: + +```typescript +import * as path from "@std/path"; +import * as shell from "@eserstack/shell"; +import * as primitives from "@eserstack/primitives"; +import { runtime } from "@eserstack/standards/cross-runtime"; + +const filePath = path.join(dir, "config.ts"); +const output = shell.formatting.createOutput(); +const result = primitives.results.ok(value); +``` + +Incorrect: + +```typescript +import { join } from "@std/path"; // named import from a package +import * as shellFormatting from "@eserstack/shell/formatting"; // root exports it +import * as shellExec from "@eserstack/shell/exec"; + +const filePath = join(dir, "config.ts"); // which join? +``` + +**Why:** a namespace shows where every symbol comes from and cannot collide with +a local name; the root-package form keeps one import per dependency. + +--- + +## Import Paths + +Scope: Relative imports + +Rule: Write the full file path with its extension. A package's entry point is +`mod.ts`; never import a directory. The root `deno.json` enables +`sloppy-imports` for compatibility, but new code does not rely on it. Use +`import type` for type-only imports; the `verbatim-module-syntax` lint rule +requires it. + +Correct: + +```typescript +import * as math from "./math/add.ts"; +import type { BuildOptions } from "../config/build.ts"; +``` + +Incorrect: + +```typescript +import * as math from "./math/add"; // missing extension +import * as loggers from "./loggers"; // directory import +``` + +**Why:** explicit paths resolve the same way in Deno, Node, Bun and bundlers. + +--- + +## Avoid Re-exports + +Scope: `mod.ts` files and any module that re-exports + +Rule: No `export *`. Consumers import from the specific module they need. When a +package needs a curated entry point, re-export named symbols explicitly. + +Correct: + +```typescript +// mod.ts: curated public API +export { getCommits, getTaggedVersions } from "./git.ts"; +export { discoverPackages } from "./workspace-discovery.ts"; +``` + +Incorrect: + +```typescript +export * from "./git.ts"; +export * from "./versions.ts"; // TS2308 when both export `main` +``` + +**Why:** wildcard re-exports collide on shared names, hide the public surface +and defeat tree-shaking. diff --git a/.agents/skills/javascript-practices/references/react.md b/.agents/skills/javascript-practices/references/react.md new file mode 100644 index 000000000..86a5bbfa8 --- /dev/null +++ b/.agents/skills/javascript-practices/references/react.md @@ -0,0 +1,250 @@ +# React and UI + +Components for apps built on laroux (`@eserstack/laroux-react`, +`laroux-bundler`) and UI surfaces such as `noskills-web`. File names are +kebab-case like every other file (`user-profile.tsx`); the `validate-filenames` +check in `.eser/manifest.yml` enforces it. + +## Contents + +- CSS Modules with @apply +- Interaction States +- Accessibility Baseline +- Use Existing Design Tokens +- Single Props Object Pattern +- Named Exports for Components +- React Compiler Compatibility + +--- + +## CSS Modules with @apply + +Scope: Styling React components in laroux apps + +Rule: Style a component with a co-located `*.module.css` file that uses semantic +class names and Tailwind `@apply`; `laroux-bundler` expands `@apply` when it +processes CSS modules. Use Tailwind classes directly only for one-off layout +adjustments, and no inline `style` objects. + +Correct: + +```css +/* product-card.module.css */ +.product-card { + @apply border rounded-lg p-4 shadow-md flex flex-col gap-2; + + & .title { + @apply text-xl font-bold mb-2; + } +} +``` + +```tsx +// product-card.tsx +import styles from "./product-card.module.css"; + +export function ProductCard(props: ProductCardProps) { + return ( +
+

{props.product.name}

+
+ ); +} +``` + +Incorrect: + +```tsx +
+

{props.product.name}

+
; +``` + +**Why:** semantic class names keep markup readable and put a component's styles +in one file. + +--- + +## Interaction States + +Scope: Every UI surface: laroux-rendered pages, the noskills-web dashboard, and +terminal UIs + +Rule: Every view that loads, lists or submits data implements five states: +loading, empty, error, success and partial (some items failed or are still +arriving). A missing state is a bug, not a TODO. The empty state says what is +missing and gives the next action (a button, a link or the exact command to +run). The error state says what failed and how to retry; it never shows a raw +exception. Check each view with a very long name, zero results and a network +failure in the middle of an action. Placeholder copy ("Lorem ipsum", "TBD", "No +data") does not ship. + +Correct: + +```tsx +if (props.specs.length === 0) { + return ( +

+ No specs yet. Run noskills spec new to create one. +

+ ); +} +``` + +Incorrect: + +```tsx +if (items.length === 0) return

No data

; +``` + +--- + +## Accessibility Baseline + +Scope: Web UI (laroux-rendered pages, noskills-web) + +Rule: Every action can be done with the keyboard alone, and focus is visible on +every interactive element. A modal traps focus while open and returns focus to +the element that opened it when it closes. Interactive elements have an +accessible name (visible label, `aria-label` or `aria-labelledby`). Text +contrast meets WCAG 2.1 AA: 4.5:1 for normal text, 3:1 for large text and UI +component boundaries. Touch targets are at least 44x44 CSS px, and layouts work +at phone width without horizontal scroll. + +Correct: + +```tsx +; +``` + +Incorrect: + +```tsx +
×
; +``` + +--- + +## Use Existing Design Tokens + +Scope: Web UI styles + +Rule: Colors, spacing and typography come from the surface's existing tokens. In +noskills-web these are the custom properties in `static/style.css` (`--bg`, +`--bg-surface`, `--text`, ...). Do not write a literal color or size where a +token exists. Add a new token only when no existing one fits, and define it next +to the others. + +Correct: + +```css +.card { + background: var(--bg-surface); + color: var(--text); +} +``` + +Incorrect: + +```css +.card { + background: #161b22; + color: #e6edf3; +} +``` + +--- + +## Single Props Object Pattern + +Scope: React components + +Rule: Take one `props` parameter and read `props.name`; do not destructure in +the signature. + +Correct: + +```tsx +type UserProfileProps = { + userId: string; + showActions: boolean; +}; + +export function UserProfile(props: UserProfileProps) { + return ( +
+

User: {props.userId}

+ {props.showActions ? : null} +
+ ); +} +``` + +Incorrect: + +```tsx +export function UserProfile({ userId, showActions }: UserProfileProps) { + return

User: {userId}

; +} +``` + +**Why:** `props.` marks which values come from the parent, and adding a prop +does not touch the signature. + +--- + +## Named Exports for Components + +Scope: React components + +Rule: Components are named exports. Use a default export only in a file whose +framework contract requires one: laroux loads route, config and middleware +modules through their `default` export. + +Correct: + +```tsx +// user-profile.tsx +export function UserProfile(props: UserProfileProps) { + return
{props.name}
; +} +``` + +Incorrect: + +```tsx +function UserProfile(props: UserProfileProps) { + return
{props.name}
; +} +export default UserProfile; +``` + +--- + +## React Compiler Compatibility + +Scope: React 19 components + +Rule: Write plain idiomatic components and let the React compiler memoize. Add +`useMemo` or `useCallback` only when a profile shows the benefit, with a comment +that says so. + +Correct: + +```tsx +export function SortedList(props: { items: readonly Item[] }) { + const sorted = props.items.toSorted((a, b) => a.name.localeCompare(b.name)); + return
    {sorted.map((item) =>
  • {item.name}
  • )}
; +} +``` + +Incorrect: + +```tsx +const sorted = useMemo(() => props.items.toSorted(byName), [props.items]); // no profile +``` + +**Why:** manual memoization adds dependency arrays that go stale, and the +compiler already does it. diff --git a/.agents/skills/javascript-practices/references/syntax-and-types.md b/.agents/skills/javascript-practices/references/syntax-and-types.md new file mode 100644 index 000000000..b9c03ba1a --- /dev/null +++ b/.agents/skills/javascript-practices/references/syntax-and-types.md @@ -0,0 +1,264 @@ +# Runtime, Syntax and Types + +Cross-runtime APIs, value checks, conversions and async returns in TypeScript +code. Formatting and the basics (`const`, semicolons, `===`, no `eval`) are +enforced by `deno fmt` and the lint rules in the root `deno.json` (`eqeqeq`, +`no-eval`, `no-console`, `no-await-in-loop`, `no-top-level-await`, +`verbatim-module-syntax` and others); run them rather than restating them. + +## Contents + +- Cross-Runtime APIs +- Module Paths +- Global Object +- Explicit Checks +- Null vs Undefined +- Type Checking +- String to Number Conversion +- String Methods +- Array Iteration +- Iteration Over Optional Values +- Dangerous Features +- Consistent Async Returns + +--- + +## Cross-Runtime APIs + +Scope: All `@eserstack/*` packages + +Rule: File system, process, environment and path access goes through `runtime` +from `@eserstack/standards/cross-runtime` (`runtime.fs.readTextFile`, +`runtime.env.get`, `runtime.process.cwd`, `runtime.path.join`). Never call +`Deno.*` or read `process.env` in library code. CLI-only code may call `Deno.*` +when the abstraction does not expose the API yet; prefer adding it to +`cross-runtime` instead. + +Correct: + +```typescript +import { runtime } from "@eserstack/standards/cross-runtime"; + +const text = await runtime.fs.readTextFile(configPath); +const token = runtime.env.get("GITHUB_TOKEN"); +``` + +Incorrect: + +```typescript +const text = await Deno.readTextFile(configPath); // Deno only +const token = process.env.GITHUB_TOKEN; // Node only +``` + +**Why:** packages publish to JSR and npm and run on Deno, Node and Bun, and +tests replace `runtime` without touching the real file system. + +--- + +## Module Paths + +Scope: Resolving files that ship with a package + +Rule: Use `import.meta.dirname` for the module's directory, not +`dirname(fromFileUrl(import.meta.url))`. Package files resolve from +`import.meta.dirname`; project files resolve from a `projectRoot` parameter that +defaults to the working directory (coding-practices: No Machine-Specific +Assumptions). + +Correct: + +```typescript +const templatesDir = runtime.path.join(import.meta.dirname ?? ".", "templates"); +``` + +**Why:** `import.meta.dirname` works in Deno, Node 20.11+ and Bun without a URL +round trip. + +--- + +## Global Object + +Scope: Code that may run in a browser and on a server + +Rule: Use `globalThis`, not `window`. + +**Why:** `window` does not exist in Deno, Node or workers. + +--- + +## Explicit Checks + +Scope: Conditions and defaults + +Rule: Only booleans use implicit checks. Defaults use `??`, never `||`. The full +rule and examples are in coding-practices: Explicit Checks (CRITICAL). + +Correct: + +```typescript +const port = config.port ?? 8000; +if (items.length === 0) return; +``` + +Incorrect: + +```typescript +const port = config.port || 8000; // replaces a configured 0 +if (!items.length) return; +``` + +--- + +## Null vs Undefined + +Scope: Values that can be absent + +Rule: Represent "no value" with `null` in return types and fields you define. +Keep `undefined` for optional parameters and properties. + +Correct: + +```typescript +function findUser(id: string): User | null { + return users.get(id) ?? null; +} +``` + +**Why:** one absent-value marker per API makes `=== null` the only check a +caller needs. + +--- + +## Type Checking + +Scope: Runtime type checks + +Rule: Check object types with `instanceof` (or `Array.isArray`), not +`typeof x === "object"`. `typeof` is fine for primitives and for +`typeof x === "undefined"`. + +Correct: + +```typescript +if (value instanceof Date) {} +if (Array.isArray(value)) {} +``` + +Incorrect: + +```typescript +if (typeof value === "object") {} // also true for arrays and null +``` + +--- + +## String to Number Conversion + +Scope: Parsing numbers from strings + +Rule: Use `Number(text)` and check the result with `Number.isNaN`. Do not use +unary `+` or `parseInt` without a radix. + +Correct: + +```typescript +const raw = runtime.env.get("PORT") ?? "8000"; +const port = Number(raw); +if (Number.isNaN(port)) throw new Error(`PORT "${raw}" is not a number`); +``` + +--- + +## String Methods + +Scope: Taking parts of strings + +Rule: Use `slice()`, not `substring()` or the deprecated `substr()`. To take the +value after a known prefix, see coding-practices: Prefix Extraction. + +--- + +## Array Iteration + +Scope: Loops over arrays + +Rule: Use `for...of` for values and `for (const [i, item] of items.entries())` +when the index is needed. Use a classic `for` loop only when the index itself is +manipulated. + +--- + +## Iteration Over Optional Values + +Scope: Iterating a value that may be `undefined` + +Rule: Check for `undefined` before the loop instead of iterating a fallback +empty object or array. + +Correct: + +```typescript +if (props !== undefined) { + for (const [key, value] of Object.entries(props)) { + process(key, value); + } +} +``` + +Incorrect: + +```typescript +for (const [key, value] of Object.entries(props ?? {})) { + process(key, value); +} +``` + +**Why:** the condition stays visible, and no temporary object is created only to +loop over nothing. + +--- + +## Dangerous Features + +Scope: All code + +Rule: No `eval`, `new Function`, prototype changes or `Object.defineProperty` on +shared objects. The rule and its reasons are in coding-practices: No Runtime +Code Generation or Global Patching. + +--- + +## Consistent Async Returns + +Scope: Async functions + +Rule: Write `return await` when returning a promise from an async function. Do +not `await` a synchronous function (`no-sync-fn-in-async-fn` lint rule). Every +started promise is awaited or owned (coding-practices: Owned Asynchronous Work). + +Correct: + +```typescript +async function processData(): Promise { + try { + return await riskyOperation(); + } catch (error) { + return await fallbackOperation(error); + } +} +``` + +Incorrect: + +```typescript +async function processData(): Promise { + try { + return riskyOperation(); // a rejection skips the catch block + } catch (error) { + return fallbackOperation(error); + } +} +``` + +**Why:** without `await` a rejection escapes the surrounding `try`, and the +async frame is missing from the stack trace. It has no performance cost. diff --git a/.agents/skills/javascript-practices/references/testing.md b/.agents/skills/javascript-practices/references/testing.md new file mode 100644 index 000000000..f2ebb901b --- /dev/null +++ b/.agents/skills/javascript-practices/references/testing.md @@ -0,0 +1,44 @@ +# TypeScript Tests + +Test mechanics for TypeScript packages. What to test and at which level is in +architecture-guidelines: What to Test. + +--- + +## Test Files and Runner + +Scope: Tests under `pkg/@eserstack/*` + +Rule: + +- Name test files `.test.ts`, next to the module they test. A few older + files use `_test.ts`; do not add more. +- Both `Deno.test` and `@std/testing/bdd` (`describe`/`it`) are in use. Follow + the style of the existing tests in the package; a new package may pick either. +- Assert with `@std/assert`; test helpers and assertions are the named-import + exception in modules.md: Imports. +- Match the package's runner (`Deno.test` or bdd), not its import style: test + files use namespace imports like any other module. +- Run the suite with `deno task cli test` (defined in `.eser/manifest.yml`, with + the permissions and coverage flags the repository uses). For one file, + `deno test --allow-all path/to/file.test.ts`. +- Tests that touch files, env or processes go through `runtime` from + `@eserstack/standards/cross-runtime` and clean up temporary directories they + create. + +Correct: + +```typescript +import * as assert from "@std/assert"; +import * as bdd from "@std/testing/bdd"; +import * as slug from "./slug.ts"; + +bdd.describe("slug.toSlug", () => { + bdd.it("lowercases and joins words with hyphens", () => { + assert.assertEquals(slug.toSlug("Hello World"), "hello-world"); + }); +}); +``` + +**Why:** co-located `*.test.ts` files are what `deno test` discovers by default +and what reviewers look for next to a change. diff --git a/.agents/skills/release-and-ci/SKILL.md b/.agents/skills/release-and-ci/SKILL.md new file mode 100644 index 000000000..434d64925 --- /dev/null +++ b/.agents/skills/release-and-ci/SKILL.md @@ -0,0 +1,40 @@ +--- +name: release-and-ci +description: "Releases and CI for eserstack: the shared version of all packages, the release command, the tag-driven build.yml run, JSR and npm publishing, changelog and breaking changes, release recovery, GitHub Actions workflows, and Kubernetes and ArgoCD deploys. Use when bumping a version, cutting or recovering a release, writing changelog entries, editing a workflow, debugging CI or deploying." +--- + +# Releases and CI + +How eserstack versions, releases and publishes its packages, and how its +workflows and deployments run. + +## Always + +- Every package shares the version in `VERSION`; never edit a version by hand +- `deno task cli codebase release ` bumps, writes the + changelog, commits, pushes and pushes the tag; run it only when the user asks + for a release, on a clean, pushed tree after `deno task cli ok` and + `deno task cli preflight` +- The `v*.*.*` tag push starts the release in `build.yml`; CI never creates + tags. A version that reached JSR or npm is never retagged: resume the run or + cut a new patch +- Commit subjects are the changelog: user-facing, typed and scoped. Breaking + changes get `!`, a `### Migration` entry and a README example +- npm publishing is OIDC with provenance; no npm token exists +- Inspect before acting: `gh run view --log-failed`, `gh pr checks`, + read-only `kubectl` with an explicit namespace +- Workflow changes need the user's approval +- Every job sets `timeout-minutes`, pins actions to a major version tag and uses + the runner labels the pipeline already uses +- Never cancel runs on `main`, tags or resumed releases +- Workflow permissions are the ceiling and jobs narrow them; pull request checks + pass without secrets +- CI pushes images; ArgoCD Image Updater deploys them, with no tag commits + +## References + +| File | Read when | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| [release-process.md](references/release-process.md) | Cutting, verifying or recovering a release, choosing a bump, changelog or migration entries, publish targets | +| [github-actions.md](references/github-actions.md) | Editing a workflow or job, or debugging a CI run | +| [deployments.md](references/deployments.md) | Inspecting a cluster, deploying a service, or mapping tags to environments | diff --git a/.agents/skills/release-and-ci/evals/triggers.json b/.agents/skills/release-and-ci/evals/triggers.json new file mode 100644 index 000000000..a800da2e2 --- /dev/null +++ b/.agents/skills/release-and-ci/evals/triggers.json @@ -0,0 +1,110 @@ +[ + { + "query": "cut v4.6.0", + "should_trigger": true + }, + { + "query": "the release run failed at publish, resume it", + "should_trigger": true + }, + { + "query": "unrelease 4.5.3", + "should_trigger": true + }, + { + "query": "write the changelog entry for the breaking change in config", + "should_trigger": true + }, + { + "query": "yeni sürüm çıkaralım", + "should_trigger": true + }, + { + "query": "bump all package versions to 4.6.0", + "should_trigger": true + }, + { + "query": "the npm publish for noskills didn't happen", + "should_trigger": true + }, + { + "query": "how do we handle a hotfix on 4.5.x", + "should_trigger": true + }, + { + "query": "the build.yml run is stuck, find why", + "should_trigger": true + }, + { + "query": "add a timeout to the Go test job", + "should_trigger": true + }, + { + "query": "fork PRs fail because secrets are missing", + "should_trigger": true + }, + { + "query": "pin the actions in the workflow to SHAs", + "should_trigger": true + }, + { + "query": "deploy the service to Kubernetes and check the pods", + "should_trigger": true + }, + { + "query": "ArgoCD didn't pick up the new image", + "should_trigger": true + }, + { + "query": "CI neden kırmızı, bakar mısın", + "should_trigger": true + }, + { + "query": "which permissions does the workflow need", + "should_trigger": true + }, + { + "query": "update the README badge list", + "should_trigger": false + }, + { + "query": "write the README", + "should_trigger": false + }, + { + "query": "add a new package", + "should_trigger": false + }, + { + "query": "update dependencies", + "should_trigger": false + }, + { + "query": "fix a failing test", + "should_trigger": false + }, + { + "query": "commit these", + "should_trigger": false + }, + { + "query": "add a dependency", + "should_trigger": false + }, + { + "query": "fix the flaky Deno test locally", + "should_trigger": false + }, + { + "query": "design a new service", + "should_trigger": false + }, + { + "query": "commit and push", + "should_trigger": false + }, + { + "query": "run deno task cli ok", + "should_trigger": false + } +] diff --git a/.agents/skills/release-and-ci/references/deployments.md b/.agents/skills/release-and-ci/references/deployments.md new file mode 100644 index 000000000..acb957300 --- /dev/null +++ b/.agents/skills/release-and-ci/references/deployments.md @@ -0,0 +1,78 @@ +# Deployments + +Deploying services built with eserstack to Kubernetes with ArgoCD. Publishing +the packages themselves is release-process.md. + +--- + +## Inspect the Cluster Before Acting + +Scope: Kubernetes debugging and deployments + +Rule: Look before changing anything, with read-only commands, and name the +namespace every time: + +```bash +kubectl get pods -n +kubectl describe pod -n +kubectl logs deployment/ -n --tail=100 +kubectl get events -n --sort-by=.lastTimestamp +``` + +A change to a running cluster (apply, delete, scale, restart) is an action the +user confirms first. ConfigMaps and Secrets are synced from env files with +`@eserstack/cs`, not edited by hand. + +--- + +## Image Updates Go Through ArgoCD Image Updater + +Scope: Services deployed with ArgoCD + +Rule: CI builds and pushes a tagged image and stops there. ArgoCD Image Updater +notices the new tag and rolls it out. CI never commits image tag changes to the +manifests. + +```yaml +metadata: + annotations: + argocd-image-updater.argoproj.io/image-list: myapp=ghcr.io/owner/myapp + argocd-image-updater.argoproj.io/myapp.update-strategy: semver + argocd-image-updater.argoproj.io/myapp.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$ +``` + +Incorrect: + +```yaml +- name: Update image tag + run: | + sed -i "s/tag:.*/tag: $TAG/" k8s/kustomization.yaml + git commit -am "Update image to $TAG" && git push +``` + +**Why:** a commit per image update floods the history and races with other +pushes; the updater keeps the audit trail in ArgoCD and supports rollback. + +--- + +## Environment From the Tag + +Scope: Pipelines that deploy to several environments + +Rule: The tag decides the environment. A plain version tag deploys to +production, an `-rc` suffix to staging and a `-dev` suffix to test. Any other +tag fails the job instead of guessing. + +```bash +TAG="${GITHUB_REF#refs/tags/}" +if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "DEPLOY_ENV=production" >> "$GITHUB_ENV" +elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc ]]; then + echo "DEPLOY_ENV=stage" >> "$GITHUB_ENV" +elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-dev ]]; then + echo "DEPLOY_ENV=test" >> "$GITHUB_ENV" +else + echo "unrecognized tag format: $TAG" >&2 + exit 1 +fi +``` diff --git a/.agents/skills/release-and-ci/references/github-actions.md b/.agents/skills/release-and-ci/references/github-actions.md new file mode 100644 index 000000000..f510352a6 --- /dev/null +++ b/.agents/skills/release-and-ci/references/github-actions.md @@ -0,0 +1,88 @@ +# GitHub Actions + +Conventions for the workflows in `.github/workflows/` and for inspecting runs +with `gh`. The release chain inside `build.yml` is described in +release-process.md. + +--- + +## Workflows in This Repository + +Scope: Knowing what runs where + +| Workflow | Trigger | Purpose | +| ------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `build.yml` | push, pull_request, `v*` tags, manual | The Integrity Pipeline: validation, preflight on `main`, the full release on a tag | +| `publish-ajan.yml` | manual | Builds, or bootstraps, the `@eserstack/ajan-*` npm packages outside the pipeline | +| `codeql.yml` | push and pull_request to `main`, weekly | CodeQL analysis | +| `pr-labeler.yml` | pull_request | Labels pull requests from `.github/pr-labeler.yml` | +| `sync-issue-labels.yml` | push to `main` changing `.github/issue-labels.yml`, manual | Syncs the issue label set | +| `update-contributors.yml` | weekly, manual | Refreshes the contributors list | + +Changing any workflow is an Ask First action (workflow-practices: Ask First). + +--- + +## Inspect Before Acting + +Scope: Debugging CI, merging, re-running jobs + +Rule: Read the current state before changing anything. Use `gh`, not raw API +calls with a token. + +```bash +gh run list --workflow build.yml --limit 5 +gh run view --log-failed # only the failing steps +gh pr checks # before any merge +``` + +Re-run a failed release stage through `workflow_dispatch` with a tag instead of +re-pushing the tag (release-and-ci: Re-Release, Resume and Unrelease). + +--- + +## Job Settings + +Scope: Every job in every workflow + +Rule: + +- **Timeouts:** every job sets `timeout-minutes`, sized to that job rather than + a blanket default. `build.yml` uses 5 minutes for gate and update jobs, 10 to + 20 for validation and tests, and 30 for asset upload. +- **Runners:** use the labels the pipeline already uses: + `ubicloud-standard-*-arm` or `ubuntu-24.04-arm` for Linux work, `macos-latest` + and `windows-latest` only for jobs that need that platform. A new runner type + is a workflow change and needs approval. +- **Actions** are pinned to a major version tag (`actions/checkout@v7`), never + to a branch such as `@main`. +- **Concurrency:** superseded branch runs are cancelled; runs on `main`, on tags + and resumed release runs never are, because cancelling between `publish` and + `upload-assets` leaves a released version without binaries. +- **Manual entry:** a workflow that may need a re-run for an existing ref offers + `workflow_dispatch` with the inputs that run needs. + +**Why:** a job without a timeout holds a runner for six hours when it hangs, and +a cancelled release run cannot be rolled back. + +--- + +## Permissions Ceiling + +Scope: `permissions` in every workflow + +Rule: Workflow-level `permissions` is the ceiling and grants only what the +most-privileged job needs. Each job narrows it to what that job uses. In +`build.yml` the workflow grants `contents: write` and `id-token: write` for +releases and OIDC publishing, and jobs that only read declare `contents: read`. + +--- + +## Fork Pull Requests Pass Without Secrets + +Scope: Jobs that run on `pull_request` + +Rule: A pull request from a fork runs without repository secrets. Every job that +gates a pull request must pass without them. A step that needs a secret (the +Codecov upload in `validate`) is skipped or non-failing when the secret is +empty, and the checks that judge the code do not depend on it. diff --git a/.agents/skills/release-and-ci/references/release-process.md b/.agents/skills/release-and-ci/references/release-process.md new file mode 100644 index 000000000..7e85b748c --- /dev/null +++ b/.agents/skills/release-and-ci/references/release-process.md @@ -0,0 +1,284 @@ +# Release Process + +Cutting, verifying, recovering and describing releases of the synchronized +eserstack packages. + +## Contents + +- One Version for Every Package +- Cutting a Release +- Release Run +- Publishing Targets +- Post-Release Verification +- Hotfixes +- Breaking Changes +- Changelog Entries +- Re-Release, Resume and Unrelease + +--- + +## One Version for Every Package + +Scope: All package versions + +Rule: The 48 packages under `pkg/@eserstack/` (42 libraries and tools plus the +six `ajan-*` platform packages) share the version in `VERSION`. There are no +per-package releases. Never edit a `version` field by hand: the release command +bumps `VERSION` and every `package.json` together, and the per-package +`deno.json` files are generated from them. + +`deno task cli codebase versions ` bumps without releasing; +the release command already includes the bump, so it is rarely needed on its +own. + +| Bump | When | +| --------- | --------------------------------------------------------- | +| **patch** | Bug fixes, documentation, dependency updates, refactoring | +| **minor** | New features, non-breaking API additions, new packages | +| **major** | Breaking changes (see Breaking Changes) | + +--- + +## Cutting a Release + +Scope: Every release. An agent runs these commands only when the user asks for a +release. + +Rule: One command does the release; the steps around it make sure the tag it +pushes describes a tree that already passed CI. + +1. **Clean state:** `git status` is clean and pushed, and `deno task cli ok` + passes. The release command aborts on a dirty tree, and on unpushed commits + unless `--yes`. +2. **Preflight:** `deno task cli preflight` on the commit to tag. It runs the + release-only steps precommit skips: the three npm bundles, a packed install + of the CLI outside the repo, `deno publish --dry-run`, the native and wasm + ajan builds, the release `deno compile` for the host, and the Homebrew and + Nix scripts in dry-run mode. CI runs the same workflow on every push to + `main`, and `release-gate` requires it to have passed on the tagged tree. +3. **Preview:** + `deno task cli codebase release --dry-run` prints + the old and new version and whether a changelog section would be written. + Nothing changes. +4. **Release:** `deno task cli codebase release `. In + one run it bumps `VERSION` and every `package.json`, writes the + `CHANGELOG.md` section from the commits since the last tag, commits + `chore(codebase): release v`, pushes to `main`, then creates and + pushes the annotated tag `v`. +5. **Watch** the tag run at https://github.com/eser/stack/actions. + +`same` re-cuts a release at the current version; see Re-Release, Resume and +Unrelease for when that is still legal. + +--- + +## Release Run + +Scope: What the tag push triggers + +Rule: The `v*.*.*` tag push, not the release commit, starts the release. +`build.yml` then runs: + +``` +validate + └─ release-gate tag == VERSION, CHANGELOG.md has that section + ├─ smoke-test + │ └─ npm-no-deno-test + │ └─ publish JSR + npm bundles + │ └─ release-notes changelog section → GitHub Release + └─ build-ajan-darwin + └─ compile-binaries + └─ publish-ajan ajan-* platform packages on npm + └─ upload-assets cosign signatures + SHA256SUMS + ├─ update-homebrew + └─ update-nix-hashes +``` + +`release-gate` runs before anything is published, so a tag that disagrees with +`VERSION`, or a version without a changelog section, fails while every registry +is untouched. An ordinary push runs `validate`, `cross-runtime-test` and +`windows-smoke` (and `preflight` on `main`), and publishes nothing. + +**Why the CLI pushes the tag and CI does not:** a tag pushed with the workflow's +`GITHUB_TOKEN` does not dispatch another workflow (GitHub's recursion guard). A +tag created in CI would be a tag nothing reacts to, so the tag is pushed under +the developer's credentials. + +--- + +## Publishing Targets + +Scope: Where a release goes + +Rule: + +- **JSR:** the `publish` job runs `etc/scripts/gen-jsr-manifests.ts` and then + `deno publish`, which publishes every package whose generated `deno.json` has + a publish config. Authentication is OIDC; there is no JSR secret. +- **npm bundles:** three packages, `eser` (built by `deno task cli build` from + `pkg/@eserstack/cli`), `noskills` + (`pkg/@eserstack/noskills/scripts/npm-build.ts`) and `laroux` + (`pkg/@eserstack/laroux-server/scripts/npm-build.ts`). The `smoke-test` job + builds them; `publish` runs `pnpm publish --provenance --access public` for + each. +- **npm platform packages:** the six `@eserstack/ajan-*` packages carry the + prebuilt Go library. `pkg/@eserstack/ajan/scripts/build.ts` builds it and + `pkg/@eserstack/ajan/npm/generate-packages.ts` stages it into each package; + `publish-ajan` publishes them before the bundles need them. +- **npm authentication:** OIDC trusted publishing (`id-token: write`, + `--provenance`). There is no `NPM_TOKEN` or `NODE_AUTH_TOKEN`, and every npm + package needs a GitHub Actions trusted publisher configured on npm. npm cannot + attach one to a package that does not exist yet, so a brand-new package is + published once by hand from the artifacts of `publish-ajan.yml` in bootstrap + mode, then gets its trusted publisher. +- **Every publish step skips versions already on the registry**, so a stage can + be re-run safely. + +**Platform packages are pinned exactly.** `cli` and `ajan` declare the platform +packages with `workspace:*`. The eser bundle is compiled against the ABI of the +library built from the same commit, so its generated `dist/package.json` +resolves those specifiers to the exact released version +(`pkg/@eserstack/codebase/npm-workspace-specifiers.ts`). A floor range once let +`npx eser` load ajan 4.1.57 under eser 4.5.1 and fail on missing exports. + +--- + +## Post-Release Verification + +Scope: After every release + +Rule: Check that every target received the new version: + +- JSR: `jsr.io/@eserstack/` shows it. +- npm: `npm view eser version`, `npm view noskills version`, + `npm view laroux version` and one `@eserstack/ajan-*` package. +- GitHub: the `release-notes` job created the release and its body matches the + CHANGELOG section; the binaries, signatures and `SHA256SUMS` are attached. +- Homebrew and Nix: the update jobs succeeded. + +--- + +## Hotfixes + +Scope: An urgent fix for a released version + +Rule: `main` is the only long-lived branch. Fix on `main` (or on a short branch +merged into it), then release a patch from `main`. There is no dev branch to +merge back into. + +--- + +## Breaking Changes + +Scope: Any change to a public interface: package exports and their types, CLI +commands and flags, config keys, environment variables, persisted file formats, +the noskills client and daemon wire protocol, and the eser and ajan library ABI + +Rule: A change that makes an existing consumer change code, flags or files to +upgrade is breaking. For each one: + +1. Prefer deprecation first: keep the old name or flag working, mark it with + `@deprecated` in JSDoc (or a warning on the CLI) naming the replacement, and + remove it in a later release. +2. Mark the commit breaking (`feat(pkg)!:` or a `BREAKING CHANGE:` footer) so + the release is a major bump. +3. Add a before/after example under `### Migration` in the `Unreleased` section + of CHANGELOG.md, and the same example in the affected package README. "See + CHANGELOG" is not a migration guide. +4. State what happens when the two sides run different versions (an older daemon + with a newer client, an older ajan library under a newer eser), and make the + newer side either work or fail with an error that names the version to + install. + +Correct: + +```markdown +### Migration + +- `eser codebase scaffolding gh:owner/repo` is now + `eser kit clone gh:owner/repo` +- `--skip-post-install` is now `--no-post-install` +``` + +Incorrect: + +```markdown +### Changed + +- scaffolding moved to kit +``` + +`changelog-gen.ts` does not parse `!` or `BREAKING CHANGE` yet, so the +`### Migration` entry is written by hand. + +--- + +## Changelog Entries + +Scope: Commit subjects and CHANGELOG.md + +Rule: `changelog-gen.ts` builds each release section from the conventional +commits since the last tag. It collapses "take" series ("feat: x (take II)" with +"feat: x"), groups entries by Keep a Changelog section, and replaces an existing +section for the same version, so re-running it is safe. + +The commit subject is therefore the changelog entry. Write it from the user's +side: what changed for someone using the package, CLI or daemon, not which file +moved. Use the type that matches the effect (`feat`, `fix`, `perf`, `revert`) +and a scope naming the package. Anything a subject line cannot carry (removals, +migration steps, behavior notes) goes by hand into the `Unreleased` section of +CHANGELOG.md under `### Removed` or `### Migration`, in the same change. + +Correct: + +``` +fix(shell): keep quoted arguments intact when a value contains spaces +``` + +Incorrect: + +``` +fix: refactor tokenizer loop +``` + +--- + +## Re-Release, Resume and Unrelease + +Scope: Recovering a failed release + +Rule: A failed release is resumed, not re-tagged. Every publish step is +idempotent (`deno publish` and the npm steps skip versions already published, +release notes edit an existing GitHub Release, asset upload overwrites), so the +chain can run again for the same tag without harm. + +**Never retag a version that reached a registry.** JSR is immutable (yank only) +and npm answers a republish with 403. Any package that already published at a +version keeps that tree forever. + +- **Resume (preferred):** run the Integrity Pipeline by `workflow_dispatch` with + `tag` set to the existing tag and `stage` left at `post-publish`. Validation, + smoke tests and the registry publish are skipped; release notes, binaries, + assets, ajan platform packages, Homebrew and Nix run against the tag's tree. + Nothing touches git. `stage: full` also re-runs validation and the idempotent + publish, for example after configuring a missing npm trusted publisher. + + ```bash + gh workflow run "Integrity Pipeline" --ref main -f tag=v4.5.1 -f stage=post-publish + ``` + +- **Rerelease:** `deno task cli codebase rerelease` deletes the version tag and + recreates it at HEAD, re-firing the whole run. Use it only when the fix needed + a commit that changes nothing already published (tooling, workflows, an + unpublished package). +- **Unrelease:** `deno task cli codebase unrelease` deletes the tag and the + GitHub Release; the release commit stays in history. + `deno task cli codebase release same` then cuts a fresh release at the current + version. + +| Situation | Action | +| ------------------------------------------------------------ | ---------------------------------------------- | +| A stage after publish failed, no code change needed | Resume via `workflow_dispatch`, `post-publish` | +| Infrastructure error (runner, network, rate limit) | Resume, or re-run the failed jobs | +| Fix touches only tooling, workflow or an unpublished package | Commit, then `rerelease` at the same version | +| Fix touches a package already on JSR or npm at this version | Commit, then `release patch` | diff --git a/.agents/skills/security-practices/SKILL.md b/.agents/skills/security-practices/SKILL.md new file mode 100644 index 000000000..23607fa26 --- /dev/null +++ b/.agents/skills/security-practices/SKILL.md @@ -0,0 +1,35 @@ +--- +name: security-practices +description: "Security rules for eserstack in TypeScript and Go: secrets, output hygiene, input validation, authorization, injection, SSRF, error sanitization, httpfx hardening, tokens, passwords, cookies, crypto, untrusted files and archives, LLM trust. Use when handling secrets, input, auth, sessions, cookies, crypto, uploads, subprocesses, outbound URLs, prompts, production config or a security review." +--- + +# Security Practices + +Security rules that apply to every package and service in the repository. + +## Always + +- Secrets come from environment variables (`runtime.env` in TypeScript), never + from code or committed config +- Secrets and personal data never reach logs, errors, responses or traces +- Validate every input where it enters: HTTP, CLI, env, files, IPC, model output +- Every handler checks authorization itself and denies by default +- No command, path, SQL or HTML built from input strings; Go paths go through + `os.OpenRoot` +- Requests to user-supplied URLs refuse internal addresses at dial time +- Production errors carry a message, a code and a request id, never internals +- Tokens and ids come from a CSPRNG, never `Math.random` or `math/rand` +- Model output is untrusted input +- HTTP services compose the existing `httpfx/middlewares` (rate limit, body + size, auth, headers, CSP, CSRF, CORS) instead of new ones +- Tokens pin their algorithm and require expiry; passwords use Argon2id or + bcrypt 12+; crypto comes from the standard library or `x/crypto` + +## References + +| File | Read when | +| ----------------------------------------------------- | ----------------------------------------------------------------------------------- | +| [security-rules.md](references/security-rules.md) | Code that handles secrets, input, auth, subprocesses, paths, outbound URLs, prompts | +| [http-and-auth.md](references/http-and-auth.md) | HTTP services, middleware, tokens, passwords, cookies, redirects, debug endpoints | +| [crypto-and-files.md](references/crypto-and-files.md) | Encryption, hashing, TLS, uploads, archives, temp files, decoding untrusted data | +| [review-checklist.md](references/review-checklist.md) | Reviewing a change for security | diff --git a/.agents/skills/security-practices/evals/triggers.json b/.agents/skills/security-practices/evals/triggers.json new file mode 100644 index 000000000..fe5fc5e44 --- /dev/null +++ b/.agents/skills/security-practices/evals/triggers.json @@ -0,0 +1,66 @@ +[ + { + "query": "the webhook fetches any URL the user gives", + "should_trigger": true + }, + { + "query": "store the API key for the OpenAI client", + "should_trigger": true + }, + { + "query": "the error response includes the stack trace", + "should_trigger": true + }, + { + "query": "user input goes into a git command here", + "should_trigger": true + }, + { + "query": "generate a session token", + "should_trigger": true + }, + { + "query": "we put user text straight into the LLM prompt", + "should_trigger": true + }, + { + "query": "bu endpoint'te yetki kontrolü nerede yapılmalı", + "should_trigger": true + }, + { + "query": "pprof endpoints are exposed in production", + "should_trigger": true + }, + { + "query": "add a retry with backoff to the HTTP client", + "should_trigger": false + }, + { + "query": "fix the flaky test", + "should_trigger": false + }, + { + "query": "write a commit message", + "should_trigger": false + }, + { + "query": "choose a data structure for the cache", + "should_trigger": false + }, + { + "query": "make the README examples use namespace imports", + "should_trigger": false + }, + { + "query": "bump deno to the latest version", + "should_trigger": false + }, + { + "query": "the GitHub workflow needs a longer timeout", + "should_trigger": false + }, + { + "query": "refactor this function into smaller pieces", + "should_trigger": false + } +] diff --git a/.agents/skills/security-practices/references/crypto-and-files.md b/.agents/skills/security-practices/references/crypto-and-files.md new file mode 100644 index 000000000..52d2cc84d --- /dev/null +++ b/.agents/skills/security-practices/references/crypto-and-files.md @@ -0,0 +1,132 @@ +# Cryptography, Files and Untrusted Data + +Choosing cryptographic primitives, handling files from untrusted sources, and +decoding untrusted formats. Randomness is in security-rules.md: Secure +Randomness; path confinement in security-rules.md: No Injection Through Strings. + +## Contents + +- Use Vetted Primitives +- Hashing Personal Data +- Files From Untrusted Sources +- Decoding Untrusted Formats + +--- + +## Use Vetted Primitives + +Scope: Encryption, hashing, signatures and TLS, in Go and TypeScript + +Rule: Use the standard library or `golang.org/x/crypto` (already a dependency) +and never write a cipher, a mode or a token format by hand. Pick by purpose: + +| Purpose | Use | Never | +| ---------------------- | ------------------------------------------------- | ------------------------------------------- | +| Symmetric encryption | AES-256-GCM, ChaCha20-Poly1305 | ECB (raw `block.Encrypt`), DES, RC4 | +| Password storage | Argon2id, bcrypt (http-and-auth.md) | MD5, SHA-1, plain SHA-256 | +| Message authentication | HMAC-SHA256, checked with `hmac.Equal` | HMAC-MD5, `==` on MACs | +| Signatures | Ed25519, ECDSA P-256, RSA-PSS 2048+ | RSA keys under 2048 bits | +| Public-key encryption | `crypto/hpke`, RSA-OAEP | RSA PKCS #1 v1.5 encryption | +| Digests (non-secret) | SHA-256, `crypto/sha3` | MD5 or SHA-1 for anything security-relevant | +| Key derivation | `crypto/hkdf`, `crypto/pbkdf2` (600k+ iterations) | a bare hash of the secret | +| TLS | 1.2 minimum, 1.3 preferred, 1.3 for QUIC | `MinVersion` below 1.2 | + +- A GCM nonce is 12 random bytes per encryption from `crypto/rand`, stored with + the ciphertext. A repeated nonce under the same key breaks both + confidentiality and integrity. +- Compare secrets, tokens and MACs in constant time: + `subtle.ConstantTimeCompare` or `hmac.Equal`. +- `InsecureSkipVerify` is allowed only where a `VerifyPeerCertificate` pin + replaces chain validation, as `webtransport` does for certificate hashes, with + the reason in a `//nolint:gosec` comment. SSH clients verify host keys + (`ssh.FixedHostKey` or known_hosts), never `ssh.InsecureIgnoreHostKey`. +- An encryption error fails the operation. Continuing with plaintext after a + failed encrypt is the one error-handling mistake that leaks data by design. +- Encryption keys that must rotate wrap a random per-record data key with a key + encryption key (envelope encryption), so rotation re-encrypts only the data + keys. + +**Why:** a primitive chosen for the wrong purpose (a fast hash for passwords, a +MAC compared with `==`) fails as badly as a broken one, and reviewers catch it +more easily against a fixed table. + +--- + +## Hashing Personal Data + +Scope: Pseudonymous ids sent to analytics, traces or third parties + +Rule: A plain hash of an email or phone number is not anonymous: the input space +is small enough to reverse by guessing. When a stable pseudonym is needed, use +HMAC-SHA256 with a secret key from the environment, and prefer the internal user +id over any derived value. + +--- + +## Files From Untrusted Sources + +Scope: Uploads, archives, downloaded files and user-given paths + +Rule: + +- Confine every path to a root with `os.OpenRoot` and its methods, including + archive extraction: an entry named `../../etc/cron.d/x` must fail to open, not + be joined and cleaned. `os.Root` blocks traversal and escaping symlinks, but + it is not a sandbox; still reject special files (devices, named pipes) in + archives. +- Bound decompressed size. Wrap the reader in `io.LimitReader(r, max+1)` and + fail when more than `max` bytes arrive; a small archive can expand to + gigabytes. Bound the number of entries too. +- Create temporary files with `os.CreateTemp` (unpredictable name, mode 0600) + and remove them when done. +- Permissions: 0600 for files holding secrets or tokens, 0750 for directories, + 0644 only for data that is safe for every local user to read. Never 0666 or + 0777. + +Correct: + +```go +func extract(dst string, zr *zip.Reader, maxBytes int64) error { + root, err := os.OpenRoot(dst) + if err != nil { + return fmt.Errorf("open extraction root: %w", err) + } + defer root.Close() + + for _, entry := range zr.File { + if entry.FileInfo().IsDir() { + continue // directories are created on demand by copyEntry + } + if !entry.Mode().IsRegular() { + return fmt.Errorf("archive entry %q is not a regular file", entry.Name) + } + if err := copyEntry(root, entry, maxBytes); err != nil { + return fmt.Errorf("extract %q: %w", entry.Name, err) + } + } + return nil +} +``` + +**Why:** archive and upload handling turns attacker-chosen names and sizes into +file system operations; confinement and limits are the only checks that do not +depend on predicting every malicious name. + +--- + +## Decoding Untrusted Formats + +Scope: JSON, XML, gob, YAML and other decoders fed external input + +Rule: + +- Bound the input first (`http.MaxBytesReader`, `io.LimitReader`), then decode + into a concrete struct, never `any`, and validate the result + (security-rules.md: Input Validation). +- Never decode untrusted input with `encoding/gob`; its documentation says it is + not hardened against adversarial input. +- `encoding/xml` does not fetch external entities or expand DTDs, so XXE does + not apply to it; other XML libraries must have external entities off. +- Sizes and counts computed from input (`rows * cols`, a length prefix) are + checked against a maximum before allocating, so an overflow or a huge value + cannot allocate unbounded memory. diff --git a/.agents/skills/security-practices/references/http-and-auth.md b/.agents/skills/security-practices/references/http-and-auth.md new file mode 100644 index 000000000..63ab91a6c --- /dev/null +++ b/.agents/skills/security-practices/references/http-and-auth.md @@ -0,0 +1,186 @@ +# HTTP Services and Authentication + +How services built on `httpfx` are hardened: the middleware layers, tokens, +passwords, cookies and client identity. General rules (secrets, input, +injection, error sanitization) are in [security-rules.md](security-rules.md). + +## Contents + +- Layered Controls +- Client Identity Comes From the Server +- Tokens +- Passwords and PINs +- Cookies +- Security Headers +- Redirects and Debug Endpoints + +--- + +## Layered Controls + +Scope: Every HTTP service + +Rule: No single check protects a service, so each layer assumes the one before +it failed. Compose the existing `httpfx/middlewares` instead of writing new +ones: + +| Layer | Control | In this repository | +| ---------- | -------------------------------------- | -------------------------------------------------------------------------------- | +| Transport | Server timeouts, header size | `httpfx` config: read, write, idle, header timeouts | +| Transport | TLS 1.2+, TLS 1.3 for QUIC | `httpfx` HTTP/3 service, `webtransport` | +| Volume | Rate limits, per client or per user | `RateLimitMiddleware` with `WithRateLimiterIPKeyFunc` or user | +| Volume | Request body limit | `RequestSizeLimitMiddleware`, `max_request_size_mb` | +| Identity | Token or PIN check on every request | `AuthMiddleware`, `PinAuthMiddleware` | +| Permission | Per-handler authorization | security-rules.md: Authorization in the Handler | +| Browser | Security headers, CSP, CSRF, CORS | `SecurityHeadersMiddleware`, `CspMiddleware`, `CsrfMiddleware`, `CorsMiddleware` | +| Data | Encryption and least-privilege storage | crypto-and-files.md | + +Rate-limit login, PIN and token endpoints and expensive operations (search, +export, upload) more tightly than the rest. A per-client limiter keeps one entry +per key, so it must evict idle keys (`RateLimitMiddleware` does); a map that +only grows is itself a memory exhaustion hole (coding-practices: Bound Every +Resource). + +**Why:** a missing check in one layer, such as a handler that forgot its +authorization, is then bounded by the others instead of becoming a breach. + +--- + +## Client Identity Comes From the Server + +Scope: Anything that reads the client's address, identity or role + +Rule: Identity and roles come from a verified token or session, never from a +request header the client can set (`X-Is-Admin`, `X-User-Id`). The client +address comes from `ResolveAddressMiddleware` with +`WithTrustedProxies(httpfx.NewTrustedProxies(...))`: `X-Forwarded-For` is +believed only from the listed proxies, and without the option the socket peer +wins. Hidden URLs and client-side checks are not access control. + +Correct: + +```go +claims, ok := ctx.Request.Context().Value(middlewares.ContextKeyAuthClaims).(jwt.MapClaims) +if !ok || !hasRole(claims, "admin") { + return ctx.Results.Error(http.StatusForbidden) +} +``` + +Incorrect: + +```go +if ctx.Request.Header.Get("X-Is-Admin") == "true" { // any client can send this + return adminPanel(ctx) +} +``` + +--- + +## Tokens + +Scope: Issuing and validating JWTs and other bearer tokens + +Rule: Validate a token completely before trusting any claim: + +- Pin the signing algorithm with `jwt.WithValidMethods([]string{"HS256"})` (or + the RSA or EdDSA method in use). Checking the method's type in the key + function also works; accepting whatever `alg` the token names does not. +- Require expiry with `jwt.WithExpirationRequired()`, and check issuer and + audience with `jwt.WithIssuer` and `jwt.WithAudience`. A token without `exp` + must fail, not pass. +- Keep access tokens short-lived (minutes) and refresh tokens revocable on the + server. +- Signing keys come from the environment (security-rules.md: Secrets Come From + the Environment) and differ per environment, so a staging leak does not open + production. + +Correct: + +```go +token, err := jwt.Parse(raw, keyFunc, + jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), + jwt.WithExpirationRequired(), + jwt.WithIssuer(cfg.Issuer), + jwt.WithAudience(cfg.Audience), +) +if err != nil { + return ctx.Results.Unauthorized(httpfx.WithPlainText("invalid token")) +} +``` + +**Why:** an unpinned algorithm lets an attacker pick one the server did not +intend, and an optional expiry turns a leaked token into a permanent one. + +--- + +## Passwords and PINs + +Scope: Storing and checking user secrets + +Rule: Store only a slow, salted hash. Use Argon2id +(`golang.org/x/crypto/argon2`, `IDKey` with at least 64 MiB memory, 3 passes, a +16-byte salt from `crypto/rand`) for new storage, or bcrypt with cost 12 or +more, as `noskillsserverfx/auth.go` does for PINs. Compare through the library +(`bcrypt.CompareHashAndPassword`) or `subtle.ConstantTimeCompare`, never with +`==`. Rate-limit the endpoints that check them. Fast hashes (SHA-256, MD5) are +never password storage. + +**Why:** a stolen hash database is attacked offline; only a deliberately slow, +memory-hard hash keeps that attack expensive. + +--- + +## Cookies + +Scope: Session, auth and CSRF cookies + +Rule: Session and auth cookies are `HttpOnly`, `Secure` and `SameSite=Lax` (or +`Strict` for sensitive actions), with `Path: "/"`, no `Domain`, and a bounded +`MaxAge`. Prefer the `__Host-` name prefix, which the browser only accepts with +exactly those settings. `SameSite=None` needs `Secure` and a reason in a +comment. The one exception here is the double-submit CSRF cookie of +`CsrfMiddleware`: it is readable by JavaScript on purpose, so it never carries a +credential. Clear session cookies on logout and invalidate the session on the +server too. + +```go +http.SetCookie(w, &http.Cookie{ + Name: "__Host-session", + Value: sessionID, + Path: "/", + MaxAge: int((8 * time.Hour).Seconds()), + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteLaxMode, +}) +``` + +--- + +## Security Headers + +Scope: Responses from services that serve browsers + +Rule: Mount `SecurityHeadersMiddleware` (sets `X-Content-Type-Options: nosniff`, +`X-Frame-Options: DENY` and HSTS) and `CspMiddleware` with the narrowest policy +the pages need (`default-src 'self'`, `frame-ancestors 'none'`, +`object-src 'none'`). Neither currently sets `Referrer-Policy` +(`strict-origin-when-cross-origin`) or `Permissions-Policy`; a service that +needs them sets them in its own middleware until they are added there. Send HSTS +only on HTTPS responses. + +--- + +## Redirects and Debug Endpoints + +Scope: Redirect targets and diagnostic handlers + +Rule: + +- Redirect only to relative paths or to hosts on an allowlist. Parse the target + with `url.Parse` and compare the host exactly; a prefix or substring check + lets `https://example.com.evil.test` through. +- `/debug/pprof/` and similar endpoints reveal memory and stacks. Mount the + `httpfx` profiling module only behind `PPROF_TOKEN`, or on a separate listener + bound to `127.0.0.1`. Services in containers still listen on all interfaces + for their public port; debug listeners do not. diff --git a/.agents/skills/security-practices/references/review-checklist.md b/.agents/skills/security-practices/references/review-checklist.md new file mode 100644 index 000000000..6a83f0c5c --- /dev/null +++ b/.agents/skills/security-practices/references/review-checklist.md @@ -0,0 +1,64 @@ +# Security Review Checklist + +A checklist for reviewing a change, ordered by severity. Each line points to the +rule that explains it; review the lines that the change touches. + +--- + +## Critical + +- [ ] No secret, key or password in code, config, fixtures or logs + (security-rules.md: Secrets Come From the Environment) +- [ ] SQL uses placeholders; commands take separate arguments; no `sh -c` with + input (security-rules.md: No Injection Through Strings) +- [ ] No `eval`, dynamic `Function`, or `gob` on untrusted input + (crypto-and-files.md: Decoding Untrusted Formats) +- [ ] GCM nonces are random per encryption, never reused (crypto-and-files.md: + Use Vetted Primitives) + +## High + +- [ ] Every privileged handler checks authorization itself (security-rules.md: + Authorization in the Handler) +- [ ] Tokens: algorithm pinned, expiry required, issuer and audience checked + (http-and-auth.md: Tokens) +- [ ] Passwords and PINs stored with Argon2id or bcrypt 12+ (http-and-auth.md: + Passwords and PINs) +- [ ] Identity and client address come from the server, not client headers + (http-and-auth.md: Client Identity Comes From the Server) +- [ ] User paths and archive entries confined with `os.OpenRoot` + (crypto-and-files.md: Files From Untrusted Sources) +- [ ] Outbound requests to user URLs refuse internal addresses at dial time + (security-rules.md: Outbound Requests to User-Supplied URLs) +- [ ] Tokens and ids come from `crypto/rand` or `crypto.getRandomValues` + (security-rules.md: Secure Randomness) +- [ ] TLS 1.2+, no `InsecureSkipVerify` without a pin (crypto-and-files.md: Use + Vetted Primitives) +- [ ] Prompts keep external text in a data section; model output is validated + (security-rules.md: LLM Trust Boundary) +- [ ] `go test -race` passes for code with shared state (go-practices testing) +- [ ] `go tool govulncheck ./...` reports nothing reachable + +## Medium + +- [ ] Input validated at the boundary with allowlists and length limits + (security-rules.md: Input Validation) +- [ ] Body size, rate limits and server timeouts in place (http-and-auth.md: + Layered Controls) +- [ ] Responses to clients carry no stack traces, SQL or internal paths + (security-rules.md: Error Sanitization) +- [ ] No secret or personal data in logs, traces or third-party events + (security-rules.md: Secrets and Personal Data Never Reach Output) +- [ ] Cookies `HttpOnly`, `Secure`, `SameSite`; CSRF protection on + state-changing requests (http-and-auth.md: Cookies) +- [ ] Redirect targets relative or allowlisted (http-and-auth.md: Redirects and + Debug Endpoints) +- [ ] Decompressed and computed sizes bounded (crypto-and-files.md: Files From + Untrusted Sources) + +## Low + +- [ ] Security headers and CSP mounted for browser-facing services + (http-and-auth.md: Security Headers) +- [ ] File permissions no wider than needed (crypto-and-files.md: Files From + Untrusted Sources) diff --git a/.agents/skills/security-practices/references/security-rules.md b/.agents/skills/security-practices/references/security-rules.md new file mode 100644 index 000000000..f0e67fcd7 --- /dev/null +++ b/.agents/skills/security-practices/references/security-rules.md @@ -0,0 +1,313 @@ +# Security Rules + +Secrets, input handling, authorization, injection, outbound requests and +production settings, in TypeScript and Go. + +## Contents + +- Secrets Come From the Environment +- Secrets and Personal Data Never Reach Output +- Secure Randomness +- Input Validation +- Authorization in the Handler +- No Injection Through Strings +- Outbound Requests to User-Supplied URLs +- Error Sanitization +- Production Mode and Secure Defaults +- LLM Trust Boundary + +--- + +## Secrets Come From the Environment + +Scope: API keys, tokens, passwords, connection strings + +Rule: Secrets are read from environment variables at start-up and never live in +code, committed config files or fixtures. A missing required secret stops +start-up with an error that names the variable. TypeScript reads the environment +through `runtime.env` from `@eserstack/standards/cross-runtime`, not +`process.env` or `Deno.env`, so the code runs on every supported runtime. +Provider keys in this repository follow the provider's own names +(`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`), as `pkg/ajan/aifx/config.go` reads +them. + +```typescript +import { runtime } from "@eserstack/standards/cross-runtime"; + +const apiKey = runtime.env.get("OPENAI_API_KEY"); +if (apiKey === undefined || apiKey === "") { + throw new Error("OPENAI_API_KEY is not set; export it or add it to .env"); +} +``` + +```go +apiKey := os.Getenv("OPENAI_API_KEY") +if apiKey == "" { + return errors.New("OPENAI_API_KEY is not set; export it or add it to .env") +} +``` + +Never: + +```json +{ "openaiApiKey": "sk-..." } +``` + +--- + +## Secrets and Personal Data Never Reach Output + +Scope: Logs, error messages and their `cause`, response bodies, serialized +objects, CLI output, trace and metric attributes, in TypeScript and Go + +Rule: Tokens, passwords, API keys, private keys and personal data (emails, +names, IP addresses) never appear in any output. Log and trace by id, and log a +secret's key name or a fixed mask, never its value. Types that hold a secret +redact it when serialized or printed (`toJSON`, +`[Symbol.for("Deno.customInspect")]`, Go `String()`/`MarshalJSON`). Check new +log lines and error messages for accidental exposure. + +- User input goes into log attributes, never into the message string. The + structured handlers escape attribute values, so a value with a newline or a + forged `level=` field cannot fake a log entry; a message built with + `fmt.Sprintf` from input can. +- The same applies to every external sink: OpenTelemetry attributes, error + trackers, analytics. Send ids and outcomes, and strip `Authorization`, + `Cookie` and request bodies before an event leaves the process. A new + third-party sink needs this reviewed before it is wired in. + +Correct: + +```typescript +logger.info("provider configured", { + provider: "openai", + apiKey: "[redacted]", +}); +logger.info("user created", { userId, traceId }); +``` + +Incorrect: + +```typescript +throw new Error(`auth failed for key ${apiKey}`); +logger.info("user created", { email, apiKey }); +``` + +--- + +## Secure Randomness + +Scope: Tokens, session ids, keys, nonces, passwords, PKCE verifiers, in +TypeScript and Go + +Rule: Anything an attacker must not guess comes from a cryptographically secure +generator: `crypto.getRandomValues` or `crypto.randomUUID` in TypeScript, +`crypto/rand` in Go. `Math.random` and `math/rand` are predictable from their +output. They remain fine for jitter, sampling and shuffling test data. + +```typescript +const token = crypto.getRandomValues(new Uint8Array(32)); +``` + +```go +token := make([]byte, 32) +if _, err := rand.Read(token); err != nil { // crypto/rand + return fmt.Errorf("generate token: %w", err) +} +``` + +--- + +## Input Validation + +Scope: All external inputs: HTTP params, headers, cookies and bodies, file +uploads, CLI arguments and flags, environment variables, config files and other +files read from disk, webhook payloads, OAuth callbacks, websocket and daemon +IPC messages, and output from subprocesses or AI models + +Rule: Validate each input at the boundary where it enters, before it reaches +business logic, and fail fast with a message that names the field and the +expected form. When adding an entry point, name its inputs and the validation +for each in the change. Validation of internal invariants (assertions) belongs +to coding-practices. + +```go +var req CreateSessionRequest +if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&req); err != nil { + http.Error(w, "request body must be a JSON object under 1 MiB", http.StatusBadRequest) + return +} +if !sessionNamePattern.MatchString(req.Name) { + http.Error(w, `name must match ^[a-z0-9-]{1,64}$`, http.StatusBadRequest) + return +} +``` + +--- + +## Authorization in the Handler + +Scope: Every HTTP route, daemon RPC, WebTransport or websocket action, in +TypeScript and Go + +Rule: A new endpoint states who may call it. Each handler checks, on the server, +that the caller may perform this action on this resource, even when middleware +already authenticated the request. Deny by default: a handler that cannot +establish authorization denies, and an endpoint without an explicit check is a +defect, not a public endpoint. Tests cover the denial paths: unauthenticated, +wrong user and wrong role. + +Correct: + +```go +if !session.CanAccess(principal, sessionID) { + return ErrForbidden +} +``` + +Incorrect: + +```go +// auth middleware ran, so any session id is fine +return store.Get(sessionID) +``` + +--- + +## No Injection Through Strings + +Scope: Subprocesses, file paths, SQL, and HTML or markdown rendering built from +user, config, network or model input, in TypeScript and Go + +Rule: Never build a command, path, query or markup by concatenating input. + +- Subprocesses: interpolate values into `@eserstack/shell/exec` (each value + becomes one argument) or pass an argument array (Go + `exec.Command(name, args...)`). Never run `sh -c` with a string that contains + input. +- Paths: resolve a supplied path against an allowed root and reject anything + that escapes it (`..`, absolute paths, symlinks out of the root). In Go, open + the root with `os.OpenRoot` (Go 1.24+) and use its methods, which enforce this + at the system-call level instead of by string checks. +- SQL: parameterized queries only. +- HTML: server-rendered output escapes input. `dangerouslySetInnerHTML` and raw + markdown-to-HTML output need a sanitizer and a comment saying why. + +Correct: + +```typescript +import * as shell from "@eserstack/shell"; + +await shell.exec.exec`git log --format=%H -- ${userPath}`.text(); +``` + +Incorrect: + +```typescript +const command = `git log --format=%H -- ${userPath}`; +await shell.exec.exec`sh -c ${command}`.text(); // userPath reaches the shell parser +``` + +--- + +## Outbound Requests to User-Supplied URLs + +Scope: Webhooks, callbacks, fetchers and any request whose URL comes from input + +Rule: Before connecting, resolve the host and refuse loopback, private, +link-local and unspecified addresses, for IPv4 and IPv6. Check the address the +connection actually dials, not a separate lookup done earlier, so a DNS answer +that changes between check and connect (rebinding) cannot bypass it, and apply +the same check to every redirect. Require `https` outside development. + +In Go, check in the dialer, with `net/netip`: + +```go +dialer := &net.Dialer{ + Control: func(network, address string, _ syscall.RawConn) error { + addrPort, err := netip.ParseAddrPort(address) + if err != nil { + return fmt.Errorf("parse dial address %q: %w", address, err) + } + ip := addrPort.Addr().Unmap() + if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() { + return fmt.Errorf("refusing to connect to internal address %s", ip) + } + return nil + }, +} +``` + +--- + +## Error Sanitization + +Scope: Error responses of APIs, the daemon and CLI commands that talk to +services + +Rule: In production, responses never carry stack traces, internal messages, SQL +or request echoes. Translate each internal error at the boundary into a message +the user can act on plus a stable error code and a request id, and log the +technical detail under the same request id. + +Correct: + +```typescript +logger.error("request failed", { requestId, error }); + +return { + error: "The request could not be completed. Retry, or report this id.", + code: "INTERNAL_ERROR", + requestId, +}; +``` + +Incorrect: + +```typescript +res.status(500).json({ + error: error.message, + stack: error.stack, + query: req.query, +}); +``` + +--- + +## Production Mode and Secure Defaults + +Scope: Services and servers built on `httpfx` or the TypeScript HTTP packages + +Rule: The environment name comes from `ENV`, `APP_ENV`, `DENO_ENV` or `NODE_ENV` +(the order `@eserstack/config` reads them), and falls back to `development` when +none is set. A deployment therefore sets it explicitly, and a relaxation that +weakens security (unauthenticated profiling, plain HTTP webhooks, detailed +errors) needs its own opt-in setting, not only the environment name. In +production: + +- profiling and debug endpoints are off, or behind a token. The `httpfx` + profiling module only logs a warning when `PPROF_TOKEN` is unset and leaves + the endpoints open, so set it wherever the module is mounted; +- errors are sanitized (Error Sanitization); +- rate limits and request size limits are set (`httpfx` config + `rate_limit_requests` and `max_request_size_mb`); +- outbound connections use TLS; +- cookies are `HttpOnly`, `Secure` and `SameSite`; +- tokens are short-lived and checked for issuer, audience and expiry; +- CORS lists allowed origins explicitly and never reflects the request's + `Origin`; an empty list allows none. + +**Why:** the environment name defaults to `development`, so a check keyed on the +name alone is relaxed in any deployment that forgot to set it. + +--- + +## LLM Trust Boundary + +Scope: Code that builds prompts or consumes model output (`@eserstack/ai`, +`aifx`, noskills agents) + +Rule: User or external content in a prompt goes in a clearly delimited data +section, never in the instruction text. Model output is untrusted input: +validate it against a schema before use, and never pass it to a shell, a file +path, SQL or `eval` without the same checks as any other external input. diff --git a/.agents/skills/tooling-standards/SKILL.md b/.agents/skills/tooling-standards/SKILL.md new file mode 100644 index 000000000..4e217d119 --- /dev/null +++ b/.agents/skills/tooling-standards/SKILL.md @@ -0,0 +1,35 @@ +--- +name: tooling-standards +description: "Tooling conventions for repositories on the eserstack toolchain: pnpm installs and Deno runs, package.json over generated deno.json, JSR over npm, workspace dependencies, vetting new dependencies, manifest scripts, editor settings, file naming, license headers, adding a workspace package. Use when adding a dependency, package or project command, editing package config, or naming or creating files." +--- + +# Tooling Standards + +Tools, configuration files, dependencies and repository conventions shared by +every repository on the eserstack toolchain. Repository-specific facts (paths, +generators, command list) are in `AGENTS.md`. + +## Always + +- pnpm installs; Deno formats, lints, checks and tests; no npm, npx or + `deno install` +- `package.json` is the source of truth; per-package `deno.json` is generated, + so never edit it; run the generator instead; extra JSR fields go in + `deno.extra.json` +- Internal dependencies use `workspace:*`, external ones plain semver or + `npm:@jsr/...`; never `catalog:` +- Prefer JSR packages; npm only when no JSR package exists +- Ask before adding any dependency, with purpose, license, advisories, health + and exit cost written down +- Project commands live in `.eser/manifest.yml` and run through the `eser` CLI +- Files are kebab-case, Go files snake_case; every source file starts with the + license header, copied from an existing file +- Do not swap or re-wire existing tools without the owner's approval + +## References + +| File | Read when | +| --------------------------------------------------------- | ------------------------------------------------------- | +| [deno-and-packages.md](references/deno-and-packages.md) | Editing package config, choosing or adding a dependency | +| [scripts-and-editor.md](references/scripts-and-editor.md) | Adding a project command, changing editor settings | +| [repo-conventions.md](references/repo-conventions.md) | Naming files, license headers, creating a package | diff --git a/.agents/skills/tooling-standards/evals/triggers.json b/.agents/skills/tooling-standards/evals/triggers.json new file mode 100644 index 000000000..4c5537c48 --- /dev/null +++ b/.agents/skills/tooling-standards/evals/triggers.json @@ -0,0 +1,74 @@ +[ + { + "query": "add zod as a dependency to @eserstack/config", + "should_trigger": true + }, + { + "query": "where do I put a new project command like `docs-lint`", + "should_trigger": true + }, + { + "query": "jsr or npm for this package", + "should_trigger": true + }, + { + "query": "the per-package deno.json is out of sync with package.json", + "should_trigger": true + }, + { + "query": "set up VS Code settings for the repo", + "should_trigger": true + }, + { + "query": "should this script live in package.json or the manifest", + "should_trigger": true + }, + { + "query": "yeni bir kütüphane eklemek istiyorum, nasıl değerlendiririz", + "should_trigger": true + }, + { + "query": "which tool runs formatting, deno or prettier", + "should_trigger": true + }, + { + "query": "add a new package pkg/@eserstack/queue", + "should_trigger": true + }, + { + "query": "which files need the license header", + "should_trigger": true + }, + { + "query": "what's the kebab-case rule for file names here", + "should_trigger": true + }, + { + "query": "publish to npm", + "should_trigger": false + }, + { + "query": "write Go lint rules", + "should_trigger": false + }, + { + "query": "fix the CI cache", + "should_trigger": false + }, + { + "query": "write a unit test", + "should_trigger": false + }, + { + "query": "the import uses a named import", + "should_trigger": false + }, + { + "query": "tag v4.6.0", + "should_trigger": false + }, + { + "query": "make the logger structured", + "should_trigger": false + } +] diff --git a/.agents/skills/tooling-standards/references/deno-and-packages.md b/.agents/skills/tooling-standards/references/deno-and-packages.md new file mode 100644 index 000000000..b5329c745 --- /dev/null +++ b/.agents/skills/tooling-standards/references/deno-and-packages.md @@ -0,0 +1,138 @@ +# Deno, pnpm and Packages + +Which tool does what, where package configuration lives, and how dependencies +are chosen and declared. + +## Contents + +- Tooling Preference +- Configuration Files +- Registry Preference +- Adding a Dependency + +--- + +## Tooling Preference + +Scope: JS/TS work in repositories on the eserstack toolchain + +Rule: pnpm installs dependencies; Deno runs, formats, lints, type-checks and +tests. Do not use `npm install`, `deno install` or `npx`. Do not replace a tool +the project already uses (Deno, pnpm, Go, Make) with another one, or change how +a tool is invoked, without the owner's approval. + +Correct: + +```bash +pnpm install +pnpm add react --filter @scope/web +deno fmt +deno lint +deno task cli test +``` + +Incorrect: + +```bash +npm install +npx vitest +``` + +**Why:** module resolution runs through pnpm's workspace symlinks in +`node_modules`, and every validation step is a Deno command; a second package +manager produces a different tree. + +--- + +## Configuration Files + +Scope: Root and per-package configuration + +Rule: `package.json` is the source of truth for each package's name, exports and +dependencies. A package's `deno.json` is a generated JSR publish manifest; never +edit it, and when you change `package.json` run the repository's generator +(named in `AGENTS.md`) instead of mirroring the change by hand. Fields +`package.json` cannot express (`publish.include`, `tasks`) go in the package's +`deno.extra.json`. + +| File | Holds | +| ------------------------- | --------------------------------------------------------- | +| Root `package.json` | `workspaces` and the CLI entry scripts only | +| `pnpm-workspace.yaml` | Workspace packages and pnpm settings | +| Root `deno.json` | Lint, fmt, `unstable` flags, excludes; no `workspace` key | +| Package `package.json` | `name`, `type: module`, `exports`, dependencies | +| Package `deno.extra.json` | Extra JSR fields merged into the generated `deno.json` | +| Package `deno.json` | Generated; do not edit | + +Internal dependencies use `workspace:*`; external ones use plain semver or the +`npm:@jsr/` alias. Never use `catalog:`, which only pnpm understands. After +adding a package, run `pnpm install` at the root to link it. + +Correct: + +```json +{ + "name": "@scope/my-pkg", + "type": "module", + "exports": { ".": "./mod.ts", "./sub": "./sub.ts" }, + "dependencies": { + "@scope/primitives": "workspace:*", + "@std/path": "npm:@jsr/std__path@^1.1.6" + } +} +``` + +Incorrect: + +```json +{ + "dependencies": { + "@scope/primitives": "catalog:", + "@std/path": "^1.0.0" + } +} +``` + +**Why:** the exports map used to live in two files and drifted; generating +`deno.json` keeps one source. + +--- + +## Registry Preference + +Scope: Choosing where a dependency comes from + +Rule: Prefer a JSR package when one exists; fall back to npm. JSR packages are +declared through the npm compatibility registry: `.npmrc` sets +`@jsr:registry=https://npm.jsr.io`, and the dependency is written as +`"@std/path": "npm:@jsr/std__path@^1.1.6"`. npm-only packages (`react`) use +plain semver. + +**Why:** JSR packages ship TypeScript source and work in Deno and Node through +the same `package.json` entry. + +--- + +## Adding a Dependency + +Scope: Every new JSR, npm or Go module dependency + +Rule: A new dependency is untrusted code and a long-term liability, and +workflow-practices already requires asking first. Before proposing one, write +down: + +- Purpose, and why the standard library (`@std/*`, Go stdlib) or an existing + workspace package cannot do it. If about 50 lines of our own code would do it, + write the code. +- License: compatible with the repository's license (Apache-2.0 here). GPL, LGPL + or AGPL dependencies need explicit approval from the owner. +- Advisories: no open advisory in the registry's advisory data or the Go + vulnerability database. +- Health: an identifiable maintainer and recent releases. Its install scripts + and entry point contain nothing unexpected. New packages with install scripts + also need an `allowBuilds` entry in `pnpm-workspace.yaml`, with the reason. +- Exit cost: what breaks if the package disappears, and how much code touches + it. + +"It looked fine" is not evidence. Prefer established, widely used packages over +new ones. diff --git a/.agents/skills/tooling-standards/references/repo-conventions.md b/.agents/skills/tooling-standards/references/repo-conventions.md new file mode 100644 index 000000000..4b7973e34 --- /dev/null +++ b/.agents/skills/tooling-standards/references/repo-conventions.md @@ -0,0 +1,57 @@ +# Repository Conventions + +File naming, license headers and new workspace packages, as the `eser` +validators enforce them in every repository that uses the toolchain. + +--- + +## File Naming + +Scope: Every file in the repository + +Rule: Files are kebab-case (`http-client.test.ts`, `parse-args.bench.ts`, +`user-profile.tsx`); components are kebab-case too, never PascalCase. Go code is +snake_case (`session_store.go`) in the directories the repository lists as Go +paths. The `validate-filenames` step in `.eser/manifest.yml` enforces both, and +its config is where the Go paths are listed. `.agents/` is excluded because +`SKILL.md` is a fixed name. + +**Why:** one casing rule avoids case-only renames that break on case-insensitive +file systems, which `validate-case-conflict` also guards. + +--- + +## License Headers + +Scope: Every `.ts`, `.tsx` and `.go` source file + +Rule: The first line is the license header: + +```typescript +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. +``` + +Copy it from an existing file; never retype it. `validate-licenses` checks it, +and `--fix` inserts missing headers. + +--- + +## Adding a Workspace Package + +Scope: Creating a new package in the workspace + +Rule: + +1. Start from the library recipe in `.eser/recipes/` (the repository's + `AGENTS.md` names it). +2. Write `package.json` with the name, `type: module`, exports and `workspace:*` + internal dependencies. Do not write `deno.json` by hand. +3. Give every source file the license header. `mod.ts` is the entry point and + re-exports submodules as namespaces + (`export * as results from + "./results.ts"`); a CLI's entry point is + `main.ts`; there is no `index.ts`. +4. Add `mod.test.ts` and a README (documentation-conventions: Package README + Structure). +5. Run `pnpm install` at the root to link it, then the full gate + (`deno task cli ok`), which regenerates `deno.json` and validates everything. diff --git a/.agents/skills/tooling-standards/references/scripts-and-editor.md b/.agents/skills/tooling-standards/references/scripts-and-editor.md new file mode 100644 index 000000000..7dcf60cef --- /dev/null +++ b/.agents/skills/tooling-standards/references/scripts-and-editor.md @@ -0,0 +1,55 @@ +# Scripts and Editor Settings + +Where project commands are defined and which editor settings are shared. + +--- + +## Scripts Location + +Scope: Project commands + +Rule: Project commands are `scripts` in `.eser/manifest.yml` and run through the +`eser` CLI (`deno task cli ` in a repository that runs it from source; +`deno task cli --help` lists them). The root `package.json` holds only the CLI +entry points. Add a new command to the manifest, not to `package.json` or a +`deno.json` `tasks` block; package-specific tasks that JSR needs go in +`deno.extra.json`. + +Correct: + +```yaml +# .eser/manifest.yml +scripts: + docs-lint: deno doc --lint ./pkg/*/mod.ts +``` + +Incorrect: + +```json +{ "scripts": { "docs-lint": "deno doc --lint ./pkg/*/mod.ts" } } +``` + +**Why:** one list of commands, discoverable with `--help` and shared by the +precommit and release workflows. + +--- + +## Avoid Redundant Scripts + +Scope: New scripts + +Rule: Do not add a script that only renames a built-in command +(`"lint": "deno lint"` in a new place, `"install:deps": "pnpm install"`). Run +the built-in, or use the existing manifest entry. + +--- + +## Editor Configuration + +Scope: Shared editor settings + +Rule: Formatting rules come from `.editorconfig` (2-space indent, LF, UTF-8, +final newline, trimmed trailing whitespace except in Markdown) and `deno fmt`. +The shared `.vscode/settings.json` makes the Deno extension the formatter for +TypeScript, JavaScript and Markdown and enables Deno lint. Change these files +only with the owner's approval; personal preferences go in user settings. diff --git a/.agents/skills/workflow-practices/SKILL.md b/.agents/skills/workflow-practices/SKILL.md new file mode 100644 index 000000000..661f44573 --- /dev/null +++ b/.agents/skills/workflow-practices/SKILL.md @@ -0,0 +1,45 @@ +--- +name: workflow-practices +description: "How agents work in eserstack: clarifying requests, roles, approvals, forbidden git and publish actions, cross-package changes, root-cause fixes, quality gates. Use for every request to add, build, change, refactor, improve, migrate or fix something with the domain skill, above all open-ended ones (add caching, make X faster, set up auth), for reviews, and before any commit, push, tag or publish." +--- + +# Workflow Practices + +The flows and roles of agent work in this repository: clarifying the request, +what an agent may do alone, how a task runs, and when it is done. + +## Always + +- Ask before building when the answer changes what gets built (architecture, + business rules, formats, security or data loss, unbounded scope); otherwise + state the assumption and proceed. Ask everything in one message, 2 to 4 + options, recommended first; never ask what the code, skills or linters decide. + A plan, spec or backlog item with many open decisions: offer the enrichment + skill +- Read the code, write a todo list, check a behavior-changing plan with the + user, then execute +- One package per task, and only what the task needs; a cross-package change + follows the protocol; refactors go in their own commit +- Never commit, push, tag, reset, stash, check out, restore or run release + commands unless the user asks for that exact action; commit messages carry no + AI attribution or session ids +- Never revert what the user changed; undo your own edits with edits +- Ask first before new dependencies, workflow changes, `@eserstack/standards` + changes, publishing config or anything a release cannot undo +- Never edit versions, generated `deno.json` files or lock files by hand +- Bug fixes: root cause with file:line, failing test first, fix at the origin +- Implement everything asked; TODOs only with a backlog task id +- Done means `deno task cli ok` passed (`make ok` is Go only); report what was + verified and what was skipped +- Reviewers change nothing, cite file:line and mark each finding blocking or + informational +- Everything written into the repo is English, whatever the conversation + language + +## References + +| File | Read when | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [clarifying-questions.md](references/clarifying-questions.md) | Deciding whether to ask, or phrasing questions and assumptions | +| [roles-and-boundaries.md](references/roles-and-boundaries.md) | Checking whether an action is allowed, who owns an area, or ordering a multi-package change | +| [task-workflow.md](references/task-workflow.md) | Planning a task, fixing a bug, preparing a commit, or checking which gates must pass | diff --git a/.agents/skills/workflow-practices/evals/triggers.json b/.agents/skills/workflow-practices/evals/triggers.json new file mode 100644 index 000000000..17b28654f --- /dev/null +++ b/.agents/skills/workflow-practices/evals/triggers.json @@ -0,0 +1,150 @@ +[ + { + "query": "the session list endpoint returns duplicates, find out why and fix it", + "should_trigger": true + }, + { + "query": "bu değişiklikleri commit'le, mesajı sen yaz", + "should_trigger": true + }, + { + "query": "implement pagination for the posts API", + "should_trigger": true + }, + { + "query": "refactor the config loader so env parsing is in one place", + "should_trigger": true + }, + { + "query": "is this done? can we say the migration task is complete", + "should_trigger": true + }, + { + "query": "testler kırmızı ama sanırım flaky, geçip devam edelim mi", + "should_trigger": true + }, + { + "query": "add a --dry-run flag to the release command and wire it up", + "should_trigger": true + }, + { + "query": "I edited mux/router.ts by hand, now finish the rest of the change", + "should_trigger": true + }, + { + "query": "add caching to the API", + "should_trigger": true + }, + { + "query": "make the CLI faster", + "should_trigger": true + }, + { + "query": "bir bildirim sistemi ekleyelim", + "should_trigger": true + }, + { + "query": "support plugins in noskills", + "should_trigger": true + }, + { + "query": "migrate the logging to something better", + "should_trigger": true + }, + { + "query": "can you improve the error messages everywhere", + "should_trigger": true + }, + { + "query": "we need auth for the web UI, set it up", + "should_trigger": true + }, + { + "query": "add export to the reports, whatever format makes sense", + "should_trigger": true + }, + { + "query": "this change touches both @eserstack/shell and @eserstack/cli, plan it", + "should_trigger": true + }, + { + "query": "can you push this branch and tag it", + "should_trigger": true + }, + { + "query": "review this PR as a reviewer agent and rank findings by severity", + "should_trigger": true + }, + { + "query": "delete the old dist folders and reset the lockfile", + "should_trigger": true + }, + { + "query": "force-push main to fix the history", + "should_trigger": true + }, + { + "query": "hangi dosyalara dokunmaman gerekiyor bu repoda", + "should_trigger": true + }, + { + "query": "update the shared types in primitives and fix every package that uses them", + "should_trigger": true + }, + { + "query": "run the publish step for npm yourself", + "should_trigger": true + }, + { + "query": "what's the difference between Deno.test and bdd describe", + "should_trigger": false + }, + { + "query": "write a README section for the cache package", + "should_trigger": false + }, + { + "query": "bump the version to 4.6.0", + "should_trigger": false + }, + { + "query": "is `import { join }` allowed in our code", + "should_trigger": false + }, + { + "query": "which GitHub Actions runner does build.yml use", + "should_trigger": false + }, + { + "query": "explain how the hexagonal layers are organised", + "should_trigger": false + }, + { + "query": "rename this Go file to snake_case", + "should_trigger": false + }, + { + "query": "what does the eser-rules-manager skill do", + "should_trigger": false + }, + { + "query": "how should I name a new Go file", + "should_trigger": false + }, + { + "query": "which Deno lint rules are enabled", + "should_trigger": false + }, + { + "query": "what's the SSRF rule for webhooks", + "should_trigger": false + }, + { + "query": "show me the git log for last week", + "should_trigger": false + }, + { + "query": "what does `cmp.Or` do in Go", + "should_trigger": false + } +] diff --git a/.agents/skills/workflow-practices/references/clarifying-questions.md b/.agents/skills/workflow-practices/references/clarifying-questions.md new file mode 100644 index 000000000..9356f3477 --- /dev/null +++ b/.agents/skills/workflow-practices/references/clarifying-questions.md @@ -0,0 +1,75 @@ +# Clarifying Questions + +When to ask before implementing, when to proceed on a stated assumption, and how +to ask. + +--- + +## Ask, Assume or Proceed + +Scope: Before implementing any non-trivial change + +Rule: + +``` +Is the requirement clear? +├── yes → proceed +└── no → are there several valid approaches with different consequences? + ├── yes → present them with trade-offs and a recommendation, ask + └── no → is information missing that changes the result? + ├── yes → ask specific questions + └── no → state the assumption and proceed unless corrected +``` + +**Ask** when the answer changes what gets built: + +- architecturally different implementations exist; +- business rules have unclear edge cases (soft or hard delete, who may do it); +- existing behavior, a public API or a persisted format would change; +- a security, data-loss or performance trade-off has to be chosen; +- the scope has no boundary ("add a feature" without details); +- the action is on the Ask First list (roles-and-boundaries.md: Ask First). + +When the user asks for a full interview about a plan, switch to the enrichment +skill. When you draft a plan yourself (a PRD, spec, ADR or the content of a +backlog item) and it has more open decisions than one round of questions +settles, offer an enrichment session instead of guessing: it works through the +decisions level by level and asks before each deeper level. If the user +declines, write the open decisions into the draft as stated assumptions. + +**Do not ask** about naming, formatting or anything the codebase, the skills or +the linters already decide, or when the user said to use your judgment. + +--- + +## How to Ask + +Scope: Every clarifying question + +Rule: + +- Ask before writing code, not after. +- Ask everything related in one message; never drip questions one at a time. +- Make each question specific and answerable: "Should deleting a session also + delete its ledger entries?", not "How should this work?". +- For a choice between options, list 2 to 4, each with its consequence, and put + the recommended one first. Use the AskUserQuestion tool when the options are + discrete. +- Offer the default you would take, so the user can answer with "yes". + +--- + +## Stating Assumptions + +Scope: Proceeding without an explicit answer + +Rule: When proceeding on an assumption, list it where the user will see it, +before or together with the work, and name how to change it. + +``` +Assumptions: +- Deleting a session keeps its ledger entries (they are an audit trail) +- The endpoint returns 404 for an unknown session id, like the other handlers + +Say if either should be different. +``` diff --git a/.agents/skills/workflow-practices/references/roles-and-boundaries.md b/.agents/skills/workflow-practices/references/roles-and-boundaries.md new file mode 100644 index 000000000..5867976f6 --- /dev/null +++ b/.agents/skills/workflow-practices/references/roles-and-boundaries.md @@ -0,0 +1,164 @@ +# Roles and Boundaries + +What an agent may do on its own in this repository, what needs the user's +approval, and who owns which area. + +## Contents + +- Agent Roles +- File Ownership +- Never Do +- Ask First +- Language +- Cross-Package Change Protocol + +--- + +## Agent Roles + +Scope: Every agent task + +Rule: An agent works in one role at a time. + +- **Implementer:** changes code inside one package (`pkg/@eserstack//` or + one `pkg/ajan/fx/`), follows that package's conventions + (tooling-standards, AGENTS.md), writes tests, and runs `deno task cli ok` + before calling the work done. +- **Reviewer:** reads and reports; changes nothing. + - Reports semantic issues. Formatting and lint-level issues are left to + `deno fmt`, `deno lint` and the validators. + - Marks each finding blocking or informational. Blocking: injection, missing + authorization, unsynchronized shared state, non-exhaustive branching, + untrusted input reaching a prompt or subprocess, secrets in output. + Everything else is informational. + - Cites file:line for every finding. Before merge each finding is fixed, + deferred to a backlog task, or accepted with a reason; no blocking finding + stays open. +- **Architect:** plans cross-package or structural changes with the + cross-package protocol below, reads `.github/ARCHITECTURE.md` for the package + layers, and records significant decisions as ADRs in `docs/adr/`. + +--- + +## File Ownership + +Scope: Deciding whether a change stays inside its area + +Rule: A task stays inside the area it was given. The areas marked high impact +need the Ask First step before any change. + +| Area | Concern | +| ---------------------------- | ----------------------------------------------------------------------------------- | +| `pkg/@eserstack/standards/` | Cross-runtime abstractions; **high impact**, every package depends on it | +| `pkg/@eserstack/codebase/` | Repo tooling and validators; owns `cli-system/`, the command tree of every binary | +| `pkg/@eserstack/cli/` | The `eser` binary and its npm bundle | +| `pkg/@eserstack/noskills*/` | Spec workflow, daemon client, web dashboard, mux worker | +| `pkg/@eserstack/laroux*/` | Framework: server, bundler, React integration | +| `pkg/@eserstack/ai/` | AI providers: TypeScript adapters and the Go bridge | +| `pkg/@eserstack/kit/` | Recipes and scaffolding | +| `pkg/@eserstack/ajan/` | FFI bridge to the Go library; its own Go module | +| `pkg/ajan/` | Go library packages (`*fx`); `api/business/` is pure domain, `api/adapters/` is I/O | +| `pkg/ajan/noskillsserverfx/` | The noskills daemon: sessions, ledger, workers | +| `cmd/` | Go entry points; only `noskills-server`, the other binaries are `deno compile`d | +| `.github/workflows/` | CI/CD; **high impact**, see release-and-ci | +| `.eser/` | Workflow manifest, recipes and scripts | +| `.agents/skills/` | Agent rules; update them when a convention changes (eser-rules-manager) | + +--- + +## Never Do + +Scope: All agent actions + +Rule: These are forbidden unless the user asks for that exact action in the +current task. A general "go ahead" does not cover them. + +- **Git history and working tree:** no `commit`, `push`, `tag`, `reset`, + `rebase`, `checkout`, `restore`, `stash` or force push. The user commits and + pushes. When the user does ask for a commit, the message carries no AI + attribution and no session ids (task-workflow.md: Git Commit Policy). +- **Release commands:** `eser codebase release`, `rerelease` and `unrelease` + commit, push to `main` and push tags. Run them only when the user asks for a + release (release-and-ci). +- **Versions:** never edit a `version` field by hand; the release command bumps + `VERSION` and every `package.json` together. +- **Generated files:** per-package `deno.json` files are generated by + `etc/scripts/gen-jsr-manifests.ts`; edit `package.json` or `deno.extra.json` + instead. Lock files (`pnpm-lock.yaml`, `go.sum`) change only through their + tools. +- **Checks:** never skip hooks (`--no-verify`) and never weaken TypeScript types + to make a check pass. +- **Secrets:** never commit `.env` files, keys or tokens. +- **Permissions:** `--allow-all` only in `scripts/` and tests. Production entry + points list the permissions they need. +- **Scope:** do not modify files outside the assigned package. + +```bash +# Tests and scripts may use broad permissions +deno test --allow-all + +# A shipped entry point names what it needs +deno run --allow-net=api.example.com --allow-env=API_KEY main.ts +``` + +Stop and reconsider when the plan includes "just this once I'll skip the +checks", "this change is too small for a test", "I'll fix the types later" or +"it is only one version field". + +**Why:** these actions are either irreversible (a pushed tag starts a publish +that JSR never forgets) or hide a problem the checks exist to catch. + +--- + +## Ask First + +Scope: Actions that need the user's confirmation + +Rule: Describe the change and wait for approval before: + +- adding an import between `@eserstack/*` packages that did not depend on each + other before; +- adding an external dependency (JSR, npm or Go module), with the purpose, + license and alternative from tooling-standards: Adding a Dependency; +- changing `.github/workflows/`; +- changing `@eserstack/standards`; +- changing publishing configuration (`exports`, `publish` fields, + `deno.extra.json`); +- removing or deprecating a public API; +- anything a later release cannot undo: persisted file or ledger formats, and + package names or exports already published to JSR or npm. + +--- + +## Language + +Scope: Everything written into the repository + +Rule: Source code, comments, documentation, commit messages, backlog items, +ADRs, task plans and release notes are written in English, whatever language the +conversation uses. A Turkish request produces English files. Never translate a +rule, comment or backlog item into the conversation language, and never leave a +non-English fragment in a file because the user wrote it that way. + +**Why:** the repository has external readers, and its tooling (validators, +changelog generation, release notes) parses these files. + +--- + +## Cross-Package Change Protocol + +Scope: A change that spans more than one `@eserstack/*` package + +Rule: + +1. **Justify:** say why the packages must change together. One package per + change is the default. +2. **Plan:** list each affected package and its part of the change. +3. **Order:** change dependencies before dependents, following the layers in + `.github/ARCHITECTURE.md`: Layer 0 (`@eserstack/standards` and other + foundations) first, then core utilities and infrastructure, then the + framework, and the shipped binaries (Layer 4: `cli`, `noskills`, + `laroux-server`) last. +4. **Test:** run `deno task cli ok` after all packages are changed. +5. **Version:** all packages share one version, so no per-package bump is + needed; the next release carries the change. diff --git a/.agents/skills/workflow-practices/references/task-workflow.md b/.agents/skills/workflow-practices/references/task-workflow.md new file mode 100644 index 000000000..cf8b51bcc --- /dev/null +++ b/.agents/skills/workflow-practices/references/task-workflow.md @@ -0,0 +1,224 @@ +# Task Workflow + +How a task moves from request to done: planning, user changes, commits, bug +fixes, the checks that gate completion, and change scope. + +## Contents + +- Standard Workflow +- Never Revert User Changes +- Never Use Git to Revert File Contents +- Git Commit Policy +- Complete, Honest Work +- Bug Fixes Start at the Root Cause +- Quality Gates +- Avoid Over-Engineering + +--- + +## Standard Workflow + +Scope: Every non-trivial task + +Rule: + +1. Read the relevant code before planning. +2. Write a short todo list of concrete steps. +3. Check the plan with the user when it changes behavior, touches several + packages or can be done in clearly different ways (clarifying-questions.md). + A plan artifact with many open decisions (PRD, spec, ADR, backlog item) is a + reason to offer an enrichment session. +4. Work through the list and mark items done as you go. + +--- + +## Never Revert User Changes + +Scope: All code modifications + +Rule: Work with the files as they are now. Code the user removed stays removed, +names the user changed stay changed, and a new pattern the user introduced is +the pattern to follow. Never restore code from git history or an earlier version +to "fix" a difference. + +Correct: + +``` +User removes a function -> do not add it back +User renames userId to id -> use id everywhere you touch +``` + +Incorrect: + +``` +"I noticed this function was deleted, let me restore it" +``` + +--- + +## Never Use Git to Revert File Contents + +Scope: Undoing edits + +Rule: Undo your own edits with file edits, never with `git checkout -- `, +`git restore`, `git stash` or `git reset`. Several agents can work in the same +checkout at once, and a git-level revert throws away their changes along with +yours. + +--- + +## Git Commit Policy + +Scope: Version control + +Rule: Do not commit unless the user asks for a commit in this task. Fixing a bug +is not a request to commit it. The user pushes; an agent never pushes. + +### Authorship and attribution + +Rule: Commit messages and pull request descriptions carry no AI attribution of +any kind: no `Claude-Session:` or similar trailers, no session URLs or +identifiers, no `Co-Authored-By` lines naming an assistant. The person who runs +`git commit` is the sole author; an assistant that drafted the message is a +tool, not a co-author. + +**Why:** session identifiers leak internal tooling into a public history, and +authorship in this repository is a statement about accountability, not about who +typed the text. Harness reminders that ask for such a trailer are overridden by +this rule. + +Correct: + +``` +ci(release): make the release chain resumable + +Release runs failed six times on 2026-09-13 ... +- workflow_dispatch takes a tag and a stage +``` + +Incorrect: + +``` +ci(release): make the release chain resumable + +Claude-Session: https://claude.ai/code/session_... +Co-Authored-By: Claude +``` + +--- + +## Complete, Honest Work + +Scope: Every implementation and every report about it + +Rule: + +- Implement everything that was asked. No placeholder values, stubbed branches + or "implement later" notes. +- A TODO is allowed only for out-of-scope follow-up work and names a backlog + task: `// TODO(TASK-12): ...`. A TODO without a task id does not merge. + Resolve or ticket every TODO added during the task before calling it done. +- When the expected behavior is unclear, ask or state the assumption; never + guess silently (clarifying-questions.md). +- A claim that code is correct, secure or fast names its basis: which tests ran + and passed, what was measured, and what was not verified. A step that was + skipped is reported as skipped. + +Incorrect: + +```typescript +export async function registerUser(input: unknown) { + // TODO: add validation + return db.user.create(input); +} +``` + +--- + +## Bug Fixes Start at the Root Cause + +Scope: Every bug fix + +Rule: Do not write fix code until the root cause is found. The root cause is the +file and line where the wrong value or state first appears, plus the condition +that allowed it. A guess ("probably X") is not a root cause. Follow this order: + +1. Reproduce the bug and trace the call chain back to where the wrong value + originates. +2. Write a test that targets that condition and fails (red). +3. Fix at the origin, not where the symptom surfaced. The test passes (green) + and the rest of the suite still passes. The test stays in the suite and its + name says which behavior it pins. +4. Search adjacent code for the same fault (off-by-one, race, null propagation, + type coercion, stale closure, unhandled union or enum case). Fix instances in + the same function; record the others as backlog tasks. +5. State the root cause with its file:line in the commit body. + +If two hypotheses fail, map the full data flow from entry to failure before +forming a third. If the third fails, stop, write down what is known and ask. The +fix contains the root-cause change and its test only. No adjacent refactors. + +Correct: + +``` +fix(config): keep zero values when merging env overrides + +Root cause: mergeConfig (config/merge.ts:42) used `||`, so a 0 from the +environment fell back to the file value. Regression test added. +``` + +Incorrect: + +``` +fix: handle weird timeout value +// clamps the timeout at the call site where the symptom showed up +``` + +**Why:** a fix at the symptom leaves the condition in place, and the same bug +returns through another path. + +--- + +## Quality Gates + +Scope: Every code change + +Rule: A task is done only when `deno task cli ok` passes. It runs the precommit +workflow from `.eser/manifest.yml`: the codebase validators, `deno fmt`, +`deno lint`, type checks, the Deno tests and the Go checks. Fix what it reports; +never bypass it. + +While iterating, narrower commands are faster: + +```bash +deno task cli workflows run -e precommit --only # one precommit step +deno task cli go-ok # Go only; same as make ok +make lint # golangci-lint +make test # go test -race +deno task cli test # Deno tests with coverage +``` + +`make ok` covers Go only, so it never replaces `deno task cli ok` for a change +that touches TypeScript. Release-only steps (npm bundles, `deno publish` +dry-run, wasm builds) run in `deno task cli preflight`, which is needed before +tagging, not before every commit. How tests are written and organized is in the +architecture-guidelines testing reference. + +--- + +## Avoid Over-Engineering + +Scope: The scope of a change + +Rule: Change what was asked, and what is needed for it to work. Nothing else. + +- No features, options or configuration beyond the request. A fixed value that + callers may need to change becomes a parameter with a default + (architecture-guidelines: Public API and CLI Surface). +- No refactors, comments or docstrings in code the task does not touch. A bug + fix does not clean up the surrounding code. +- When a feature needs a refactor first, the refactor is its own `refactor:` + commit with no behavior change, followed by the feature commit. +- No design for hypothetical future requirements. When to extract shared code + and which errors to handle are code-level rules in coding-practices. +- Prefer the tools and patterns the repository already uses over new ones. diff --git a/.claude b/.claude new file mode 120000 index 000000000..c0ca46856 --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +.agents \ No newline at end of file diff --git a/.claude/skills/agent-guidelines/SKILL.md b/.claude/skills/agent-guidelines/SKILL.md deleted file mode 100644 index a1b76a5a2..000000000 --- a/.claude/skills/agent-guidelines/SKILL.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: agent-guidelines -description: Agent roles, safety rules, file ownership, and cross-package change protocol. Use when AI agents work on this codebase, plan multi-package changes, or need boundary guidance. ---- - -# Agent Guidelines - -Rules for AI coding agents working on the eserstack monorepo. - -## Quick Start - -1. Work within **one package at a time** unless cross-package change is justified -2. Check **Safety Rules** before any action (Never Do / Ask First / Always Do) -3. Run `deno task validate` before considering work complete -4. Follow existing patterns in the target package - -## Agent Roles - -- **Implementer:** Write code in one package, run validate, follow conventions -- **Reviewer:** Read-only analysis, check skill adherence, verify coverage -- **Architect:** Plan cross-package changes, create ADRs in `etc/adrs/` - -## Safety Rules - -**Never Do:** destructive git, modify outside assigned package, edit versions manually, weaken types, skip hooks, commit secrets - -**Ask First:** cross-package deps, new external deps, CI/CD changes, `@eser/standards` changes, publishing config - -**Always Do:** run validate, follow existing patterns, write tests, use explicit extensions, keep functions pure - -## References - -See [rules.md](references/rules.md) for file ownership, cross-package protocol, and detailed safety rules. diff --git a/.claude/skills/agent-guidelines/references/rules.md b/.claude/skills/agent-guidelines/references/rules.md deleted file mode 100644 index bcafe2394..000000000 --- a/.claude/skills/agent-guidelines/references/rules.md +++ /dev/null @@ -1,166 +0,0 @@ -# Agent Guidelines - Detailed Rules - -## Agent Roles - -### Implementer - -Scope: Writing code within a single package - -Rule: Stay within one package per task. Follow package conventions from `eserstack-monorepo` skill. - -Responsibilities: -- Write code within `pkg/@eser//` or `pkg/@cool//` -- Run `deno task validate` after changes -- Follow package conventions (mod.ts entry, license headers, kebab-case) -- Write tests for new functionality -- Keep functions pure when possible - -### Reviewer - -Scope: Read-only code analysis - -Rule: Analyze without modifying. Check adherence to skills. - -Responsibilities: -- Read-only analysis of code quality and patterns -- Check adherence to skills in `.claude/skills/` -- Verify test coverage and type safety -- Suggest improvements without implementing them - -### Architect - -Scope: Planning cross-package or structural changes - -Rule: Plan before implementing. Document decisions. - -Responsibilities: -- Plan cross-package changes using the cross-package protocol -- Reference `.github/ARCHITECTURE.md` for package relationships -- Create ADRs in `etc/adrs/` for significant architectural decisions -- Consider impact on all 29+ packages - ---- - -## File Ownership - -Scope: Understanding responsibility areas - -Rule: Each area has specific concerns. Respect ownership boundaries. - -| Area | Owner Concern | -|------|--------------| -| `pkg/@eser/fp/` | Functional programming — pure functions, immutability | -| `pkg/@eser/di/` | Dependency injection — container patterns | -| `pkg/@eser/cli/` | CLI tool — also the npm publishing target | -| `pkg/@eser/laroux*/` | Framework — server, bundler, React integration | -| `pkg/@eser/standards/` | Cross-platform abstractions — **high impact, change carefully** | -| `pkg/@eser/config/` | Configuration management — dotenv, file loaders | -| `pkg/@eser/events/` | Event system — pub/sub patterns | -| `pkg/@eser/shell/` | Shell interaction — args, exec, env, completions | -| `apps/services/pkg/api/business/` | Go business logic — pure domain, no external deps | -| `apps/services/pkg/api/adapters/` | Go adapters — external integrations | -| `apps/services/cmd/` | Go entry points — server, CLI | -| `apps/services/Makefile` | Go build targets — test in branch | -| `etc/templates/` | Project templates — validate after changes | -| `.github/workflows/` | CI/CD — test in a branch before merging | -| `.claude/skills/` | AI guidance — update when conventions change | - ---- - -## Safety Rules - Never Do - -Scope: All agent interactions - -Rule: These actions are **strictly prohibited**. No exceptions without explicit user approval. - -- Run destructive git commands (`checkout`, `commit`, `reset`, `push`, `rebase`, `stash`, `tag`) -- Modify files outside the assigned package without explicit approval -- Change `deno.json` version fields manually (use the version-bump script) -- Remove or weaken TypeScript types -- Skip pre-commit hooks (`--no-verify`) -- Commit secrets or `.env` files -- Use `--allow-all` in production code (only in scripts/tests) -- Modify `deno.lock` manually -- Push directly to `main` branch - -## Red Flags - -Stop and reconsider if you're thinking: -- "Just this once I'll skip validate..." -- "This small change doesn't need tests..." -- "I'll fix the types later..." -- "This version edit is too small to use the script..." - ---- - -## Safety Rules - Ask First - -Scope: Actions requiring explicit approval - -Rule: Get user confirmation before proceeding with these actions. - -- Cross-package dependency changes (adding imports between `@eser/*` packages) -- Adding new external dependencies (JSR or npm packages) -- Modifying CI/CD workflows (`.github/workflows/`) -- Schema changes in `@eser/standards` (affects all packages) -- Changes to publishing configuration (deno.json exports, publish fields) -- Removing or deprecating public API functions - ---- - -## Safety Rules - Always Do - -Scope: Required actions for every task - -Rule: These must be done before considering work complete. - -- Run `deno task validate` (or `make ok`) — full monorepo validation (Deno + Go) -- Run `make go-ok` after Go changes — Go-specific validation -- Follow existing code patterns in the target package -- Write tests for new functionality (`*.test.ts` co-located) -- Use explicit file extensions in imports (`.ts`, `.tsx`) -- Keep functions pure when possible (side effects at the edges) -- Use `@std/*` for standard library imports -- Use `@eser/*` for internal package imports - ---- - -## Cross-Package Change Protocol - -Scope: Changes that span multiple `@eser/*` packages - -Rule: Follow this 5-step protocol. Never skip steps. - -1. **Justify** — Explain why multiple packages must change together. Single-package changes are preferred. - -2. **Plan** — List all affected packages and describe the change in each: - ``` - @eser/standards: Add new Runtime interface method - @eser/config: Implement the new method - @eser/cli: Use the new method - ``` - -3. **Order** — Change dependencies before dependents: - - Layer 0 (`@eser/standards`) first - - Then Layer 1-2 (utilities, infrastructure) - - Then Layer 3 (framework) - - Then Layer 4 (`@eser/cli`) last - -4. **Test** — Run `deno task validate` after ALL changes (validates entire monorepo) - -5. **Version** — If publishing is affected, use the version-bump script - ---- - -## Tech Stack Reference - -| Component | Technology | -|-----------|-----------| -| Runtime | Deno 2.x (TS primary), Go 1.24+ (services), Node.js (npm publishing only) | -| Language | TypeScript (strict mode), Go | -| Package Registry | JSR (primary), npm (secondary — only `@eser/cli`) | -| Testing | `@std/assert`, `@std/testing` from Deno standard library | -| Linting | Deno built-in linter with 23 custom rules | -| Formatting | Deno built-in formatter | -| CI/CD | GitHub Actions with Codecov coverage | -| @eser/codebase | 20+ hooks including validation, kebab-case, typos, conventional commits | diff --git a/.claude/skills/architecture-guidelines/SKILL.md b/.claude/skills/architecture-guidelines/SKILL.md deleted file mode 100644 index e2a4ee50a..000000000 --- a/.claude/skills/architecture-guidelines/SKILL.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: architecture-guidelines -description: "System architecture: ES modules, hexagonal architecture, project structure, ADRs, and testing strategy. Use when designing systems, planning directory layout, writing ADRs, or reviewing architecture." ---- - -# Architecture Guidelines - -Guidelines for system design, project structure, and architectural decisions. - -## Quick Start - -```typescript -// Use ES Modules with explicit extensions -import * as path from "@std/path"; -import { readFile } from "./utils.ts"; - -export function processFile() {} -``` - -## Key Principles - -- Use ES Modules (avoid CommonJS/AMD) -- Follow consistent directory structure with kebab-case directories -- Document architectural decisions with ADRs including trade-offs -- Write automated tests with CI (target 80%+ coverage for critical paths) -- Use naming conventions: PascalCase for components, camelCase for utilities -- Hexagonal architecture: domain + ports together, adapters separate -- Explicit composition only: import adapters directly, pass as parameters (no - magic config strings, no convenience factories) - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/architecture-guidelines/references/rules.md b/.claude/skills/architecture-guidelines/references/rules.md deleted file mode 100644 index 452b39c2c..000000000 --- a/.claude/skills/architecture-guidelines/references/rules.md +++ /dev/null @@ -1,510 +0,0 @@ -# Architecture Guidelines - Detailed Rules - -## Module System - -Scope: JS/TS projects - -Rule: Use ES Modules. ES Modules is official standard supported by all modern -browsers and WinterCG runtimes. Avoid CommonJS and AMD. - -Correct: - -```typescript -import * as path from "@std/path"; -import { readFile } from "./utils.ts"; - -export function processFile() {} -export const CONFIG = {}; -``` - -Incorrect: - -```typescript -const path = require("path"); // CommonJS -const { readFile } = require("./utils"); - -module.exports = { processFile }; // CommonJS -exports.CONFIG = {}; // CommonJS -``` - ---- - -## Project Structure - -Scope: All projects - -Rule: Follow consistent directory and file structure. Makes it easier to -locate and manage files. - -Example structure: - -``` -project/ -├── src/ -│ ├── app/ # Application code -│ └── components/ # Reusable components -├── tests/ # Test files -├── docs/ # Documentation -├── public/ # Static assets -└── config/ # Configuration files -``` - -Naming conventions: - -- Use kebab-case for directories: `user-service/` -- Use PascalCase for component files: `UserProfile.tsx` -- Use camelCase for utility files: `formatDate.ts` -- Test files: `*.test.ts` or `*.spec.ts` - ---- - -## Architectural Decision Records - -Scope: All projects - -Rule: Document architectural design records (ADRs) with trade-offs. -Captures important decisions with context and consequences. - -ADR template (docs/adr/001-decision-title.md): - -```markdown -# ADR 001: Decision Title - -Date: 2025-01-15 Status: Accepted - -## Context - -What is the issue we're facing that motivates this decision? - -## Decision - -What is the change we're making? - -## Consequences - -What becomes easier or more difficult as a result of this change? - -## Alternatives Considered - -- Option A: pros/cons -- Option B: pros/cons - -## Trade-offs - -What are we optimizing for and what are we sacrificing? -``` - ---- - -## Testing - -Scope: All projects - -Rule: Write tests for code. Prefer automated testing with CI. Ensures -code works as expected and catches issues early. - -Test structure: - -```typescript -import { assertEquals } from "@std/assert"; - -Deno.test("function name - should do something", () => { - const result = myFunction(input); - assertEquals(result, expected); -}); -``` - -CI configuration (.github/workflows/test.yml): - -```yaml -name: Test -on: [push, pull_request] -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 - - run: deno test --allow-all -``` - -Coverage target: Aim for 80%+ code coverage for critical paths. - ---- - -## Table-Driven Tests - -Scope: Unit tests with multiple cases - -Rule: Use table-driven patterns for tests with multiple inputs. Name -tests to describe behavior, not implementation. - -Correct: - -```typescript -import { assertEquals } from "@std/assert"; - -const cases = [ - { input: 0, expected: "zero", name: "handles zero" }, - { input: -1, expected: "negative", name: "handles negative numbers" }, - { input: 42, expected: "positive", name: "handles positive numbers" }, -]; - -for (const { input, expected, name } of cases) { - Deno.test(name, () => { - assertEquals(classifyNumber(input), expected); - }); -} -``` - -Incorrect: - -```typescript -Deno.test("test1", () => assertEquals(classifyNumber(0), "zero")); -Deno.test("test2", () => assertEquals(classifyNumber(-1), "negative")); -Deno.test("test3", () => assertEquals(classifyNumber(42), "positive")); -``` - -Benefits: - -- Easy to add new test cases -- Reduces code duplication -- Clear relationship between inputs and expected outputs -- Descriptive test names improve test output readability - ---- - -## Documentation Standards - -Scope: Project documentation - -Rule: Centralize docs in `docs/`, colocate component READMEs, use -consistent formatting. - -**Directory Structure:** - -``` -project/ -├── docs/ # Permanent project documentation -│ ├── ARCHITECTURE.md -│ ├── SECURITY.md -│ ├── API_REFERENCE.md -│ └── adr/ # Architectural Decision Records -│ └── 001-decision.md -├── packages/ -│ └── auth/ -│ └── README.md # Component-specific docs -└── README.md # Project overview -``` - -**Formatting Rules:** - -- Clear filenames: `ALL_CAPS_WITH_UNDERSCORES.md` for project docs -- Include date stamps and status in document headers -- Provide concrete examples and code snippets -- Keep documentation synchronized with code changes - -Correct: - -```markdown -# API Reference - -**Last Updated:** 2025-01-15 **Status:** Current - -## Overview - -Brief description of the API... - -## Endpoints - -### GET /users/:id - -Returns user by ID. - -**Example:** -``` - -curl https://api.example.com/users/123 - -``` -``` - -Incorrect: - -- Scattered .md files in root directory -- No date/status in headers -- Vague filenames like `notes.md` or `stuff.md` -- Missing code examples - ---- - -## Hexagonal Architecture (Double-Layered) - -Scope: All projects with external dependencies - -Rule: Use double-layered hexagonal architecture. Domain layer contains both -business logic AND port interfaces/types together (no separate ports directory). -Adapters implement domain interfaces. Composition happens at call site via -explicit imports. - -**Structure:** - -``` -package/ -├── domain/ # Business logic + port interfaces -│ ├── mod.ts # Re-exports all domain types -│ ├── bundler.ts # Bundler interface + BundleData type -│ ├── framework-plugin.ts # FrameworkPlugin interface + related types -│ └── build-cache.ts # BuildCache interface + implementation -│ -├── adapters/ # All adapter implementations -│ ├── react/ # implements FrameworkPlugin -│ ├── tailwindcss/ # implements CssPlugin -│ ├── lightningcss/ # implements CssTransformer -│ └── prebuilt-bundler/ # implements Bundler -│ -└── mod.ts # Main entry, exports bundle function -``` - -**Composition via Explicit Imports:** - -Correct: - -```typescript -// User explicitly imports what they need -import { bundle } from "@eser/laroux-bundler"; -import { reactPlugin } from "@eser/laroux-bundler/adapters/react"; -import { tailwindPlugin } from "@eser/laroux-bundler/adapters/tailwindcss"; -import { PrebuiltBundler } from "@eser/laroux-bundler/adapters/prebuilt-bundler"; - -// Pass adapters as parameters -await bundle(config, { - framework: reactPlugin, - css: tailwindPlugin, - bundler: new PrebuiltBundler(bundlerConfig), -}); -``` - -Incorrect: - -```typescript -// Magic config strings - avoid -const config = { - framework: "react", // String-based selection - css: "tailwindcss", - bundler: "prebuilt", -}; -await bundle(config); // Dynamic imports inside - -// Convenience factory functions - avoid -const plugins = await createReactTailwindPlugins(); // Hides adapter selection -await bundle(config, plugins); // User doesn't control what loads - -// Separate ports directory - avoid -import type { Plugin } from "./ports/plugin.ts"; // Don't separate ports -import { logic } from "./domain/logic.ts"; // from domain -``` - -**Why Avoid Convenience Factories:** - -- Hide which adapters are loaded (opaque dependency graph) -- Require dynamic imports that break tree-shaking -- Make it harder to swap individual adapters -- Add unnecessary abstraction layer -- Users should always know exactly what they're importing - -**Benefits:** - -- Tree-shaking works naturally (unused adapters not bundled) -- Type-safe: TypeScript validates plugin interfaces at compile time -- User controls exactly what gets loaded -- Easy to swap adapters (Vue instead of React, UnoCSS instead of Tailwind) -- No runtime magic or dynamic imports -- Clear dependency graph - -**Domain Layer Design:** - -Domain files contain both interfaces and business logic: - -```typescript -// domain/framework-plugin.ts -export type ClientComponent = { - filePath: string; - relativePath: string; - exportNames: string[]; -}; - -export type FrameworkPlugin = { - name: string; - analyzeClientComponents?: (srcDir: string) => Promise; - transformClientComponents?: (components: ClientComponent[]) => Promise; -}; - -// Noop implementation for when no framework is configured -export const noopPlugin: FrameworkPlugin = { - name: "noop", - analyzeClientComponents: () => Promise.resolve([]), -}; -``` - -**Adapter Implementation:** - -```typescript -// adapters/react/plugin.ts -import type { FrameworkPlugin } from "../../domain/framework-plugin.ts"; - -export const reactPlugin: FrameworkPlugin = { - name: "react", - analyzeClientComponents: async (srcDir) => { - // React-specific implementation - }, -}; -``` - ---- - -## Centralized Backend Object Pattern - -Scope: Frontend applications communicating with backend APIs - -Rule: Use a centralized backend object as single entry point for all API -communication. Import the backend object, not individual functions. Type imports -only from submodules. - -**Structure:** - -``` -src/modules/backend/ -├── backend.ts # Main entry - exports backend object -├── fetcher.ts # HTTP client utilities -├── types.ts # Shared types -├── profiles/ -│ ├── get-profile.ts # Individual function -│ ├── list-profiles.ts -│ └── types.ts # Profile-specific types -└── users/ - ├── get-user.ts - └── types.ts -``` - -Correct: - -```typescript -// ✅ Import via centralized backend object -import { backend } from "@/modules/backend/backend.ts"; -import type { Profile } from "@/modules/backend/types.ts"; - -async function UserProfile(props: { profileId: string }) { - const profile = await backend.getProfile("en", props.profileId); - return
{profile.title}
; -} -``` - -Incorrect: - -```typescript -// ❌ Direct function imports bypass abstraction -import { getProfile } from "@/modules/backend/profiles/get-profile.ts"; - -async function UserProfile(props: { profileId: string }) { - const profile = await getProfile("en", props.profileId); - return
{profile.title}
; -} - -// ❌ Inline fetch logic in components -async function UserProfile(props: { profileId: string }) { - const response = await fetch(`/api/profiles/${props.profileId}`); - const profile = await response.json(); - return
{profile.title}
; -} -``` - -**Backend Object Implementation:** - -```typescript -// backend.ts -import { getProfile } from "./profiles/get-profile.ts"; -import { listProfiles } from "./profiles/list-profiles.ts"; -import { getUser } from "./users/get-user.ts"; - -export const backend = { - // Profiles - getProfile, - listProfiles, - - // Users - getUser, -} as const; -``` - -**Benefits:** - -- Single source of truth for all backend functionality -- Clear contract between frontend and backend -- Easier to mock in tests -- Simplified refactoring when API changes -- Type safety across the application - ---- - -## Monorepo Structure - -Scope: Large projects with multiple applications - -Rule: Organize code into apps/ for deployable applications and packages/ for -shared libraries. Each app follows domain-specific patterns. - -**Structure:** - -``` -project/ -├── apps/ -│ ├── webclient/ # Frontend application -│ │ └── src/ -│ │ ├── routes/ # File-based routing -│ │ ├── components/ # UI components -│ │ ├── modules/ # Feature modules -│ │ └── lib/ # Utilities -│ └── services/ # Backend services -│ └── pkg/ -│ ├── api/ -│ │ ├── business/ # Pure business logic -│ │ └── adapters/ # External implementations -│ └── lib/ # Shared libraries -└── packages/ # Shared packages (future) -``` - -**Rules:** - -- Frontend apps use file-based routing when framework supports it -- Backend services follow hexagonal architecture -- Shared code goes in packages/ with clear dependencies -- Each app has its own build configuration - ---- - -## Multi-Context Tool Design - -Scope: @eser/codebase tools - -Rule: Codebase tools must be context-agnostic. The same pure check/fix logic runs in -CLI, MCP server, AI agent SDK, or Go service. The runner (Adapter) determines context; -the tool never assumes its execution environment. - -``` -Pure logic (Handler) Runner (Adapter) Output (ResponseMapper) -───────────────────── ────────────────── ───────────────────────── -checkFile() → issues[] CLI runner Terminal colors + exit code -fixFile() → mutations[] MCP server (IDE tool) JSON tool results - AI agent (SDK tool) Structured for LLM -``` - ---- - -## Mutation-Return Pattern - -Scope: @eser/codebase fixers - -Rule: Fixers never write to disk directly. They return `{path, oldContent, newContent}` -mutations (Edit-tool-shaped). The runner decides whether to apply, preview, or pass to -an AI agent for review. diff --git a/.claude/skills/ci-cd-practices/SKILL.md b/.claude/skills/ci-cd-practices/SKILL.md deleted file mode 100644 index b4dcbd5b3..000000000 --- a/.claude/skills/ci-cd-practices/SKILL.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: ci-cd-practices -description: "CI/CD operations: GitHub Actions, Kubernetes, ArgoCD, and CI timeout configuration. Use when configuring pipelines, debugging deployments, or working with GitHub Actions. Do NOT use for general development workflow." ---- - -# CI/CD Practices - -## Quick Start - -1. Use `gh` CLI for GitHub operations (PRs, issues, actions) -2. Use `kubectl` for Kubernetes inspection and debugging -3. Set reasonable CI timeouts (20 minutes default) -4. Always inspect before acting (check PR status, pod logs first) - -## Key Principles - -- Operational tools: `gh` for GitHub, `kubectl` for Kubernetes -- Inspect before acting — check status, logs, and state first -- Use `ubuntu-latest` runners, avoid self-hosted unless justified -- ArgoCD image updater workflow for deployments - -## References - -See [rules.md](references/rules.md) for complete conventions. diff --git a/.claude/skills/ci-cd-practices/references/rules.md b/.claude/skills/ci-cd-practices/references/rules.md deleted file mode 100644 index ccab5ffe1..000000000 --- a/.claude/skills/ci-cd-practices/references/rules.md +++ /dev/null @@ -1,424 +0,0 @@ -# CI/CD Practices - Detailed Rules - -## GitHub CLI Usage - -Scope: GitHub operations - -Rule: Use `gh` CLI for all GitHub-related tasks. - -**Common Operations:** - -Check pull request status: - -```bash -gh pr view 123 -gh pr checks 123 -gh pr diff 123 -``` - -View GitHub Actions runs: - -```bash -gh run list -gh run view -gh run view --log -``` - -Work with issues: - -```bash -gh issue list -gh issue view 123 -gh issue create --title "Bug" --body "Description" -``` - -Create pull requests: - -```bash -gh pr create --title "Feature" --body "Description" -gh pr merge 123 --squash -``` - -Correct: - -```bash -# Check if CI passed before merging -gh pr checks 123 --watch - -# View failed workflow logs -gh run view 12345 --log-failed -``` - -Incorrect: - -```bash -# Don't use curl for GitHub API when gh is available -curl -H "Authorization: token $TOKEN" https://api.github.com/repos/... -``` - ---- - -## Kubernetes Inspection - -Scope: Kubernetes debugging and inspection - -Rule: Use `kubectl` for querying Kubernetes resources. - -**Common Operations:** - -Find pods and services: - -```bash -kubectl get pods -n -kubectl get services -n -kubectl get deployments -n -``` - -Check pod logs: - -```bash -kubectl logs -n -kubectl logs -n --previous # Previous container -kubectl logs -n -f # Follow logs -``` - -Describe resources for debugging: - -```bash -kubectl describe pod -n -kubectl describe service -n -``` - -Get resource details: - -```bash -kubectl get pod -n -o yaml -kubectl get configmap -n -o yaml -``` - -Correct: - -```bash -# Check pod status before investigating -kubectl get pods -n production | grep my-app - -# View recent logs for debugging -kubectl logs -n production deployment/my-app --tail=100 -``` - ---- - -## CI Timeout Configuration - -Scope: GitHub Actions and CI pipelines - -Rule: Set reasonable timeouts for CI jobs. - -**Guidelines:** - -- Default timeout: 20 minutes for standard jobs -- Build jobs: 30 minutes maximum -- Test jobs: 20 minutes maximum -- Deploy jobs: 15 minutes maximum -- Always set explicit timeouts to prevent runaway jobs - -Correct: - -```yaml -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - run: deno install --frozen - - run: deno task build - - test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - run: deno install --frozen - - run: deno test --allow-all -``` - -Incorrect: - -```yaml -jobs: - build: - runs-on: ubuntu-latest - # No timeout - could run indefinitely - steps: - - run: deno task build -``` - ---- - -## Inspect Before Acting - -Scope: All operations - -Rule: Always inspect the current state before making changes. - -**Before merging a PR:** - -```bash -gh pr checks 123 # Ensure CI passed -gh pr diff 123 # Review changes -gh pr view 123 # Check description and reviewers -``` - -**Before deploying:** - -```bash -kubectl get pods -n production # Check current state -kubectl get events -n production # Check for issues -``` - -**Before debugging:** - -```bash -kubectl describe pod # Get pod status and events -kubectl logs # Check application logs -``` - -Correct: - -```bash -# Full inspection before merge -gh pr checks 123 && gh pr view 123 && gh pr merge 123 --squash -``` - -Incorrect: - -```bash -# Merging without checking CI status -gh pr merge 123 --squash -``` - ---- - -## Workflow Best Practices - -Scope: CI/CD pipeline design - -Rule: Follow best practices for reliable pipelines. - -**Checklist:** - -- [ ] All jobs have explicit timeouts -- [ ] Secrets are stored in GitHub Secrets or external vault -- [ ] Workflows use pinned action versions (e.g., `@v4`, not `@main`) -- [ ] Failed jobs have clear error messages -- [ ] Workflows support manual triggers for debugging (`workflow_dispatch`) - -Correct: - -```yaml -name: CI -on: - push: - branches: [main] - pull_request: - workflow_dispatch: # Allow manual triggers - -jobs: - test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - run: deno install --frozen - - run: deno test --allow-all -``` - ---- - -## ArgoCD Image Updater Workflow - -Scope: Kubernetes deployments with ArgoCD - -Rule: Use ArgoCD Image Updater for automated image updates. Do NOT create git -commits for image tag updates - ArgoCD handles this automatically. - -**How It Works:** - -1. CI pushes new image to registry with tag -2. ArgoCD Image Updater detects new image -3. ArgoCD updates deployment automatically (no git commit needed) - -**Annotations for ArgoCD Application:** - -```yaml -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - annotations: - argocd-image-updater.argoproj.io/image-list: myapp=gcr.io/project/myapp - argocd-image-updater.argoproj.io/myapp.update-strategy: semver - argocd-image-updater.argoproj.io/myapp.allow-tags: regexp:^v[0-9]+\.[0-9]+\.[0-9]+$ -``` - -Correct: - -```yaml -# CI workflow - only push image, don't commit tag updates -- name: Push to Container Registry - run: | - docker build -t gcr.io/$PROJECT/myapp:$TAG . - docker push gcr.io/$PROJECT/myapp:$TAG -``` - -Incorrect: - -```yaml -# ❌ Don't commit image tag updates - ArgoCD handles this -- name: Update image tag in kustomization - run: | - sed -i "s/tag:.*/tag: $TAG/" k8s/kustomization.yaml - git commit -am "Update image to $TAG" - git push -``` - -**Benefits:** - -- No git commits for every image update -- Cleaner git history -- Automatic rollback support -- Audit trail in ArgoCD - ---- - -## GitHub Actions Runner Configuration - -Scope: Self-hosted runners and resource optimization - -Rule: Use appropriate runner types and resource limits for different job types. - -**Runner Selection:** - -```yaml -jobs: - # Standard jobs - use ubuntu-latest - lint: - runs-on: ubuntu-latest - timeout-minutes: 10 - - # Build jobs - may need more resources - build: - runs-on: ubuntu-latest - timeout-minutes: 30 - - # Deploy jobs - may need specific access - deploy: - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: production -``` - -**Concurrency Control:** - -```yaml -concurrency: - group: ${{ github.ref }}-${{ github.workflow }} - cancel-in-progress: true -``` - -**Cache Dependencies:** - -```yaml -- name: Cache Go modules - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - -- name: Cache Deno dependencies - uses: actions/cache@v4 - with: - path: ~/.cache/deno - key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }} -``` - ---- - -## Environment-Specific Deployments - -Scope: Multi-environment deployment pipelines - -Rule: Use tag patterns to determine deployment environment. Semantic versioning -tags trigger production, suffixed tags trigger staging/test. - -**Tag Patterns:** - -```bash -v1.0.0 # Production deployment -v1.0.0-rc.1 # Staging deployment -v1.0.0-dev.1 # Test/development deployment -``` - -**Workflow Implementation:** - -```yaml -- name: Determine Environment - run: | - TAG="${GITHUB_REF#refs/tags/}" - - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "DEPLOY_ENV=production" >> $GITHUB_ENV - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc ]]; then - echo "DEPLOY_ENV=stage" >> $GITHUB_ENV - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-dev ]]; then - echo "DEPLOY_ENV=test" >> $GITHUB_ENV - else - echo "Error: Unrecognized tag format" - exit 1 - fi -``` - -**Benefits:** - -- Clear separation of environments -- Automatic environment detection -- Consistent deployment process -- Easy rollback by re-tagging - ---- - -## Reusable Workflows - -Scope: GitHub Actions CI/CD - -Rule: Extract shared CI jobs into `.github/workflows/integration.yml` with -`workflow_call` trigger. Callers use `uses:` + `secrets: inherit`. - ---- - -## Auto-Tag Pattern - -Scope: Release pipeline - -Rule: Release commits (`chore(codebase): release v*`) are auto-tagged by the -`tag-release` job in `build.yml` after integration passes. No manual tagging needed. - ---- - -## Permissions Ceiling - -Scope: GitHub Actions workflows - -Rule: Workflow-level `permissions` sets the ceiling. Job-level permissions restrict -down. Never use `permissions: {}` — it blocks reusable workflows from requesting -any permissions. - ---- - -## npm Provenance - -Scope: npm publishing - -Rule: Use OIDC (`id-token: write`) for npm publish with `--provenance`. Link the -npm package to the GitHub repo in npm settings. No `NPM_TOKEN` secret needed. diff --git a/.claude/skills/coding-practices/SKILL.md b/.claude/skills/coding-practices/SKILL.md deleted file mode 100644 index d9bc92511..000000000 --- a/.claude/skills/coding-practices/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: coding-practices -description: "Code quality: error handling, validation, logging, DRY, and self-documenting code. Use when writing or reviewing code quality, handling errors, or validating inputs. Do NOT use for language-specific syntax (use javascript-practices or go-practices)." ---- - -# Coding Practices - -Guidelines for writing maintainable, robust, and self-documenting code. - -## Quick Start - -```typescript -// Self-documenting with proper error handling -function createUser(email: string, age: number): User { - if (!email.includes("@")) throw new Error("Invalid email"); - if (age < 0 || age > 150) throw new Error("Invalid age"); - return { email, age }; -} -``` - -## Key Principles - -- Compare entities by IDs, never by slugs/usernames/strings -- Use meaningful names (self-documenting code) -- Comments explain "why", not "what" -- DRY: abstract when used 3+ times -- Validate all input data -- Handle all error cases with proper Error objects -- Never ignore errors — handle or propagate with context -- Use named constants instead of magic values -- Explicit checks only — never use truthy/falsy for non-booleans -- Early returns to reduce nesting (guard clauses first) - -## Anti-Patterns - -**"I'll add a quick `if (!value)` check"** -No. Use explicit comparisons: `value === null`, `value === undefined`, `str === ""`. - -**"I'll just throw `new Error('failed')`"** -No. Include context: domain-specific error types, `{ cause: error }`, correlation IDs. - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/coding-practices/references/rules.md b/.claude/skills/coding-practices/references/rules.md deleted file mode 100644 index 60f52cbc8..000000000 --- a/.claude/skills/coding-practices/references/rules.md +++ /dev/null @@ -1,654 +0,0 @@ -# Coding Practices - Detailed Rules - -## Self-Documenting Code - -Scope: All languages - -Rule: Use meaningful variable, class, and function names. Makes code easier to -understand and maintain. - -Correct: - -```typescript -function calculateTotalPrice(items: Item[], taxRate: number): number { - const subtotal = items.reduce((sum, item) => sum + item.price, 0); - const tax = subtotal * taxRate; - return subtotal + tax; -} - -class UserAuthenticationService { - async validateCredentials( - username: string, - password: string, - ): Promise {} -} -``` - -Incorrect: - -```typescript -function calc(arr: Item[], r: number): number { - const s = arr.reduce((a, b) => a + b.price, 0); // what is s? - const t = s * r; // what is t? - return s + t; -} - -class UAS { // unclear abbreviation - async validate(u: string, p: string): Promise {} // u? p? -} -``` - ---- - -## Comments - -Scope: All languages - -Rule: Use comments to explain 'why' and 'how' if code is not self-explanatory. - -Correct: - -```typescript -// Use binary search because dataset can be very large (10M+ items) -// Linear search would be O(n), binary search is O(log n) -function findUser(users: User[], id: string): User | null { - // Binary search implementation -} - -// Cache results for 5 minutes to reduce database load -// Trade-off: slight staleness for better performance -const cache = new Map(); -``` - -Incorrect: - -```typescript -// This function finds a user -function findUser(users: User[], id: string): User | null { // redundant comment -} - -const x = 5 * 60 * 1000; // no explanation of why this specific value -``` - -Good practices: - -- Explain why, not what (code shows what) -- Document trade-offs and decisions -- Explain non-obvious algorithms or optimizations -- Note important constraints or assumptions - ---- - -## Don't Repeat Yourself - -Scope: All languages - -Rule: Abstract common functionality into reusable modules or functions. - -Correct: - -```typescript -function formatUserName(user: User): string { - return `${user.firstName} ${user.lastName}`; -} - -function displayWelcome(user: User) { - console.log(`Welcome, ${formatUserName(user)}`); -} - -function sendEmail(user: User) { - email.send(`Hello ${formatUserName(user)}`); -} -``` - -Incorrect: - -```typescript -function displayWelcome(user: User) { - console.log(`Welcome, ${user.firstName} ${user.lastName}`); // duplicated -} - -function sendEmail(user: User) { - email.send(`Hello ${user.firstName} ${user.lastName}`); // duplicated -} -``` - -When to abstract: - -- Used 3+ times: definitely abstract -- Used 2 times: consider abstracting if logic is complex -- Used 1 time: keep inline unless it improves clarity - ---- - -## Input Validation - -Scope: All languages - -Rule: Validate input data. Ensures robustness and prevents bugs or security -vulnerabilities. - -Correct: - -```typescript -function createUser(email: string, age: number): User { - if (!email.includes("@")) throw new Error("Invalid email"); - if (age < 0 || age > 150) throw new Error("Invalid age"); - return { email, age }; -} -``` - -Incorrect: - -```typescript -function createUser(email: string, age: number): User { - return { email, age }; // no validation -} -``` - ---- - -## Error Handling - -Scope: All languages - -Rule: Handle all possible error cases. Ensures graceful recovery and better -user experience. - -Correct: - -```typescript -async function fetchUser(id: string): Promise { - try { - const response = await fetch(`/api/users/${id}`); - if (!response.ok) return null; - return await response.json(); - } catch (error) { - console.error("Failed to fetch user", error); - return null; - } -} -``` - -Incorrect: - -```typescript -async function fetchUser(id: string): Promise { - const response = await fetch(`/api/users/${id}`); // unhandled - return await response.json(); -} -``` - ---- - -## Error Objects - -Scope: All languages - -Rule: Use proper error objects. Include context via -properties/cause. Avoid string concatenation in messages. - -Correct: - -```typescript -throw new Error("Failed to process payment", { - cause: { userId, amount, reason: "Insufficient funds" }, -}); - -try { - processPayment(data); -} catch (error) { - throw new Error("Payment processing failed", { cause: error }); -} -``` - -Incorrect: - -```typescript -throw new Error("Failed for user " + userId + " amount " + amount); -throw "Payment failed"; // not an Error object -``` - ---- - -## Specific Error Types - -Scope: All languages - -Rule: Throw specific error types, not generic errors. -Provides better context. - -Correct: - -```typescript -class ValidationError extends Error { - constructor(message: string, public field: string) { - super(message); - this.name = "ValidationError"; - } -} - -throw new ValidationError("Invalid email", "email"); -``` - -Incorrect: - -```typescript -throw new Error("Something went wrong"); // too generic -``` - ---- - -## Result Objects - -Scope: All languages - -Rule: Consider result objects instead of throwing errors. -More predictable. - -Correct: - -```typescript -type Result = { ok: true; value: T } | { ok: false; error: E }; - -function parseData(input: string): Result { - if (!input) return { ok: false, error: "Empty input" }; - try { - return { ok: true, value: JSON.parse(input) }; - } catch { - return { ok: false, error: "Invalid JSON" }; - } -} - -const result = parseData(input); -if (result.ok) { - process(result.value); -} -``` - ---- - -## Ignored Errors - -Scope: All languages - -Rule: Never ignore returned errors. Handle appropriately. - -Correct: - -```typescript -try { - await saveToDatabase(data); -} catch (error) { - console.error("Failed to save", error); - throw new Error("Database operation failed", { cause: error }); -} -``` - -Incorrect: - -```typescript -try { - await saveToDatabase(data); -} catch (error) { - // ignored -} -``` - ---- - -## Assertions - -Scope: All languages - -Rule: Use assertions to verify pre/post-conditions and -invariants. Documents assumptions. - -Correct: - -```typescript -function divide(a: number, b: number): number { - console.assert(b !== 0, "Divisor must not be zero"); - console.assert(Number.isFinite(a), "Dividend must be finite"); - return a / b; -} -``` - ---- - -## Logging - -Scope: All languages - -Rule: Use logging for tracing and debugging. Helps -understand program flow. - -Correct: - -```typescript -function processOrder(order: Order) { - console.log("Processing order", { orderId: order.id }); - try { - const result = validateOrder(order); - console.log("Order validated", { orderId: order.id }); - return result; - } catch (error) { - console.error("Validation failed", { orderId: order.id, error }); - throw error; - } -} -``` - ---- - -## Circular Dependencies - -Scope: All languages - -Rule: Avoid circular dependencies. Leads to unexpected -behavior. - -Incorrect: - -```typescript -// file-a.ts -import { funcB } from "./file-b.ts"; -export function funcA() { - funcB(); -} - -// file-b.ts -import { funcA } from "./file-a.ts"; // circular -export function funcB() { - funcA(); -} -``` - ---- - -## Magic Values - -Scope: All languages - -Rule: Avoid magic numbers or strings. Use named constants. - -Correct: - -```typescript -const MAX_RETRIES = 3; -const API_TIMEOUT_MS = 5000; - -if (retries >= MAX_RETRIES) {} -setTimeout(callback, API_TIMEOUT_MS); -``` - -Incorrect: - -```typescript -if (retries >= 3) {} // what is 3? -setTimeout(callback, 5000); // what is 5000? -``` - ---- - -## Structured Logging - -Scope: All logging statements - -Rule: Use appropriate log levels by layer and -include correlation context. - -**Layer Guidelines:** - -- Repository/data layer: Only `warn`, `debug`, `trace` levels -- Service/business layer: Log successful operations at `info` level -- Always include context (trace IDs, user IDs, operation IDs) -- Log errors with full context before propagating - -Correct: - -```typescript -// Service layer - info for successful operations -logger.info("user created", { userId, traceId, operation: "createUser" }); - -// Repository layer - debug for data operations -logger.debug("fetching user from db", { userId, traceId }); - -// Error with full context -logger.error("failed to create user", { - userId, - traceId, - error: err.message, - stack: err.stack, -}); -throw err; -``` - -Incorrect: - -```typescript -console.log("user created"); // no structure, no context -logger.info("db query executed"); // wrong level for repository layer -logger.error("error"); // no context -``` - ---- - -## Error Wrapping - -Scope: All error handling - -Rule: Wrap errors with context for traceability. -Define domain-specific error types. - -Correct: - -```typescript -class UserNotFoundError extends Error { - constructor(public userId: string) { - super(`User not found: ${userId}`); - this.name = "UserNotFoundError"; - } -} - -class PaymentError extends Error { - constructor( - message: string, - public transactionId: string, - options?: ErrorOptions, - ) { - super(message, options); - this.name = "PaymentError"; - } -} - -// Wrap with cause for traceability -try { - await processPayment(data); -} catch (error) { - throw new PaymentError("Failed to process payment", txId, { cause: error }); -} -``` - -Incorrect: - -```typescript -throw new Error("Something went wrong"); // no context -throw originalError; // no wrapping, loses context -throw new Error(originalError.message); // loses stack trace -``` - ---- - -## Explicit Checks (CRITICAL) - -Scope: All languages - -Rule: NEVER use implicit/truthy/falsy checks except for boolean values. Always -use explicit comparisons for null, undefined, empty strings, and zero values. - -Correct: - -```typescript -// Explicit null/undefined checks -if (value === null) {} -if (value !== undefined) {} - -// Explicit string checks -if (string === "") {} -if (string.length === 0) {} - -// Explicit array checks -if (array.length === 0) {} -if (items.length > 0) {} - -// Explicit number checks -if (count === 0) {} -if (index !== -1) {} - -// Boolean values can use implicit checks -if (!response.ok) {} -if (isValid) {} -if (user.isActive) {} - -// Ternary with explicit checks -const result = value !== null ? value : defaultValue; -``` - -Incorrect: - -```typescript -// ❌ Implicit truthy/falsy checks -if (!value) {} // Fails for 0, "", false -if (!string) {} // Fails for "" -if (!array.length) {} // Fails for 0 -if (user) {} // Ambiguous - -// ❌ Implicit ternary -const result = value || defaultValue; // Fails if value is 0 or "" -``` - -**Why This Matters:** - -- `0`, `""`, `false`, `null`, `undefined` are all falsy in JavaScript -- Implicit checks can cause subtle bugs when these are valid values -- Explicit checks document intent and prevent ambiguity -- Makes code review easier - intent is clear - ---- - -## Identity Comparison - -Scope: All languages - -Rule: Always compare entities by their stable unique identifiers (IDs), never by -display strings like slugs, usernames, emails, or titles. Strings are mutable, -can be duplicated, and may have formatting differences (whitespace, casing). -IDs are immutable and guaranteed unique. - -Correct: - -```typescript -// Compare by ID — stable and unambiguous -const isAuthorProfile = story.author_profile?.id === profile.id; -const isSameUser = comment.user_id === currentUser.id; -const isOwnProfile = membership.profile_id === userProfile.id; -``` - -```go -// Go — same principle -if story.AuthorProfileID == profile.ID { - // author's own profile -} -``` - -Incorrect: - -```typescript -// ❌ Comparing by slug — can change, may have formatting issues -const isAuthorProfile = story.author_profile?.slug === slug; - -// ❌ Comparing by username — can change, case-sensitive issues -const isSameUser = comment.username === currentUser.username; - -// ❌ Comparing by email — can change, normalization issues -const isOwner = member.email === user.email; -``` - -**Why This Matters:** - -- Slugs, usernames, and emails can be renamed or reformatted -- String comparisons are vulnerable to whitespace, casing, and encoding issues -- IDs are immutable, unique, and indexed for performance -- Using IDs makes refactoring safer — renaming a slug won't break logic - ---- - -## Early Returns - -Scope: All languages - -Rule: Use early returns to reduce nesting and improve readability. Handle error -cases first, then proceed with the happy path. - -Correct: - -```typescript -function processUser(user: User | null): Result { - if (user === null) { - return { error: "User not found" }; - } - - if (!user.isActive) { - return { error: "User is inactive" }; - } - - // Happy path - no nesting - const profile = loadProfile(user.id); - return { data: profile }; -} -``` - -Incorrect: - -```typescript -function processUser(user: User | null): Result { - if (user !== null) { - if (user.isActive) { - const profile = loadProfile(user.id); - return { data: profile }; - } else { - return { error: "User is inactive" }; - } - } else { - return { error: "User not found" }; - } -} -``` - -**Benefits:** - -- Reduces cognitive load from nested conditionals -- Error handling is visible at the top -- Main logic flows naturally without indentation -- Easier to add new validation checks - ---- - -## File Tool Pure Functions - -Scope: @eser/codebase check/fix tools - -Rule: Check functions receive data, return issues. Fix functions receive data, return -mutations. Never read from filesystem or write to disk inside check/fix logic — that's -the runner's job. - ---- - -## Two-Phase File Loading - -Scope: @eser/codebase file tools - -Rule: Tools receive metadata `{path, name, stat}` first. Call `loadContent(file)` only -when text content is needed. Tools like `check-large-files` and `check-case-conflict` -never need content. diff --git a/.claude/skills/design-principles/SKILL.md b/.claude/skills/design-principles/SKILL.md deleted file mode 100644 index 6b8698538..000000000 --- a/.claude/skills/design-principles/SKILL.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: design-principles -description: "Code design patterns: pure functions, immutability, composition, lazy initialization, and side-effect management. Use when designing function signatures, structuring modules, or choosing between mutable and immutable patterns." ---- - -# Code Design Principles - -Patterns for writing clean, maintainable, and testable code. - -## Quick Start - -```typescript -// Pure function with immutability -function updateUser(user: User, age: number): User { - return { ...user, age }; // new object, no mutation -} -``` - -## Key Principles - -- Pure functions: no side effects, same input = same output -- Immutability: create new objects/arrays instead of mutating -- Single responsibility: one function = one task -- Early returns: see `coding-practices` for details -- Composition over inheritance: inject dependencies -- Promises over callbacks for async code -- Template strings over concatenation -- Plain objects for data, classes only for stateful services -- Avoid global variables and getters/setters -- Lazy initialization: no module-level side effects, wrap in getter functions - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/design-principles/references/rules.md b/.claude/skills/design-principles/references/rules.md deleted file mode 100644 index eb0ad8ac5..000000000 --- a/.claude/skills/design-principles/references/rules.md +++ /dev/null @@ -1,463 +0,0 @@ -# Code Design Principles - Detailed Rules - -## Pure Functions - -Scope: All languages - -Rule: Prefer pure (stateless) functions over stateful ones. -Easier to reason about, test, and reuse. - -Correct: - -```typescript -function calculateTotal(items: Item[]): number { - return items.reduce((sum, item) => sum + item.price, 0); -} - -function formatDate(date: Date): string { - return date.toISOString(); -} -``` - -Incorrect: - -```typescript -let total = 0; -function addToTotal(item: Item) { // modifies external state - total += item.price; -} -``` - ---- - -## Immutability - -Scope: All languages - -Rule: Prefer immutable data structures. Prevents unintended -side effects. - -Correct: - -```typescript -const user = { name: "John", age: 30 }; -const updatedUser = { ...user, age: 31 }; // new object - -const items = [1, 2, 3]; -const newItems = [...items, 4]; // new array -const filtered = items.filter((x) => x > 1); // new array -``` - -Incorrect: - -```typescript -const user = { name: "John", age: 30 }; -user.age = 31; // mutation - -const items = [1, 2, 3]; -items.push(4); // mutation -``` - ---- - -## Single Responsibility - -Scope: All languages - -Rule: Keep functions short and focused on single task. -Easier to understand, test, and reuse. - -Correct: - -```typescript -function validateEmail(email: string): boolean { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); -} - -function sendWelcomeEmail(email: string): void { - const message = createWelcomeMessage(); - sendEmail(email, message); -} -``` - -Incorrect: - -```typescript -function processUser(email: string, data: UserData): void { - // validates email - if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return; - // saves to database - database.save(data); - // sends email - const message = "Welcome!"; - sendEmail(email, message); - // logs activity - console.log("User processed"); -} -``` - ---- - -## Composition Over Inheritance - -Scope: All languages - -Rule: Prefer composition over inheritance. More flexible -and decoupled. - -Correct: - -```typescript -interface Logger { - log(message: string): void; -} - -class UserService { - constructor(private logger: Logger) {} - - createUser(data: UserData) { - this.logger.log("Creating user"); - } -} -``` - -Incorrect: - -```typescript -class BaseService { - log(message: string) {} -} - -class UserService extends BaseService { // tight coupling - createUser(data: UserData) { - this.log("Creating user"); - } -} -``` - ---- - -## Async Patterns - -Scope: JS/TS - -Rule: Prefer promises over callbacks. Cleaner and more -maintainable. - -Correct: - -```typescript -async function fetchUser(id: string): Promise { - const response = await fetch(`/api/users/${id}`); - return response.json(); -} - -function loadData(): Promise { - return fetch("/api/data").then((r) => r.json()); -} -``` - -Incorrect: - -```typescript -function fetchUser(id: string, callback: (user: User) => void) { - fetch(`/api/users/${id}`).then((r) => r.json()).then(callback); -} -``` - ---- - -## String Formatting - -Scope: JS/TS - -Rule: Prefer template strings over concatenation. More readable, -allows embedded expressions. - -Correct: - -```typescript -const greeting = `Hello, ${user.name}!`; -const url = `/api/users/${userId}/posts/${postId}`; -const multiline = ` - Line 1 - Line 2 -`; -``` - -Incorrect: - -```typescript -const greeting = "Hello, " + user.name + "!"; -const url = "/api/users/" + userId + "/posts/" + postId; -``` - ---- - -## Loop Control Flow - -Scope: All languages - -Rule: Use plain for loops when break/continue needed. - -Correct: - -```typescript -for (const item of items) { - if (item.skip) continue; - if (item.error) break; - process(item); -} -``` - -Use array methods when no break/continue needed: - -```typescript -const processed = items - .filter((item) => !item.skip) - .map((item) => process(item)); -``` - ---- - -## Global Variables - -Scope: All languages - -Rule: Avoid global variables. Lead to unintended side -effects. - -Correct: - -```typescript -export function createConfig(env: string) { - return { apiUrl: env === "prod" ? "api.prod" : "api.dev" }; -} - -class AppState { - constructor(private config: Config) {} -} -``` - -Incorrect: - -```typescript -let globalConfig = {}; // global mutable state -let currentUser = null; // global mutable state - -export function setConfig(config: Config) { - globalConfig = config; -} -``` - ---- - -## Class Design - -Scope: All languages - -Rule: Separate data and methods. Use plain objects for -data, classes only for stateful services. - -Plain objects (records): - -```typescript -interface User { - id: string; - name: string; - email: string; -} - -function createUser(name: string, email: string): User { - return { id: generateId(), name, email }; -} -``` - -Functions for behavior: - -```typescript -function validateUser(user: User): boolean { - return user.email.includes("@"); -} -``` - -Classes for stateful services: - -```typescript -class UserRepository { - private cache = new Map(); - - async getUser(id: string): Promise { - if (this.cache.has(id)) return this.cache.get(id)!; - const user = await this.fetchUser(id); - this.cache.set(id, user); - return user; - } -} -``` - ---- - -## Side Effects - -Scope: All languages - -Rule: Avoid side effects in non-pure functions. Store state -closest to where used. - -Correct: - -```typescript -function calculateDiscount(price: number, discountRate: number): number { - return price * (1 - discountRate); -} - -function processOrder(order: Order) { - const discount = calculateDiscount(order.total, 0.1); - const finalPrice = order.total - discount; - return { ...order, finalPrice }; -} -``` - -Incorrect: - -```typescript -let appliedDiscount = 0; // external state - -function calculateDiscount(price: number, rate: number): number { - appliedDiscount = price * rate; // side effect - return price * (1 - rate); -} -``` - ---- - -## Getters/Setters - -Scope: All languages - -Rule: Avoid getter/setters. Can lead to unexpected side -effects. - -Correct: - -```typescript -interface User { - readonly name: string; - readonly email: string; -} - -function updateUserEmail(user: User, email: string): User { - return { ...user, email }; -} -``` - -Incorrect: - -```typescript -class User { - private _email: string; - - get email(): string { - this.logAccess(); // unexpected side effect - return this._email; - } - - set email(value: string) { - this.validateEmail(value); // side effect - this._email = value; - } -} -``` - ---- - -## Module-Level Side Effects - -Scope: All languages - -Rule: Avoid code executed at module scope. Wrap in lazy -initialization functions instead. - -Problems with module-level code: -- Runs on import, not when needed (wastes resources) -- Errors cause entire module import to fail -- Top-level await blocks module graph resolution -- Hard to test (can't control execution timing) -- Prevents tree-shaking -- Creates import order dependencies - -Correct (lazy initialization): - -```typescript -import { loadConfig } from "./config.ts"; - -let cachedSettings: Settings | null = null; -let initPromise: Promise | null = null; - -export const getSettings = async (): Promise => { - if (cachedSettings !== null) return cachedSettings; - - if (initPromise === null) { - initPromise = loadConfig().then((config) => { - cachedSettings = processConfig(config); - return cachedSettings; - }); - } - - return initPromise; -}; -``` - -Correct (sync lazy initialization): - -```typescript -let cachedValue: string | null = null; - -export const getValue = (): string => { - if (cachedValue === null) { - cachedValue = computeExpensiveValue(); - } - return cachedValue; -}; -``` - -Incorrect (module-level side effects): - -```typescript -// Runs on import - can fail, blocks loading -const config = await loadConfig(); -export const settings = processConfig(config); - -// I/O at module level -const envVars = Deno.env.toObject(); -const fileContent = await Deno.readTextFile("./data.json"); - -// Complex computation on import -const processedData = heavyComputation(rawData); -``` - -Acceptable module-level code: - -```typescript -// Pure constants -const MAX_RETRIES = 3; -const API_VERSION = "v1"; - -// Type definitions -interface Config { - apiUrl: string; -} - -// Function/class declarations (no invocation) -function processData(data: Data): Result { - return transform(data); -} - -// Simple object literals without side effects -const defaultOptions = { timeout: 5000, retries: 3 }; -``` - -Refactoring approach: When encountering module-level side -effects, wrap in getter functions that compute on first -call and cache the result. Use promises for async operations. diff --git a/.claude/skills/documentation-conventions/SKILL.md b/.claude/skills/documentation-conventions/SKILL.md deleted file mode 100644 index c689b9a98..000000000 --- a/.claude/skills/documentation-conventions/SKILL.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: documentation-conventions -description: "README and documentation conventions: import patterns in examples, emoji preservation, and descriptive text rules. Use when writing or editing README files, package documentation, or code examples in docs." ---- - -# Documentation Conventions - -Guidelines for writing and maintaining package documentation and README files. - -## Quick Start - -```typescript -// ✅ README examples use namespace imports -import * as functions from "@eser/functions"; - -const result = await functions.run(async function* () { /* ... */ }); -const pipeline = functions.collect(); -``` - -## Key Principles - -- README code examples must use **namespace imports** (`import * as pkg from "@eser/pkg"`) -- Never show sub-path direct imports in README examples -- Never remove existing emojis from files (titles use 🧱, ⚡; footers use 🔗) -- Never strip descriptive details, comments, or explanatory text when rewriting docs -- Preserve all descriptive context — details like "(LIFO cleanup)" exist for a reason - -## Anti-Patterns - -**"I'll simplify the import for the README"** -No. Always use the namespace pattern. Sub-path exports are for advanced users, not docs. - -**"I'll shorten this description to save space"** -No. Preserve descriptive context. If the original says "guaranteed cleanup", keep it. - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/documentation-conventions/references/rules.md b/.claude/skills/documentation-conventions/references/rules.md deleted file mode 100644 index ecd8a2504..000000000 --- a/.claude/skills/documentation-conventions/references/rules.md +++ /dev/null @@ -1,115 +0,0 @@ -# Documentation Conventions - Detailed Rules - -## README Import Pattern - -Scope: All package README.md files - -Rule: README code examples must use the **namespaced import pattern** -(`import * as pkg from "@eser/pkg"`) and access everything through the -namespace. Never show sub-path direct imports in README examples. Sub-path -exports exist in deno.json for advanced users, but READMEs showcase the primary -namespace pattern. - -Correct: - -```typescript -import * as functions from "@eser/functions"; - -// Access through namespace -const result = await functions.run(async function* () { /* ... */ }); -const pipeline = functions.collect(); -const myTask = functions.task.fromPromise(() => fetch("/api")); -const data = await functions.resources.bracket(acquire, use, release); -``` - -Incorrect: - -```typescript -// ❌ Sub-path imports in README examples -import { collect } from "@eser/functions"; -import * as resources from "@eser/functions/resources"; -import * as task from "@eser/functions/task"; -``` - ---- - -## Emoji Preservation - -Scope: All documentation files - -Rule: Never remove existing emojis from files. Package README titles use emojis -(e.g. 🧱, ⚡) and footers use 🔗. Preserve them when editing. - -Correct: - -```markdown -# 🧱 @eser/primitives -... -## 🔗 Links -``` - -Incorrect: - -```markdown -# @eser/primitives -... -## Links -``` - ---- - -## Descriptive Text Preservation - -Scope: All documentation files - -Rule: Never strip descriptive details, comments, or explanatory text when -rewriting documentation. If the original says "Safe acquire-use-release patterns -with guaranteed cleanup", do not shorten it to "Safe acquire-use-release -patterns". Preserve all descriptive context — details like "(LIFO cleanup)" and -"in reverse order" exist for a reason. - -Correct: - -```markdown -Safe acquire-use-release patterns with guaranteed cleanup (LIFO order) -``` - -Incorrect: - -```markdown -Safe acquire-use-release patterns -``` - ---- - -## Package README Structure - -Scope: All package README.md files - -Rule: Each package README should include: - -1. **Title** with emoji and package name -2. **Vision/description** — what the package does and its design philosophy -3. **Quick Start** — minimal usage examples with namespace imports -4. **API sections** — grouped by category with examples -5. **Links footer** with 🔗 emoji - ---- - -## Cross-Package References - -Scope: Documentation that references other packages - -Rule: When documenting how packages relate, clearly state what each layer -provides and how they differ. Use concrete examples showing the import from -the correct source package. - -Correct: - -```markdown -Types and constructors come from `@eser/primitives`: -import * as results from "@eser/primitives/results"; - -Pure FP utilities come from `@eser/fp`: -import * as fp from "@eser/fp"; -``` diff --git a/.claude/skills/eser-rules-manager/SKILL.md b/.claude/skills/eser-rules-manager/SKILL.md deleted file mode 100644 index 2ae4e5e50..000000000 --- a/.claude/skills/eser-rules-manager/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: eser-rules-manager -description: Skill discovery and rule management. Use when starting any conversation to identify applicable skills, and when user states preferences or asks to add/modify rules. ---- - -# Skill Discovery & Rule Management - -## Skill Discovery (Mandatory) - -**Before ANY response** (including clarifying questions): -1. Scan user message for applicable skills -2. Invoke relevant skills using Skill tool -3. Announce: "Applying skills: [list]", then respond - -Even 1% probability requires checking skills first. This is not optional. - -## Rule Management - -1. Identify scope → choose skill (or create new) -2. Update rule in `.claude/skills//references/rules.md` - -## Available Skills - -| Skill | Triggers | -| --------------------------- | ----------------------------------------------- | -| `eserstack-monorepo` | Package structure, versions, adding packages | -| `agent-guidelines` | Agent roles, safety rules, cross-package changes| -| `release-management` | Version bumps, releases, publishing | -| `architecture-guidelines` | System design, ADRs, testing strategy | -| `design-principles` | Pure functions, immutability, composition | -| `coding-practices` | Error handling, validation, DRY, explicit checks| -| `documentation-conventions` | READMEs, import patterns in docs, emoji rules | -| `javascript-practices` | JS/TS code, modules, types, React patterns | -| `go-practices` | Go code, hexagonal architecture | -| `tooling-standards` | Deno, JSR registry, config files | -| `security-practices` | Auth, secrets, validation, SSRF | -| `workflow-practices` | Task execution, git commits | -| `ci-cd-practices` | GitHub Actions, Kubernetes, deployments | -| `requirement-clarification` | Unclear scope, multiple approaches | - -## References - -- [skill-discovery.md](references/skill-discovery.md) - Mandatory invocation rules -- [skill-format.md](references/skill-format.md) - Creating/updating skills -- [skill-testing.md](references/skill-testing.md) - TDD for skills diff --git a/.claude/skills/eser-rules-manager/references/skill-discovery.md b/.claude/skills/eser-rules-manager/references/skill-discovery.md deleted file mode 100644 index c8420afc2..000000000 --- a/.claude/skills/eser-rules-manager/references/skill-discovery.md +++ /dev/null @@ -1,188 +0,0 @@ -# Skill Discovery & Invocation - -Mandatory skill identification and invocation at every conversation start. - ---- - -## The Non-Negotiable Rule - -**Invoke relevant skills BEFORE any response or action.** - -This includes: -- Before answering questions -- Before asking clarifying questions -- Before exploring code -- Before making suggestions - -Even 1% probability of skill relevance requires checking first. - ---- - -## Discovery Workflow - -``` -1. Receive user message -2. Scan for skill triggers (keywords, file types, task types) -3. Identify ALL potentially relevant skills -4. Invoke skills using Skill tool -5. Announce: "Applying skills: [list]" -6. Follow skill instructions -7. Then respond to user -``` - -### Example - -User: "Add a new API endpoint for user preferences" - -Before responding: -``` -Applying skills: javascript-practices, workflow-practices, security-practices -``` - -Then proceed with the task following those skill guidelines. - ---- - -## Skill Trigger Keywords - -| Skill | Trigger Keywords/Patterns | -| --------------------------- | ------------------------------------------------- | -| `javascript-practices` | .ts, .tsx, .js, React, TypeScript, module, import | -| `go-practices` | .go, Go, hexagonal, service, repository | -| `workflow-practices` | implement, add, fix, refactor, task, commit | -| `requirement-clarification` | unclear, multiple ways, should I, which approach | -| `security-practices` | auth, password, secret, token, validation, CORS | -| `ci-cd-practices` | deploy, CI, GitHub Actions, Kubernetes, pipeline | -| `tooling-standards` | deno, npm, package.json, config, lint, format | -| `coding-practices` | error handling, validation, testing, DRY | -| `architecture-guidelines` | structure, design, ADR, hexagonal, layers | - ---- - -## Skill Prioritization - -When multiple skills apply, invoke in this order: - -1. **Process skills first** (determine approach) - - `requirement-clarification` - clarify before implementing - - `workflow-practices` - establish execution plan - -2. **Implementation skills second** (guide execution) - - `javascript-practices`, `go-practices` - language conventions - - `security-practices` - security requirements - - `coding-practices` - quality standards - -3. **Operational skills last** (deployment/CI) - - `ci-cd-practices` - pipeline and deployment - ---- - -## Skill Types - -### Rigid Skills (Follow Exactly) - -These skills have strict workflows that must be followed step-by-step: - -- `workflow-practices` - git commit process -- `ci-cd-practices` - deployment procedures -- `security-practices` - security requirements - -### Flexible Skills (Adapt Principles) - -These skills provide guidelines to adapt contextually: - -- `javascript-practices` - coding conventions -- `go-practices` - architectural patterns -- `coding-practices` - general quality - ---- - -## Red Flag Rationalizations - -Stop immediately if you're thinking any of these: - -**"I'll just answer quickly first"** -No. Skills come before ANY response. - -**"This is just a simple question"** -Questions still require skill context. Check first. - -**"I already know how to do this"** -Your knowledge doesn't replace documented workflows. - -**"I'll check skills after gathering more info"** -Wrong order. Skills first, then information gathering. - -**"No skills seem directly relevant"** -If even 1% might apply, check. `workflow-practices` applies to almost everything. - -**"The user seems in a hurry"** -Speed doesn't override process. Skills prevent costly mistakes. - -**"I'll mention skills later"** -Invoke at start. Later is too late. - ---- - -## Common Mistakes - -### Mistake 1: Responding Before Invoking - -``` -❌ Wrong: -User: "Fix the login bug" -Claude: "I'll look at the login code..." [no skill invocation] - -✅ Correct: -User: "Fix the login bug" -Claude: "Applying skills: workflow-practices, security-practices, javascript-practices" -[Then proceeds with investigation] -``` - -### Mistake 2: Asking Questions Before Invoking - -``` -❌ Wrong: -User: "Add caching to the API" -Claude: "What caching strategy do you prefer?" [no skill invocation] - -✅ Correct: -User: "Add caching to the API" -Claude: "Applying skills: requirement-clarification, javascript-practices" -[requirement-clarification guides how to ask about caching strategy] -``` - -### Mistake 3: Invoking Only Obvious Skills - -``` -❌ Wrong: -User: "Write a Go service for notifications" -Claude: "Applying skills: go-practices" [missed others] - -✅ Correct: -Claude: "Applying skills: go-practices, architecture-guidelines, - workflow-practices, security-practices" -``` - ---- - -## Verification Checklist - -Before every response, verify: - -- [ ] Scanned message for ALL potential skill triggers -- [ ] Invoked relevant skills using Skill tool -- [ ] Announced which skills are active -- [ ] Following skill instructions in my response -- [ ] Not rationalizing skipping any skills - ---- - -## Meta-Rule - -If uncertain whether a skill applies: **invoke it anyway**. - -The cost of unnecessary invocation is minimal. -The cost of missing a relevant skill can be significant. - -Default to inclusion, not exclusion. diff --git a/.claude/skills/eser-rules-manager/references/skill-format.md b/.claude/skills/eser-rules-manager/references/skill-format.md deleted file mode 100644 index 29b866430..000000000 --- a/.claude/skills/eser-rules-manager/references/skill-format.md +++ /dev/null @@ -1,304 +0,0 @@ -# Claude Skills Format Requirements - -Complete reference for creating and maintaining Claude Skills. - -## Directory Structure - -``` -skill-name/ -├── SKILL.md # Required: Main skill file (<50 lines) -└── references/ # Optional: Detailed documentation - └── rules.md # Detailed rules with examples -``` - ---- - -## YAML Frontmatter - -Required fields at the top of SKILL.md: - -```yaml ---- -name: skill-name -description: What it does and when to use it ---- -``` - -### name Field - -- Lowercase letters, numbers, hyphens only -- Max 64 characters -- Must be unique across all skills - -Correct: `javascript-practices`, `eser-rules-manager` Incorrect: -`JavaScript_Practices`, `my skill` - -### description Field - -- Max 1024 characters (target <200 for efficiency) -- Must include WHAT the skill does AND WHEN to use it -- Start with action, follow with "Use when..." trigger phrase -- Include keywords: error messages, symptoms, tool names, synonyms - -Correct: - -```yaml -description: JavaScript and TypeScript conventions for syntax, modules, types, and runtime behavior. Use when writing or reviewing JS/TS code, implementing modules, handling types, or working with runtime APIs. -``` - -```yaml -description: Conventions for adding routes, middleware, and handlers. Use when adding new endpoints, modifying routing, or implementing API handlers. -``` - -Incorrect: - -```yaml -description: JavaScript stuff # Too vague, no trigger context -``` - -```yaml -description: Helps with routing stuff # Missing "Use when..." trigger, too vague -``` - ---- - -## SKILL.md Body Requirements - -### Limits - -- **Max 50 lines** (hard limit) -- Max 1000 words -- 1-2 code examples recommended -- 3-5 sections recommended - -### Required Sections - -1. **Title** (H1): Matches skill name -2. **Quick Start**: Essential usage in 3-5 steps -3. **Key Principles**: Bullet points of core rules -4. **References**: Links to detailed documentation - -### Example Structure - -```markdown -# Skill Title - -Brief intro (1-2 sentences). - -## Quick Start - -Minimal steps to use this skill. - -## Key Principles - -- Bullet point 1 -- Bullet point 2 -- Bullet point 3 - -## References - -See [rules.md](references/rules.md) for complete guidelines. -``` - ---- - -## Progressive Disclosure - -Skills use 3-level loading for token efficiency: - -**Level 1 - SKILL.md (Always Loaded)** - -- Quick overview and key principles -- Kept under 50 lines -- Loaded when skill is discovered - -**Level 2 - references/ (Loaded on Demand)** - -- Detailed rules with Correct/Incorrect examples -- Read only when Claude needs specifics -- Can be extensive (100+ lines) - -**Level 3 - Supporting Files (Loaded on Demand)** - -- Scripts, templates, configs -- Located in scripts/, templates/ -- Read only when explicitly needed - ---- - -## references/ Directory - -Store detailed documentation that's loaded only when needed. - -### Common Files - -- `references/rules.md` - Detailed rules with examples -- `references/guide.md` - Comprehensive implementation guide -- `references/examples.md` - Extended examples - -### Linking from SKILL.md - -```markdown -## References - -See [rules.md](references/rules.md) for complete guidelines. -``` - -### Rule Format in references/ - -````markdown -## Section Name - -Scope: applicable context - -Rule: concise statement - -Correct: - -```code -example -``` -```` - -Incorrect: - -```code -example -``` - -```` -Use `---` to separate major sections. - ---- - -## Validation - -Always validate after creating or modifying a skill: - -```bash -npx -y claude-skills-cli validate .claude/skills/ -```` - -### Validation Checks - -| Check | Requirement | -| ------------- | ------------------------ | -| YAML Syntax | Valid YAML, no tabs | -| name | kebab-case, max 64 chars | -| description | Under 1024 chars | -| SKILL.md body | Under 50 lines | -| File location | Correct path structure | - -### Common Errors - -**"SKILL.md body is X lines (MAX: 50)"** → Move detailed content to references/ - -**"Description is X characters (recommended: <200)"** → Shorten description, -move details to SKILL.md body - -**"Missing Quick Start section"** → Add `## Quick Start` with minimal usage -steps - -**"No references/ links found but SKILL.md is large"** → Split content into -references/ files - ---- - -## Creating a New Skill - -1. Create directory: `.claude/skills/skill-name/` -2. Create `SKILL.md` with frontmatter and sections -3. Create `references/rules.md` with detailed rules -4. Validate: `npx -y claude-skills-cli validate .claude/skills/skill-name` -5. Test: Ask Claude questions that should trigger the skill - ---- - -## Updating Existing Skills - -1. Edit `references/rules.md` for detailed rule changes -2. Update `SKILL.md` key principles if needed -3. Validate after changes -4. Keep SKILL.md under 50 lines - ---- - -## Anti-Rationalization Sections - -Bulletproof discipline-enforcing skills by adding explicit counters. - -### Pattern - -```markdown -## Anti-Patterns - -**"This is a special case"** -No. The rule applies to ALL cases. Ask for explicit exception approval. - -**"I already tested it manually"** -Manual testing doesn't replace automated tests. Run the test suite. - -**"Being pragmatic means adapting"** -Pragmatism doesn't mean skipping quality gates. Follow the process. -``` - -### Red Flags List - -Include warning signs that indicate rule-breaking temptation: - -```markdown -## Red Flags - -Stop and reconsider if you're thinking: -- "Just this once..." -- "The user won't notice..." -- "I can fix it later..." -- "It's close enough..." -``` - ---- - -## Terminology Consistency - -Pick one term per concept; use throughout the skill. - -| Concept | Pick ONE | Avoid mixing | -| ------------- | -------- | --------------------------- | -| API paths | endpoint | URL, route, path | -| Form elements | field | input, box, element | -| Data action | extract | pull, get, fetch, retrieve | -| Error action | return | throw, raise, emit | - ---- - -## Degrees of Freedom - -Match instruction specificity to task fragility. - -**High Freedom** (multiple valid approaches): - -``` -Choose an appropriate data structure for the use case. -``` - -**Medium Freedom** (preferred patterns with flexibility): - -``` -1. Validate input -2. Transform data -3. Return result or error -``` - -**Low Freedom** (exact steps required): - -``` -Run exactly: `make lint && make test` -Do not modify or skip steps. -``` - ---- - -## Skills Inventory - -See the `eser-rules-manager` SKILL.md for the full skill table. -Create new skills for: `ui/`, `testing/` when needed. diff --git a/.claude/skills/eser-rules-manager/references/skill-testing.md b/.claude/skills/eser-rules-manager/references/skill-testing.md deleted file mode 100644 index 348da3def..000000000 --- a/.claude/skills/eser-rules-manager/references/skill-testing.md +++ /dev/null @@ -1,253 +0,0 @@ -# Skill Testing & Validation - -Test-Driven Development applied to skill documentation. - ---- - -## TDD for Skills: RED-GREEN-REFACTOR - -Rule: Never write a skill without observing a failure first. - -### RED Phase (Baseline) - -1. Run scenarios WITHOUT the skill present -2. Document exact failures and rationalizations word-for-word -3. Create pressure scenarios with 3+ simultaneous constraints -4. Identify what actually needs preventing (not assumptions) - -### GREEN Phase (Write Skill) - -1. Write minimal documentation addressing observed failures -2. Re-test with skill present -3. Agents should now comply -4. If violations continue, clarify the skill - -### REFACTOR Phase (Close Loopholes) - -1. Identify new rationalizations during testing -2. Add explicit counters for each excuse -3. Update anti-pattern sections -4. Re-test until compliance achieved - ---- - -## Pressure Scenario Design - -Rule: Test skills under realistic operational pressure, not ideal conditions. - -### Constraint Types - -Combine 3+ of these in test scenarios: - -- **Time scarcity**: Tight deadlines, closing windows -- **Sunk cost**: Hours invested, reluctance to discard work -- **Fatigue**: End-of-session, competing priorities -- **Authority pressure**: User insistence or directive -- **Economic stakes**: Importance of task completion -- **Social dynamics**: Appearing inflexible or unhelpful - -### Weak vs Strong Scenarios - -Weak (single pressure): - -``` -"Implement this feature quickly" -``` - -Strong (layered pressures): - -``` -"We're 3 hours into this refactor, the user is getting frustrated, -and they just said 'just make it work, we can fix it later'. -Should you skip the tests?" -``` - ---- - -## Rationalization Capture - -Rule: Document agent excuses verbatim to identify skill gaps. - -### Common Rationalizations - -Track these patterns and add explicit counters: - -- "This case is different because..." -- "I'm following the spirit, not the letter" -- "Being pragmatic means adapting rules" -- "I already manually verified it works" -- "The user implicitly approved this" -- "It's just a small exception" - -### Counter Pattern - -For each rationalization, add explicit negation: - -```markdown -## Anti-Patterns - -**"This is a special case"** -No. The rule applies to ALL cases. If you think it's special, -ask the user to confirm the exception explicitly. -``` - ---- - -## Cross-Model Testing - -Rule: Test skills with multiple model tiers. - -| Model | Testing Focus | -| ------ | -------------------------------------------- | -| Haiku | Needs most guidance; reveals unclear areas | -| Sonnet | Balanced; good baseline for average behavior | -| Opus | Most capable; may mask skill ambiguities | - -If Opus complies but Haiku doesn't, the skill needs more explicit guidance. - ---- - -## Degrees of Freedom - -Rule: Match instruction specificity to task fragility. - -### High Freedom (Text Instructions) - -Use when: Multiple valid approaches exist, context-dependent decisions. - -```markdown -Choose an appropriate data structure for the use case. -``` - -### Medium Freedom (Pseudocode) - -Use when: Preferred patterns exist but some variation acceptable. - -```markdown -1. Validate input -2. Transform data -3. Return result or error -``` - -### Low Freedom (Exact Scripts) - -Use when: Operations are fragile, consistency critical. - -```markdown -Run exactly: `npm run build && npm test` -Do not modify or skip steps. -``` - ---- - -## Feedback Loops - -Rule: Build validator → fix → repeat patterns into skills. - -### Pattern Structure - -``` -1. Create output/plan -2. Validate against requirements -3. If issues found → revise and revalidate -4. Proceed only when validation passes -``` - -### Example - -```markdown -## Workflow - -1. Generate migration SQL -2. Run: `make lint` to validate syntax -3. If errors, fix and re-run lint -4. Only after lint passes, run: `make migrate` -``` - ---- - -## Search Optimization - -Rule: Optimize descriptions and content for skill discovery. - -### Keyword Categories - -Include all relevant keywords: - -- **Error messages**: Exact text from common errors -- **Symptoms**: "flaky", "hanging", "slow", "failing" -- **Synonyms**: "test/spec/check", "lint/format/style" -- **Tool names**: Actual CLI tools and frameworks - -### Naming Conventions - -Use active voice with gerund form when appropriate: - -Correct: `processing-pdfs`, `validating-forms`, `handling-errors` - -Incorrect: `pdf-helper`, `form-utils`, `error-stuff` - ---- - -## Pre-Share Checklist - -### Content Quality - -- [ ] Description includes WHAT + WHEN triggers (<200 chars) -- [ ] SKILL.md under 50 lines -- [ ] Detailed content in references/ -- [ ] No time-sensitive information -- [ ] Consistent terminology throughout -- [ ] Concrete Correct/Incorrect examples -- [ ] File references one level deep only -- [ ] Clear workflows with numbered steps - -### Testing Quality - -- [ ] Baseline (RED) test completed without skill -- [ ] At least 3 pressure scenarios tested -- [ ] Tested with multiple model tiers if available -- [ ] Rationalizations captured and countered -- [ ] Real usage scenarios validated - -### Anti-Patterns Covered - -- [ ] Common excuses explicitly addressed -- [ ] Spirit-vs-letter arguments countered -- [ ] Red flags section for warning signs -- [ ] No vague guidance ("be careful", "use judgment") - ---- - -## Terminology Consistency - -Rule: Choose one term per concept; use throughout. - -### Examples - -| Concept | Pick ONE | Avoid mixing | -| ------------- | ------------------- | ------------------------- | -| API paths | endpoint | URL, route, path | -| Form elements | field | input, box, element | -| Data action | extract | pull, get, fetch, retrieve | -| File action | create | write, generate, make | - ---- - -## Collaborative Development - -Rule: Use separate Claude instances for refinement vs testing. - -### Process - -1. **Claude A**: Refine skill instructions iteratively -2. **Claude B**: Test on real tasks (fresh context) -3. **Observe**: Navigation patterns, missed sections, confusion points -4. **Iterate**: Based on observed behavior, not assumptions - -### Observation Points - -- Unexpected exploration → structure needs clarity -- Missed connections → links need prominence -- Overreliance on one section → move to main file -- Ignored content → remove or improve signaling diff --git a/.claude/skills/eserstack-monorepo/SKILL.md b/.claude/skills/eserstack-monorepo/SKILL.md deleted file mode 100644 index dd50e5de2..000000000 --- a/.claude/skills/eserstack-monorepo/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: eserstack-monorepo -description: Monorepo structure, package conventions, version synchronization, and publishing workflow. Use when adding packages, modifying project structure, or managing versions. ---- - -# eserstack Monorepo - -Multi-language monorepo: Deno/TypeScript packages (JSR/npm) + Go services. - -## Quick Start - -1. Each package: `pkg/@eser//` with `deno.json`, `mod.ts`, `mod.test.ts` -2. Unified version — use `deno task cli codebase versions ` -3. JSR primary (`deno publish`), npm secondary (only `@eser/cli`) -4. Run `deno task validate` to check entire monorepo - -## Key Principles - -- Entry point: `mod.ts` (or `main.ts` for CLIs) -- Tests: `*.test.ts` co-located with source, benchmarks: `*.bench.ts` -- File naming: kebab-case enforced by pre-commit hook -- License header required on all `.ts` files -- Explicit `exports` and `publish.include` in each `deno.json` -- Never manually edit version fields — always use the version-bump script -- `@eser/standards` is the foundation — change carefully - -## Go Services - -Go code lives in `apps/services/` with independent git-tag versioning. -- Run `make go-ok` for Go-only validation -- Go does NOT use the unified version-bump script -- Follow hexagonal architecture — see `go-practices` skill - -## References - -See [rules.md](references/rules.md) for package conventions, adding packages, and templates. diff --git a/.claude/skills/eserstack-monorepo/references/rules.md b/.claude/skills/eserstack-monorepo/references/rules.md deleted file mode 100644 index 55b7fa3ec..000000000 --- a/.claude/skills/eserstack-monorepo/references/rules.md +++ /dev/null @@ -1,260 +0,0 @@ -# eserstack Monorepo - Detailed Rules - -## Package Layout - -Scope: All packages under `pkg/@eser/*` and `pkg/@cool/*` - -Rule: Every package follows a standard directory structure with specific file conventions. - -``` -pkg/@eser// -├── deno.json # Package config (name, version, exports, publish) -├── mod.ts # Entry point (barrel exports with license header) -├── mod.test.ts # Entry point tests -├── *.ts # Source modules (kebab-case names) -├── *.test.ts # Co-located tests -├── *.bench.ts # Co-located benchmarks -└── README.md # Package documentation -``` - ---- - -## Package deno.json - -Scope: Each package's `deno.json` - -Rule: Must include name, version (matching root), exports, and publish configuration. - -Correct: - -```json -{ - "name": "@eser/my-package", - "version": "4.0.43", - "exports": { - ".": "./mod.ts" - }, - "publish": { - "include": ["mod.ts", "*.ts", "README.md", "LICENSE"] - } -} -``` - -Incorrect: - -```json -{ - "name": "@eser/my-package", - "version": "1.0.0", - "exports": "./mod.ts" -} -``` - -**Why:** Unified versioning ensures all packages are released together. Explicit `publish.include` prevents accidentally publishing test files or internal scripts. - ---- - -## License Headers - -Scope: All `.ts` files in packages - -Rule: Every TypeScript file must start with the Apache-2.0 license header. - -Correct: - -```typescript -// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. -``` - -Validated by: `deno task cli codebase validate --only licenses` -Auto-fixed by: `deno task cli codebase validate --only licenses --fix` - ---- - -## File Naming - -Scope: All source files - -Rule: Use kebab-case for all file names. Enforced by pre-commit hook. - -Correct: `my-module.ts`, `http-client.test.ts`, `parse-args.bench.ts` - -Incorrect: `myModule.ts`, `HttpClient.test.ts`, `ParseArgs.bench.ts` - ---- - -## Module Entry Points - -Scope: Package entry points - -Rule: Use `mod.ts` as the barrel export file. Use `main.ts` for CLI entry points. - -Correct: - -```typescript -// mod.ts - re-export public API -export { myFunction } from "./my-function.ts"; -export type { MyType } from "./types.ts"; -``` - -Incorrect: - -```typescript -// index.ts - wrong convention for Deno -export { myFunction } from "./my-function.ts"; -``` - ---- - -## Development Commands - -Scope: Development workflow - -Rule: Use deno tasks defined in root `package.json`. Makefile targets wrap these. - -| Command | Purpose | -|---------|---------| -| `eser ok` | Full validation (fmt, lint, tools, types, tests, publish dry-run) | -| `eser fix` | Auto-fix version of validation | -| `eser test` | Run tests with coverage | -| `eser test-watch` | Run tests in watch mode | -| `eser check` | Type-check all package entry points | -| `eser lint` | Run linter | -| `eser fmt` | Run formatter | -| `eser build` | Build @eser/cli for npm | -| `eser --help` | Show all available commands and scripts | - ---- - -## Adding a New Package - -Scope: Creating new `@eser/*` packages - -Rule: Follow this exact sequence: - -1. Create directory: `pkg/@eser//` -2. Create `deno.json` with name, version (matching current root version), exports, publish -3. Create `mod.ts` with license header and barrel exports -4. Create `mod.test.ts` with initial tests using `@std/assert` -5. Create `README.md` with package description, installation, usage -6. Package is auto-included via `pkg/@eser/*` glob in root `package.json` -7. Run `deno task validate` to verify - -Template available at: `etc/templates/library-pkg/` - ---- - -## Version Synchronization - -Scope: All package versions - -Rule: All packages share one version. Never manually edit version fields. - -Correct: - -```bash -deno task cli codebase versions patch # 4.0.43 → 4.0.44 -deno task cli codebase versions minor # 4.0.43 → 4.1.0 -make version-bump TYPE=patch # Makefile shortcut -``` - -Incorrect: - -```bash -# Manually editing one deno.json — breaks synchronization -vim ./pkg/@eser/fp/deno.json -``` - ---- - -## Key Files - -| File | Purpose | -|------|---------| -| `deno.json` | Root config (lint rules, unstable features, excludes) | -| `package.json` | npm workspace root, deno task scripts | -| `.manifest.yml` | Pre-commit hooks (20+ checks) | -| `pkg/@eser/*/deno.json` | Per-package TS config | -| `pkg/@eser/*/mod.ts` | TS package entry points | -| `apps/services/go.mod` | Go module definition + tool directives | -| `apps/services/Makefile` | Go-specific build targets | -| `apps/services/.golangci.yaml` | Go linting configuration | -| `pkg/@eser/codebase/versions.ts` | Synchronized TS version bumping | -| `etc/templates/library-pkg/` | New TS package template | -| `etc/templates/go-service/` | New Go service template | -| `Makefile` | Unified command interface (Deno + Go) | - ---- - -## Go Package Layout - -Scope: Go services under `apps/services/` - -Rule: Go services live in `apps/services/` with independent git-tag versioning. They do NOT participate in the unified TS version-bump script. - -``` -apps/services/ -├── go.mod # Module definition + tool directives -├── go.sum -├── Makefile # Go targets: ok, check, lint, fix, test, build -├── .golangci.yaml # Linting rules -├── cmd/ -│ └── serve/ -│ └── main.go # HTTP server entry point -└── pkg/ - └── api/ - ├── adapters/ - │ └── appcontext/ - │ └── appcontext.go # Composition root - └── business/ # Pure business logic (no external deps) -``` - -**Go module path:** `github.com/eser/stack/apps/services` - -**Go versioning:** Uses git tags (e.g., `apps/services/v0.1.0`), NOT the unified version-bump script. - ---- - -## Go Development Commands - -Scope: Go development workflow - -| Command | Purpose | -|---------|---------| -| `make go-ok` | Run all Go checks (fmt, vet, lint, tests) | -| `make go-test` | Run Go tests with race detector | -| `make go-lint` | Run golangci-lint | -| `make go-fmt` | Auto-fix Go formatting | -| `make go-build` | Build Go binaries | -| `cd apps/services && make ok` | Same as `make go-ok` | - ---- - -## Adding a Go Business Domain - -Scope: Adding new business logic to Go services - -Rule: Follow hexagonal architecture — business logic has zero external dependencies. - -1. Create directory: `apps/services/pkg/api/business//` -2. Define interfaces (ports) for external interactions -3. Implement pure business logic with no imports outside stdlib -4. Create adapters in `apps/services/pkg/api/adapters/` for external implementations -5. Wire in composition root (`apps/services/pkg/api/adapters/appcontext/`) -6. Run `make go-ok` to validate - -See `go-practices` skill for detailed conventions. - ---- - -## Templates - -Scope: Project starter templates in `etc/templates/` - -Rule: Templates are excluded from linting/formatting via `deno.json` excludes. - -Available templates: -- `laroux-app/` — Full framework app with TypeScript, Tailwind -- `jsx-runtime-app/` — JSX runtime test app -- `library-pkg/` — New `@eser/*` package starter (with placeholders) -- `go-service/` — Go service with hexagonal architecture diff --git a/.claude/skills/go-practices/SKILL.md b/.claude/skills/go-practices/SKILL.md deleted file mode 100644 index 1158af13a..000000000 --- a/.claude/skills/go-practices/SKILL.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: go-practices -description: "Go service conventions: hexagonal architecture, snake_case files, error wrapping with fmt.Errorf, table-driven tests, and OpenTelemetry. Use when writing .go files or creating Go services." ---- - -# Go Practices - -## Quick Start - -1. Follow hexagonal architecture: business logic in `pkg/api/business/`, - adapters in `pkg/api/adapters/` -2. Use snake_case for all Go files -3. Wrap errors: `fmt.Errorf("%w: %w", ErrSentinelError, err)` -4. Run tests with race detection: `go test -race ./...` - -## Key Principles - -- Business logic has NO external dependencies -- All external interactions through interfaces -- Composition via AppContext (composition root) -- Table-driven tests with `t.Parallel()` -- OpenTelemetry for observability - -## References - -See [rules.md](references/rules.md) for complete conventions. diff --git a/.claude/skills/go-practices/references/rules.md b/.claude/skills/go-practices/references/rules.md deleted file mode 100644 index f95d3129a..000000000 --- a/.claude/skills/go-practices/references/rules.md +++ /dev/null @@ -1,427 +0,0 @@ -# Go Practices - Detailed Rules - -## Hexagonal Architecture - -Scope: Go projects - -Rule: Follow strict hexagonal architecture with clear -separation of concerns. - -**Core Layers:** - -- **Business Logic** (`pkg/api/business/`) - Pure domain logic with NO external - dependencies -- **Adapters** (`pkg/api/adapters/`) - External integrations (HTTP, Redis, LLM - providers) -- **Framework** (`pkg/eser-go/`) - Shared infrastructure components -- **Applications** (`cmd/`) - Entry points (serve, cli) - -**Key Rules:** - -- Business logic ONLY depends on other business logic -- All external interactions through well-defined interfaces -- Ports are interfaces defined within domain/business packages -- Dependency injection via AppContext (composition root) -- All composition happens in composition root - (`pkg/api/adapters/appcontext/appcontext.go`) - -Correct: - -```go -// pkg/api/business/user/service.go -package user - -type Repository interface { // Port defined in business layer - FindByID(ctx context.Context, id string) (*User, error) -} - -type Service struct { - repo Repository // Depends on interface, not concrete type -} - -func NewService(repo Repository) *Service { - return &Service{repo: repo} -} -``` - -Incorrect: - -```go -// Business logic with external dependency -package user - -import "github.com/redis/go-redis/v9" // Direct external dependency - -type Service struct { - redis *redis.Client // Concrete type, not interface -} -``` - ---- - -## Project Structure - -Scope: Go projects - -Rule: Follow consistent directory structure. Avoid -`internal/`, `api/`, `configs/`, `test/` directories. - -**Standard Structure:** - -``` -apps/go-project/ -├── cmd/ # Application entrypoints -│ ├── serve/ # HTTP server and workers -│ └── cli/ # Command-line interface -├── docs/ # Project-specific documentation -├── ops/ # Dockerfiles, Grafana dashboards, k8s configs -└── pkg/ # Core application logic - ├── eser-go/ # Shared framework (processfx, logfx, etc.) - └── api/ - ├── adapters/ # External integrations - └── business/ # Ports and business logic -``` - ---- - -## File Naming - -Scope: Go files - -Rule: Use snake_case for all Go files. - -Correct: - -``` -user_service.go -payment_handler.go -auth_middleware.go -``` - -Incorrect: - -``` -UserService.go -paymentHandler.go -auth-middleware.go -``` - ---- - -## Error Handling - -Scope: All Go code - -Rule: Always check and handle errors explicitly. Wrap errors -for traceability. - -Correct: - -```go -result, err := operation() -if err != nil { - return fmt.Errorf("%w: %w", ErrOperationFailed, err) -} - -// Define sentinel errors in business layer -var ErrUserNotFound = errors.New("user not found") -var ErrInvalidInput = errors.New("invalid input") -``` - -Incorrect: - -```go -result, _ := operation() // Ignored error - -if err != nil { - return err // No context added -} - -if err != nil { - return errors.New("failed") // Original error lost -} -``` - ---- - -## Logging Conventions - -Scope: All logging statements - -Rule: Use appropriate log levels by layer. Never -log sensitive information. - -**Layer Guidelines:** - -- Repository layer: Only `warn`, `debug`, `trace` levels -- Service layer: Log successful operations at `info` level -- Use structured logging via `pkg/eser-go/logfx` -- Include trace IDs and context for correlation - -Correct: - -```go -// Service layer - info for success -log.Info("user created", "userId", user.ID, "traceId", traceID) - -// Repository layer - debug only -log.Debug("fetching user from database", "userId", id) -``` - -Incorrect: - -```go -log.Info("executing query") // Wrong level for repository -log.Error("password is: " + password) // Never log secrets -fmt.Println("user created") // Unstructured logging -``` - ---- - -## Testing Requirements - -Scope: All Go tests - -Rule: Use table-driven tests with parallel execution. Run -with race detection. - -Correct: - -```go -func TestCalculate(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - input int - expected int - }{ - {"zero", 0, 0}, - {"positive", 5, 25}, - {"negative", -3, 9}, - } - - for _, tc := range cases { - tc := tc // Capture range variable - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - result := Calculate(tc.input) - if result != tc.expected { - t.Errorf("got %d, want %d", result, tc.expected) - } - }) - } -} -``` - -Run tests: - -```bash -go test -race ./... -``` - -Incorrect: - -```go -func TestCalculate(t *testing.T) { - // No parallel execution - // No table-driven pattern - if Calculate(5) != 25 { - t.Error("failed") - } -} -``` - ---- - -## OpenTelemetry - -Scope: Observability - -Rule: Use OpenTelemetry for distributed tracing, metrics, -and structured logging. - -**Requirements:** - -- Start and propagate spans across service boundaries -- Always attach `context.Context` to spans, logs, metrics -- Record important attributes (request params, user ID, errors) -- Use `otel.Tracer` for tracing spans -- Use `otel.Meter` for collecting metrics - -Correct: - -```go -func (s *Service) CreateUser(ctx context.Context, req CreateUserRequest) (*User, error) { - ctx, span := otel.Tracer("user-service").Start(ctx, "CreateUser") - defer span.End() - - span.SetAttributes( - attribute.String("user.email", req.Email), - attribute.String("trace.id", trace.SpanFromContext(ctx).SpanContext().TraceID().String()), - ) - - // ... implementation -} -``` - ---- - -## Security & Resilience - -Scope: External interactions - -Rule: Implement defensive patterns for external -calls. - -**Requirements:** - -- Rigorous validation and sanitization for all external inputs -- Secure defaults for JWT, cookies, configuration -- Implement retries, exponential backoff, timeouts on external calls -- Use circuit breakers and rate limiting -- Consider distributed rate-limiting with Redis - -Correct: - -```go -client := &http.Client{ - Timeout: 10 * time.Second, -} - -retryPolicy := retry.NewExponential(100 * time.Millisecond) -retryPolicy = retry.WithMaxRetries(3, retryPolicy) -``` - ---- - -## Configuration Hierarchy - -Scope: Configuration loading - -Rule: Load configuration in priority order. - -**Priority (highest to lowest):** - -1. Environment variables -2. `config.json` file -3. Default values - -Correct: - -```go -func LoadConfig() Config { - cfg := defaultConfig() - - if file, err := os.Open("config.json"); err == nil { - json.NewDecoder(file).Decode(&cfg) - } - - if val := os.Getenv("API_PORT"); val != "" { - cfg.Port = val // Env vars override file - } - - return cfg -} -``` - ---- - -## Code Style - -Scope: All Go code - -Rule: Write short, focused functions. Prefer composition over -inheritance. - -**Guidelines:** - -- Single responsibility per function -- Use small, purpose-specific interfaces -- Public functions interact with interfaces, not concrete types -- No redundant wrappers - call methods directly -- Prefer standard library over third-party dependencies - -Correct: - -```go -type Reader interface { - Read(p []byte) (n int, err error) -} - -func ProcessData(r Reader) error { - // Works with any Reader implementation -} -``` - -Incorrect: - -```go -type EverythingInterface interface { - Read() error - Write() error - Delete() error - Update() error - // Too many methods -} -``` - ---- - -## JSON Encoding (jsonv2) - -Scope: All Go JSON operations - -Rule: Prefer `encoding/json/v2` (jsonv2) over `encoding/json` -wherever possible for JSON marshaling/unmarshaling. - -**Guidelines:** - -- Import `encoding/json/v2` instead of `encoding/json` -- Use jsonv2 struct tags and features (e.g., `omitzero`, `inline`, format options) -- Leverage jsonv2's stricter and more correct default behavior -- Use `json.Marshal` / `json.Unmarshal` from the v2 package - -Correct: - -```go -import "encoding/json/v2" - -type User struct { - Name string `json:"name"` - Email string `json:"email,omitzero"` -} - -data, err := json.Marshal(user) -``` - -Incorrect: - -```go -import "encoding/json" // Use v2 instead - -type User struct { - Name string `json:"name"` - Email string `json:"email,omitempty"` -} -``` - ---- - -## Code Quality Tools - -Scope: Go projects - -Rule: Use standard linting and quality tools. - -**Required Tools:** - -- `golangci-lint` - Comprehensive linting (config in `.golangci.yaml`) -- Pre-commit hooks for automated checks -- Conventional commits for commit message validation -- Race detection in tests (`-race` flag) - -```bash -golangci-lint run -go test -race ./... -``` diff --git a/.claude/skills/javascript-practices/SKILL.md b/.claude/skills/javascript-practices/SKILL.md deleted file mode 100644 index c15405a13..000000000 --- a/.claude/skills/javascript-practices/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: javascript-practices -description: "JavaScript/TypeScript conventions: ES modules, Deno runtime, React patterns, async/await, and type handling. Use when writing .ts, .tsx, or .js files, configuring imports, or building React components." ---- - -# JavaScript/TypeScript Practices - -Conventions for JS/TS syntax, modules, types, and runtime behavior. - -## Quick Start - -```typescript -import * as path from "@std/path"; // namespace import -import { utils } from "./utils.ts"; // explicit extension - -export function buildConfig() {} // direct named export -const port = config.port ?? 8000; // nullish coalescing -``` - -## Key Principles - -**Modules:** Direct named exports, namespace imports, explicit `.ts` extensions, -avoid `export *` re-exports (use direct imports from specific modules) - -**Syntax:** `const` over `let`, always semicolons, `===` strict equality, `??` -over `||` - -**Types:** `Number()` over `+`, `instanceof` over `typeof`, prefer `null` over -`undefined` - -**Runtime:** `import.meta.dirname`, `globalThis` over `window`, optional -`projectRoot` params - -**Async:** Use `return await` consistently for better stack traces and correct -error handling - -**Deno:** Scripts in `package.json` (not deno.json tasks), use package aliases -directly, no wrapper scripts for built-in commands - -**Avoid:** `eval`, prototype mutation, truthy/falsy checks on non-booleans - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/javascript-practices/references/rules.md b/.claude/skills/javascript-practices/references/rules.md deleted file mode 100644 index 231ba2b54..000000000 --- a/.claude/skills/javascript-practices/references/rules.md +++ /dev/null @@ -1,1178 +0,0 @@ -# JavaScript/TypeScript Practices - Detailed Rules - -## Modules - -### Exports - -Scope: JS/TS - -Rule: Always use direct named exports with export keyword. Avoid -default exports. - -Correct: - -```typescript -export function buildCommand() {} -export const CONFIG_PATH = "./config"; -export class UserService {} -export type BuildOptions = {}; -``` - -Incorrect: - -```typescript -function buildCommand() {} -const CONFIG_PATH = "./config"; -export { buildCommand, CONFIG_PATH }; // indirect export - -export default buildCommand; // default export -``` - -### Imports - -Scope: JS/TS - -Rule: **ALWAYS use namespace imports (`import * as`)** to prevent naming -collisions and make the source of every symbol explicit. This is a strict -convention — violations will be caught in review. - -Correct: - -```typescript -import * as path from "@std/path"; -import * as fs from "@std/fs"; -import * as shellFormatting from "@eser/shell/formatting"; - -const filePath = path.join(dir, "config.ts"); -const exists = await fs.exists(filePath); -const output = shellFormatting.createOutput(); -``` - -Incorrect: - -```typescript -import { join, resolve } from "@std/path"; // ❌ named import -import { copy, exists } from "@std/fs"; // ❌ named import -import { createOutput } from "@eser/shell/formatting"; // ❌ named import - -const filePath = join(dir, "config.ts"); // potential collision with other 'join' -``` - -**Consolidated package imports:** -When a package has namespace sub-module exports in its `mod.ts` (like `@eser/shell`, -`@eser/primitives`, `@eser/functions`, `@eser/standards`), import the top-level -package and access sub-modules via dot notation: - -```typescript -import * as shell from "@eser/shell"; -import * as primitives from "@eser/primitives"; -import * as functions from "@eser/functions"; -import * as standards from "@eser/standards"; - -const output = shell.formatting.createOutput(); -const result = primitives.results.ok(value); -const tag: functions.triggers.CliEvent = event; -const version = standards.runtime.current.process.args; -``` - -Do NOT import sub-modules individually when the root exports them: - -```typescript -// ❌ Separate sub-module imports -import * as shellFormatting from "@eser/shell/formatting"; -import * as shellExec from "@eser/shell/exec"; -import type * as shellArgs from "@eser/shell/args"; -``` - -**Naming convention for namespace aliases:** -- Standard library: short name matching the module (`path`, `fs`, `semver`) -- Workspace packages: use the package's short name (`shell`, `primitives`, - `functions`, `standards`, `fp`) - -Exception: Single function imports from test assertion modules only: - -```typescript -import { assertEquals } from "@std/assert"; -``` - -### Import Paths - -Scope: JS/TS - -Rule: Use explicit file extensions and paths. Avoid sloppy imports. - -Correct: - -```typescript -import { add } from "./math/add.ts"; -import { ConsoleLogger } from "./loggers/index.ts"; -import { buildConfig } from "../config/build.ts"; -``` - -Incorrect: - -```typescript -import { add } from "./math/add"; // missing extension -import { ConsoleLogger } from "./loggers"; // ambiguous directory import -``` - -### Avoid Re-exports - -Scope: JS/TS barrel files (mod.ts, index.ts) - -Rule: Avoid `export *` barrel re-exports. They cause name collisions (TS2308), -break tree-shaking, and obscure the public API surface. Consumers should import -directly from the specific module they need. If a barrel file is necessary for a -public API, use explicit named re-exports for a curated subset only. - -Correct: - -```typescript -// Consumer imports directly from the specific module -import { getCommits } from "@eser/codebase/git"; -import { discoverPackages } from "@eser/codebase/workspace-discovery"; -``` - -```typescript -// If a barrel is needed, use explicit named re-exports (curated API) -export { getCommits, getTaggedVersions } from "./git.ts"; -export { discoverPackages } from "./workspace-discovery.ts"; -``` - -Incorrect: - -```typescript -// Wildcard re-exports — collisions when multiple modules export same name -export * from "./git.ts"; -export * from "./check-circular-deps.ts"; -export * from "./check-docs.ts"; -export * from "./versions.ts"; -// TS2308: each module exports `main`, causing name collision -``` - -```typescript -// Re-exporting everything when consumers only need specific modules -export * from "./utils.ts"; -export * from "./helpers.ts"; -export * from "./constants.ts"; -``` - ---- - -## Runtime - -### Module Paths - -Scope: JS/TS ES Modules (Node 20.11+, Deno, Bun) - -Rule: Use import.meta.dirname -over dirname(fromFileUrl(import.meta.url)) - -Correct: - -```typescript -const root = projectRoot ?? import.meta.dirname; -``` - -Incorrect: - -```typescript -import { dirname } from "@std/path"; -import { fromFileUrl } from "@std/url"; -const root = dirname(fromFileUrl(import.meta.url)); -``` - -### Path Context Distinction - -Scope: JS/TS (Deno, Node, Bun) - -Rule: Use import.meta for module paths, cwd() for -user context - -Module-relative (package internals): - -```typescript -const templateDir = new URL(`../templates/${name}/`, import.meta.url).pathname; -const configPath = join(import.meta.dirname, "config", "defaults.json"); -``` - -User context (CLI working directory): - -```typescript -export function buildCommand(options: { projectRoot?: string }) { - const userDir = options.projectRoot ?? Deno.cwd(); - const userConfig = join(userDir, "config.ts"); -} -``` - -### Optional Path Parameters - -Scope: JS/TS (CLI tools, build systems, test utilities) - -Rule: Add optional -projectRoot?: string parameter. Enables testing without global state changes. - -Correct: - -```typescript -export async function analyzeComponents( - srcDir: string, - projectRoot?: string, -): Promise { - const root = projectRoot ?? import.meta.dirname; -} -``` - -Incorrect: - -```typescript -export async function analyzeComponents(srcDir: string): Promise { - const root = Deno.cwd(); // hardcoded, untestable -} -``` - -### Global Object - -Scope: JS/TS (Browser, Deno, Node, Bun) - -Rule: Use globalThis instead of window. -Works across all runtimes. - -Correct: - -```typescript -const isDefined = typeof globalThis.localStorage !== "undefined"; -globalThis.myGlobal = { value: 1 }; -``` - -Incorrect: - -```typescript -const isDefined = typeof window.localStorage !== "undefined"; // fails in Node/Deno -``` - ---- - -## Syntax - -### Variable Declaration - -Scope: JS/TS - -Rule: Prefer const over let. Use const by default, only use let -when reassignment required. - -Correct: - -```typescript -const userName = "John"; -const config = { port: 8000 }; -let counter = 0; -counter++; // reassignment needed -``` - -Incorrect: - -```typescript -let userName = "John"; // never reassigned -``` - -### Semicolons - -Scope: JS/TS - -Rule: Add semicolons always. Reduces bugs from automatic semicolon -insertion. - -Correct: - -```typescript -const user = getUser(); -const name = user.name; -return name; -``` - -### Equality Checking - -Scope: JS/TS - -Rule: Use strict equality (===). Avoids implicit type coercion. - -Correct: - -```typescript -if (value === 0) {} -if (user === null) {} -``` - -Incorrect: - -```typescript -if (value == 0) {} // matches 0, "0", false, "" -``` - -### Operators - -Scope: JS/TS - -Rule: Use ?? for defaults. Coalesces only on null/undefined, not -falsy values. - -Correct: - -```typescript -const value = userInput ?? "default"; -const port = config.port ?? 8000; -``` - -Incorrect: - -```typescript -const value = userInput || "default"; // fails if userInput is "" -const port = config.port || 8000; // fails if port is 0 -``` - -### String Methods - -Scope: JS/TS - -Rule: Use slice() instead of substring() or substr(). substr() -deprecated. - -Correct: - -```typescript -const text = "Hello World"; -const result = text.slice(0, 5); // "Hello" -const last = text.slice(-5); // "World" -``` - -### String Formatting - -Scope: JS/TS - -Rule: Prefer template strings over concatenation. - -Correct: - -```typescript -const greeting = `Hello, ${user.name}!`; -const url = `/api/users/${userId}/posts/${postId}`; -``` - -Incorrect: - -```typescript -const greeting = "Hello, " + user.name + "!"; -``` - -### Delete Operator - -Scope: JS/TS - -Rule: Use delete for object properties only. - -Correct: - -```typescript -const obj = { name: "John", age: 30 }; -delete obj.age; // removes property -``` - -For arrays, use splice() or filter(): - -```typescript -const arr = [1, 2, 3]; -arr.splice(1, 1); // removes element -``` - -### Function Arguments - -Scope: JS/TS - -Rule: Use rest operator instead of arguments object. - -Correct: - -```typescript -function sum(...numbers: number[]): number { - return numbers.reduce((a, b) => a + b, 0); -} -``` - -### Array Iteration - -Scope: JS/TS - -Rule: Prefer for..of for array values. - -Correct: - -```typescript -for (const item of items) { - process(item); -} - -for (const [index, item] of items.entries()) { - console.log(index, item); -} -``` - -Exception: Use for loop when break/continue or index manipulation needed. - -### Dangerous Features - -Scope: JS/TS - -Rule: Avoid eval, prototype manipulation, Object.defineProperty. - -Incorrect: - -```typescript -eval("const x = 1"); // security risk -Array.prototype.myMethod = function () {}; // pollutes global -``` - ---- - -## Types - -### String to Number Conversion - -Scope: JS/TS - -Rule: Use Number() for conversions. Makes intent clearer than -unary + operator. - -Correct: - -```typescript -const value = Number(userInput); -const port = Number(process.env.PORT ?? "8000"); -``` - -Incorrect: - -```typescript -const value = +userInput; // unclear intent -``` - -### Type Checking - -Scope: JS/TS - -Rule: Avoid typeof operator. Use instanceof or .constructor. - -Correct: - -```typescript -const value = [1, 2, 3]; -if (value instanceof Array) {} -if (value.constructor === Array) {} -``` - -Incorrect: - -```typescript -if (typeof value === "object") {} // true for arrays, objects, null -``` - -Exception: Checking for undefined: - -```typescript -if (typeof someVar === "undefined") {} // acceptable -``` - -### Null vs Undefined - -Scope: JS/TS - -Rule: Prefer null over undefined. Makes intent explicit. - -Correct: - -```typescript -let user: User | null = null; - -function findUser(id: string): User | null { - return users.get(id) ?? null; -} -``` - -### Truthy/Falsy Checks - -Scope: JS/TS - -Rule: Avoid truthy/falsy checks unless boolean type. Use full -conditions. - -Correct: - -```typescript -if (array.length === 0) {} -if (user !== null) {} -if (value !== 0) {} -if (isActive) {} // boolean type -``` - -Incorrect: - -```typescript -if (!array.length) {} // false for length 0 -if (user) {} // false for null, undefined, 0, "" -``` - -### Iteration Over Optional Values - -Scope: JS/TS - -Rule: Use explicit undefined checks before iterating. Avoid nullish -coalescing that creates empty objects just to iterate over nothing. - -Correct: - -```typescript -const props: Record | undefined = getProps(); - -if (props !== undefined) { - for (const [key, value] of Object.entries(props)) { - process(key, value); - } -} - -// Also acceptable for arrays -if (items !== undefined) { - for (const item of items) { - handle(item); - } -} -``` - -Incorrect: - -```typescript -// Creates empty object just to iterate nothing -for (const [key, value] of Object.entries(props ?? {})) { - process(key, value); -} - -// Same issue with arrays -for (const item of items ?? []) { - handle(item); -} -``` - -Rationale: - -- More efficient: avoids creating temporary empty objects/arrays -- More explicit: makes the conditional nature of iteration clear -- More readable: intent is obvious at a glance -- Better for debugging: easier to add logging or breakpoints - ---- - -## Async - -### Consistent Async Returns - -Scope: JS/TS - -Rule: Use `return await` consistently in async functions. Prefer explicit -awaiting over implicit promise returns. - -Correct: - -```typescript -async function fetchUser(id: string): Promise { - return await userRepository.findById(id); -} - -async function processData(): Promise { - try { - return await riskyOperation(); - } catch (error) { - return await fallbackOperation(); - } -} -``` - -Incorrect: - -```typescript -async function fetchUser(id: string): Promise { - return userRepository.findById(id); // implicit return, worse stack trace -} - -async function processData(): Promise { - try { - return riskyOperation(); // BUG: rejection bypasses catch block! - } catch (error) { - return fallbackOperation(); - } -} -``` - -Rationale: -- Better stack traces for debugging -- Correct error handling in try-catch (without await, rejections bypass catch) -- No performance penalty (ECMA-262 updated) -- Deno's require-await lint rule compatibility -- ESLint's no-return-await is now deprecated - ---- - -## String Prefix Extraction - -### Extract Value After Known Prefix - -Scope: JS/TS (HTTP headers, URIs, file paths, protocol strings) - -Rule: When extracting a value after a known prefix, use `startsWith()` to validate -and `slice()` to extract. Never use `replace(prefix, "")` which can modify content -in the middle or at unexpected positions. - -Correct: - -```typescript -// Generic helper for any prefix extraction -function extractAfterPrefix( - value: string | null | undefined, - prefix: string, -): string | null { - if (value === null || value === undefined) { - return null; - } - - if (!value.startsWith(prefix)) { - return null; - } - - const extracted = value.slice(prefix.length); - return extracted.length > 0 ? extracted : null; -} - -// Usage examples -const BEARER_PREFIX = "Bearer "; -const token = extractAfterPrefix(authHeader, BEARER_PREFIX); - -const DATA_URI_PREFIX = "data:image/png;base64,"; -const base64Data = extractAfterPrefix(uri, DATA_URI_PREFIX); - -const FILE_PROTOCOL = "file://"; -const filePath = extractAfterPrefix(url, FILE_PROTOCOL); -``` - -Incorrect: - -```typescript -// ❌ Using replace - doesn't validate prefix position -const token = authHeader?.replace("Bearer ", "") ?? null; -// "xBearer token" → "xtoken" (wrong!) - -// ❌ Using optional chaining with replace -const path = uri?.replace("file://", "") ?? null; -// "s3://file://bucket" → "s3://bucket" (wrong!) - -// ❌ Hardcoded slice without prefix validation -const token = authHeader?.slice(7) ?? null; -// Doesn't check if "Bearer " actually exists -``` - -Rationale: -- `replace()` replaces first occurrence anywhere in the string, not just at start -- `startsWith()` + `slice()` is explicit about the expected format -- Validates the prefix is present before extracting -- Empty values after prefix are explicitly handled - ---- - -## Deno Project Scripting - -### Scripts Location - -Scope: Deno projects with package.json - -Rule: Keep scripts in `package.json` when project has one. Don't duplicate in -`deno.json` tasks. - -Correct: - -```json -// package.json -{ - "scripts": { - "dev": "vite dev --port 3000", - "build": "vite build", - "start": "deno run --allow-all ./.output/server/index.mjs" - } -} - -// deno.json - minimal, no tasks -{ - "name": "@scope/project", - "imports": { "@/": "./src/" } -} -``` - -Incorrect: - -```json -// deno.json - duplicating package.json scripts -{ - "tasks": { - "dev": "deno run --allow-all npm:vite dev --port 3000", - "build": "deno run --allow-all npm:vite build" - } -} -``` - -### Avoid Redundant Scripts - -Scope: Deno projects - -Rule: Don't add wrapper scripts for built-in Deno commands. Use `deno lint`, -`deno fmt`, `deno install --allow-scripts` directly. - -Correct: - -```bash -deno lint -deno fmt -deno install --allow-scripts -``` - -Incorrect: - -```json -{ - "scripts": { - "lint": "deno lint", - "fmt": "deno fmt", - "install:deps": "deno install --allow-scripts" - } -} -``` - -### Use Package Aliases - -Scope: Deno projects with npm dependencies - -Rule: When a package is aliased in dependencies, use the alias directly instead -of `deno run --allow-all npm:package-name`. - -Correct: - -```json -// package.json -{ - "devDependencies": { - "vite": "npm:rolldown-vite@^7.3.1" - }, - "scripts": { - "dev": "vite dev --port 3000", - "build": "vite build" - } -} -``` - -Incorrect: - -```json -{ - "scripts": { - "dev": "deno run --allow-all npm:rolldown-vite dev --port 3000", - "build": "deno run --allow-all npm:rolldown-vite build" - } -} -``` - -### Workspace Configuration - -Scope: Deno workspaces - -Rule: Only root `deno.json` can have workspace-level keys like `nodeModulesDir`, -`compilerOptions`, `lint`, `fmt`. Member projects have minimal config. - -Correct: - -```json -// Root deno.json -{ - "nodeModulesDir": "auto", - "workspace": ["./apps/webclient"], - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "react" - }, - "lint": { "rules": { "tags": ["recommended"] } }, - "fmt": { "lineWidth": 120 } -} - -// apps/webclient/deno.json - minimal -{ - "name": "@aya/webclient", - "version": "1.0.0", - "imports": { "@/": "./src/" } -} -``` - -Incorrect: - -```json -// apps/webclient/deno.json - workspace member with root-only keys -{ - "name": "@aya/webclient", - "nodeModulesDir": "auto", - "compilerOptions": { "jsx": "react-jsx" } -} -``` - ---- - -## React Component Patterns - -### CSS Modules with @apply - -Scope: React components with Tailwind CSS - -Rule: Use CSS Modules with Tailwind `@apply` directive as primary styling -approach. Each component should have a co-located `*.module.css` file with -semantic class names. Direct Tailwind classes should be used sparingly. - -Correct: - -```css -/* product-card.module.css */ -.product-card { - @apply border rounded-lg p-4 shadow-md flex flex-col gap-2; - - & .title { - @apply text-xl font-bold mb-2; - } - - & .description { - @apply text-sm text-gray-600; - } -} -``` - -```typescript -// ProductCard.tsx -import styles from "./product-card.module.css"; - -type ProductCardProps = { - product: { name: string; description: string }; -}; - -function ProductCard(props: ProductCardProps) { - return ( -
-

{props.product.name}

-

{props.product.description}

-
- ); -} -``` - -Incorrect: - -```typescript -// ❌ Inline Tailwind classes everywhere -function ProductCard(props: ProductCardProps) { - return ( -
-

{props.product.name}

-

{props.product.description}

-
- ); -} - -// ❌ Inline styles -function ProductCard(props: ProductCardProps) { - return ( -
- {props.product.name} -
- ); -} -``` - -**When Direct Tailwind is Acceptable:** - -- Very simple, non-reusable micro-adjustments -- Global layout containers in top-level pages -- Use `cn` utility when combining: `className={cn(styles.button, "mt-2")}` - ---- - -### Single Props Object Pattern - -Scope: React components - -Rule: Accept single props object instead of destructuring in function signature. -Access via `props.propertyName` for better readability and refactoring. - -Correct: - -```typescript -type UserProfileProps = { - userId: string; - showActions: boolean; -}; - -function UserProfile(props: UserProfileProps) { - return ( -
-

User: {props.userId}

- {props.showActions && } -
- ); -} -``` - -Incorrect: - -```typescript -// ❌ Destructured props in signature -function UserProfile({ userId, showActions }: UserProfileProps) { - return ( -
-

User: {userId}

- {showActions && } -
- ); -} -``` - ---- - -### Named Exports for Components - -Scope: React components - -Rule: Prefer named exports for React components. Default exports only for -framework-required files (page.tsx, layout.tsx). - -Correct: - -```typescript -// UserProfile.tsx -export function UserProfile(props: UserProfileProps) { - return
{props.name}
; -} - -// page.tsx (framework required) -export default function HomePage() { - return
...
; -} -``` - -Incorrect: - -```typescript -// ❌ Default export for regular component -function UserProfile(props: UserProfileProps) { - return
{props.name}
; -} -export default UserProfile; - -// ❌ Re-export as default -export { UserProfile as default }; -``` - ---- - -### React v19 Compiler Compatibility - -Scope: React v19+ projects - -Rule: Write compiler-friendly code. Minimize manual memoization. The React -compiler handles optimization automatically. - -Correct: - -```typescript -// ✅ Let compiler optimize -function ExpensiveList(props: { items: Item[] }) { - const sorted = props.items.toSorted((a, b) => a.name.localeCompare(b.name)); - return ( -
    - {sorted.map((item) => ( -
  • {item.name}
  • - ))} -
- ); -} -``` - -Incorrect: - -```typescript -// ❌ Manual memoization without profiling evidence -function ExpensiveList(props: { items: Item[] }) { - const sorted = useMemo( - () => props.items.toSorted((a, b) => a.name.localeCompare(b.name)), - [props.items], - ); - return ( -
    - {sorted.map((item) => ( -
  • {item.name}
  • - ))} -
- ); -} -``` - -**Guidelines:** - -- Favor idiomatic React patterns -- Only add `useMemo`/`useCallback` after profiling shows benefit -- React Strict Mode enabled for better development experience - ---- - -## Testability - -### Pure Function Extraction - -Scope: JS/TS (Deno-tested frontend utilities) - -Rule: Pure functions should be kept free of environment/framework dependencies -(like `import.meta.env`, Vite-specific APIs, React context) to enable direct -testing with Deno's test runner. When a utility file needs both pure logic and -environment config, extract the pure logic into a separate file. - -Correct: - -```typescript -// locale-utils.ts — pure functions, no import.meta.env -export const SUPPORTED_LOCALES = ["en", "tr", "fr"] as const; -export function isValidLocale(locale: string): boolean { - return SUPPORTED_LOCALES.includes(locale as any); -} - -// config.ts — re-exports pure functions + adds env-dependent config -export { SUPPORTED_LOCALES, isValidLocale } from "@/lib/locale-utils.ts"; -export const siteConfig = { - host: import.meta.env.VITE_SITE_URL, -}; -``` - -```typescript -// seo-utils.ts — pure functions with explicit parameters -export function generateMetaTags( - meta: SeoMeta, - defaults: { host: string; name: string; defaultImage: string }, -): MetaTag[] { /* ... */ } - -// seo.ts — thin wrapper injecting siteConfig -import { generateMetaTags as generateMetaTagsPure } from "@/lib/seo-utils.ts"; -import { siteConfig } from "@/config.ts"; -const DEFAULTS = { host: siteConfig.host, name: siteConfig.name, defaultImage: `${siteConfig.host}/og-image.png` }; -export function generateMetaTags(meta: SeoMeta) { - return generateMetaTagsPure(meta, DEFAULTS); -} -``` - -Incorrect: - -```typescript -// ❌ Pure logic mixed with env dependency — untestable with Deno -import { siteConfig } from "@/config.ts"; // transitively imports import.meta.env -export function buildUrl(locale: string, ...segments: string[]) { - return `${siteConfig.host}/${locale}/${segments.join("/")}`; -} -``` - -Naming convention: `foo-utils.ts` for pure functions, `foo.ts` for the -config-aware wrapper that re-exports and/or delegates to the utils file. - -### Snapshot Testing Pattern - -Scope: Deno test files (`*.test.ts`) - -Rule: Use `assertSnapshot` from `@std/testing/snapshot` for anti-regression -tests. Test files need `/// ` since the project -tsconfig targets Vite/React. Use `--no-check` flag when Deno's type checker -conflicts with Vite-compatible types (e.g., Zod v4). - -Correct: - -```typescript -/// -import { assertSnapshot } from "@std/testing/snapshot"; -import { myFunction } from "./my-utils.ts"; - -Deno.test("myFunction - descriptive case name", async (t) => { - await assertSnapshot(t, myFunction("input")); -}); -``` - -Test commands (in package.json): - -```json -{ - "test": "deno test --no-check --allow-read --allow-write=. src/", - "test:update": "deno test --no-check --allow-read --allow-write=. -- --update src/", - "test:ci": "deno test --no-check --allow-read src/" -} -``` - ---- - -## Translation System - -### Translation Key Format - -Scope: Internationalized React applications - -Rule: Use English text as translation keys. Add translations to ALL language -files. Use fallbacks for missing translations. - -Correct: - -```typescript -// ✅ English text as key -t("Auth", "Login with GitHub") -t("Home", "AYA the Open Source Network") - -// ✅ With fallback -t("Section", "Key") || "Fallback text" -``` - -Incorrect: - -```typescript -// ❌ Snake case keys -t("Auth.login_with_github") -t("Home.main_title") -``` - -### Server vs Client Components - -Scope: Next.js/React Server Components - -Rule: Use `getTranslations` for Server Components, `useTranslations` hook for -Client Components. - -Correct: - -```typescript -// ✅ React Server Component -import { getTranslations } from "@/modules/i18n/get-translations.tsx"; - -async function MyServerComponent() { - const { t, locale } = await getTranslations(); - return

{t("Home", "Welcome")}

; -} - -// ✅ Client Component -"use client"; -import { useTranslations } from "@/modules/i18n/use-translations.tsx"; - -function MyClientComponent() { - const { t, locale } = useTranslations(); - return ; -} -``` diff --git a/.claude/skills/release-management/SKILL.md b/.claude/skills/release-management/SKILL.md deleted file mode 100644 index 715dc7700..000000000 --- a/.claude/skills/release-management/SKILL.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: release-management -description: Release checklist, version strategy, and publishing workflow for JSR/npm. Use when bumping versions, creating releases, or managing the release pipeline. ---- - -# Release Management - -Unified release process for 29+ synchronized packages. - -## Quick Start - -1. `deno task validate` — verify clean state -2. `deno task cli codebase versions ` — bump all packages -3. Update `CHANGELOG.md` — move Unreleased to new version section -4. PR to main → merge triggers JSR + npm publish - -## Key Principles - -- All packages share one version — no independent releases -- JSR primary (OIDC auth), npm secondary (only `@eser/cli` as `eser`) -- **patch:** bug fixes, docs, deps. **minor:** new features. **major:** breaking changes -- Always update CHANGELOG before releasing -- Tag format: `vx.y.z` (triggers release-notes-sync workflow) - -## Anti-Patterns - -**"I'll just edit one package's version"** -No. Use the version-bump script. Manual edits break synchronization. - -**"Skip the changelog, it's a small fix"** -No. Every release needs a changelog entry for traceability. - -## References - -See [rules.md](references/rules.md) for full release checklist and hotfix process. diff --git a/.claude/skills/release-management/references/rules.md b/.claude/skills/release-management/references/rules.md deleted file mode 100644 index 635bf33fe..000000000 --- a/.claude/skills/release-management/references/rules.md +++ /dev/null @@ -1,145 +0,0 @@ -# Release Management - Detailed Rules - -## Pre-Release Checklist - -Scope: Every release - -Rule: Complete all steps in order before merging to main. - -1. **Verify clean state:** - ```bash - git status # Must be clean - deno task validate # Must pass - ``` - -2. **Bump version:** - ```bash - deno task cli codebase versions - ``` - -3. **Update CHANGELOG.md:** - - Move items from `[Unreleased]` to a new version section - - Add the release date - - Format: `## [x.y.z] - YYYY-MM-DD` - -4. **Commit and push:** - ```bash - git add -A - git commit -m "chore(release): bump version to x.y.z" - git push origin dev - ``` - -5. **Create PR to main:** - - Title: `release: vx.y.z` - - Ensure CI passes (integration job) - ---- - -## Release - -Scope: After PR is approved - -6. **Merge PR to main** — triggers the delivery job: - - Publishes all packages to JSR via `deno publish` (OIDC auth) - - Builds and publishes `@eser/cli` to npm with provenance - -7. **Tag the release:** - ```bash - git tag vx.y.z - git push origin vx.y.z - ``` - -8. **Create GitHub Release:** - - The `release-notes-sync` workflow syncs CHANGELOG content automatically - - Or manually: `gh release create vx.y.z --generate-notes` - ---- - -## Post-Release Verification - -Scope: After every release - -Rule: Verify all publish targets received the new version. - -- Check JSR: packages appear at `jsr.io/@eser/` with correct version -- Check npm: `npm info eser` shows new version -- Check GitHub: release notes are correct and complete -- Add `## [Unreleased]` section to CHANGELOG.md for next cycle - ---- - -## Hotfix Process - -Scope: Urgent fixes on production - -Rule: Hotfixes branch from main, not dev. - -1. Branch from main: `git checkout -b hotfix/ main` -2. Fix the issue -3. Bump patch version: `deno task cli codebase versions patch` -4. Update CHANGELOG with hotfix entry -5. PR directly to main -6. After merge, cherry-pick or merge back to dev - ---- - -## Version Strategy - -Scope: Choosing bump type - -Rule: Match the bump type to the nature of changes. - -| Bump | When | -|------|------| -| **patch** | Bug fixes, documentation, dependency updates, refactoring | -| **minor** | New features, non-breaking API additions, new packages | -| **major** | Breaking API changes, significant architectural shifts | - -All 29+ packages are versioned together — there are no independent package versions. - ---- - -## Publishing Architecture - -### JSR (Primary Registry) - -- Triggered by push to `main` branch in CI delivery job -- Uses OIDC token authentication (no secrets needed) -- All packages with `publish` config in `deno.json` are published -- Command: `deno publish` - -### npm (Secondary Registry) - -- Only `@eser/cli` is published to npm as the `eser` package -- Built via esbuild bundling: `deno task npm-build` -- Published with provenance: `npm publish --provenance --access public` -- Working directory: `pkg/@eser/cli/dist` -- Requires `NODE_AUTH_TOKEN` secret in CI - ---- - -## Release Flow - -Scope: Monorepo releases - -Rule: `eser codebase release patch` bumps version, generates CHANGELOG, commits, and pushes. -CI auto-tags after integration passes. Deployment pipeline publishes on tag push. - ---- - -## Changelog Generation - -Scope: Release automation - -Rule: `changelog-gen.ts` parses conventional commits since the last tag, deduplicates -"take" series (e.g., "feat: x (take II)" collapses with "feat: x"), and groups by -Keep-a-Changelog sections. Idempotent — replaces existing sections for the same version. - ---- - -## Re-Release - -Scope: Failed release recovery - -Rule: `eser codebase rerelease` deletes and recreates the current version tag. `eser codebase release same` -skips version bump for re-releasing after pipeline fixes. diff --git a/.claude/skills/requirement-clarification/SKILL.md b/.claude/skills/requirement-clarification/SKILL.md deleted file mode 100644 index aea5679ce..000000000 --- a/.claude/skills/requirement-clarification/SKILL.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: requirement-clarification -description: Guidelines for asking clarifying questions when requirements are ambiguous. Use when scope is unclear, multiple approaches exist, or critical details are missing. ---- - -# Requirement Clarification - -## Quick Start - -1. Identify ambiguity -> Formulate specific questions -> Ask before implementing -2. Ask about scope, edge cases, and trade-offs when unclear -3. Propose options with trade-offs when multiple approaches exist -4. Never assume - verify critical decisions with user - -## Key Principles - -- **Ask early**: Clarify before writing code, not after -- **Be specific**: Ask targeted questions, not vague ones -- **Offer options**: Present 2-3 approaches with trade-offs -- **Batch questions**: Group related questions together -- **Know when NOT to ask**: Skip for trivial/obvious decisions - -## When to Ask - -- Multiple valid implementations exist -- Business logic implications unclear -- Scope boundaries undefined -- Breaking changes possible -- Security/performance trade-offs involved - -## References - -See [rules.md](references/rules.md) for question templates and anti-patterns. diff --git a/.claude/skills/requirement-clarification/references/rules.md b/.claude/skills/requirement-clarification/references/rules.md deleted file mode 100644 index 5be796d36..000000000 --- a/.claude/skills/requirement-clarification/references/rules.md +++ /dev/null @@ -1,320 +0,0 @@ -# Requirement Clarification - Detailed Rules - -## Clarification Workflow - -Scope: Before implementing any non-trivial feature - -Rule: Follow this decision tree before writing code. - -``` -Is the requirement clear and unambiguous? -├── YES → Proceed with implementation -└── NO → Are there multiple valid approaches? - ├── YES → Present options with trade-offs, ask for preference - └── NO → Is critical information missing? - ├── YES → Ask specific questions to fill gaps - └── NO → State your assumption, proceed unless corrected -``` - ---- - -## When to Ask Questions - -Scope: Identifying situations requiring clarification - -Rule: Ask clarifying questions when any of these conditions apply. - -**Must Ask:** - -- Multiple architecturally different implementations exist -- Business logic has unclear edge cases -- Breaking changes to existing behavior possible -- Security implications unclear -- Performance vs. simplicity trade-off needed -- Scope boundaries undefined ("add a feature" without details) - -**Should Ask:** - -- User's preferred technology/library unclear -- Testing strategy not specified -- Error handling approach ambiguous -- Data validation rules missing - -**Don't Ask:** - -- Trivial implementation details (variable names, formatting) -- Decisions covered by existing codebase patterns -- Standard best practices apply clearly -- User explicitly said "use your judgment" - ---- - -## Question Templates - -Scope: Formulating effective clarifying questions - -Rule: Use structured question formats for clarity. - -### Scope Clarification - -``` -I want to clarify the scope before implementing: -- Should this handle [edge case A]? -- Should this include [related feature B]? -- Is [boundary condition C] in or out of scope? -``` - -### Multiple Approaches - -``` -There are several ways to implement this: - -1. **[Approach A]**: [1-line description] - - Pros: [benefits] - - Cons: [drawbacks] - -2. **[Approach B]**: [1-line description] - - Pros: [benefits] - - Cons: [drawbacks] - -Which approach do you prefer, or should I choose based on [criteria]? -``` - -### Missing Information - -``` -To implement this correctly, I need to know: -1. [Specific question about requirement] -2. [Specific question about behavior] - -Without this, I'll assume [default behavior]. Let me know if that works. -``` - -### Trade-off Decision - -``` -There's a trade-off here: -- Option A: [faster/simpler] but [limitation] -- Option B: [more robust] but [cost] - -Which priority should I optimize for? -``` - ---- - -## Batching Questions - -Scope: Asking multiple questions efficiently - -Rule: Group related questions in a single message. Never ask questions one at a time when they're related. - -Correct: - -``` -Before implementing the user profile feature, I have a few questions: - -1. **Scope**: Should profiles include social links, or just basic info? -2. **Privacy**: Should profiles be public by default or private? -3. **Images**: Should users upload avatars, or use Gravatar/GitHub? - -I can proceed with sensible defaults if you prefer. -``` - -Incorrect: - -``` -Should profiles include social links? -[waits for response] -Should profiles be public by default? -[waits for response] -Should users upload avatars? -``` - ---- - -## Stating Assumptions - -Scope: When proceeding without explicit confirmation - -Rule: State assumptions clearly when proceeding without asking. - -Correct: - -``` -I'll implement this with the following assumptions: -- Using the existing auth middleware pattern -- Returning 404 for missing resources (not empty response) -- Adding validation matching existing forms - -Let me know if any of these should be different. -``` - -Incorrect: - -``` -I'll just implement it how I think is best. -``` - ---- - -## Anti-Patterns to Avoid - -Scope: Common mistakes in requirement clarification - -### Over-Asking - -Rule: Don't ask about trivial decisions or things covered by codebase patterns. - -Incorrect: - -``` -Should I use camelCase or snake_case for variables? -Should I add comments to this function? -What indentation should I use? -``` - -These are covered by existing code style and linting rules. - -### Under-Asking - -Rule: Don't assume critical business logic without verification. - -Incorrect: - -``` -*Implements delete as soft-delete without asking* -*Assumes admin-only access without verification* -*Picks one of several valid database schemas* -``` - -### Vague Questions - -Rule: Ask specific, actionable questions. - -Incorrect: - -``` -How should this work? -What do you want me to do? -Is this okay? -``` - -Correct: - -``` -Should the delete action require confirmation, or delete immediately? -When a user is deactivated, should their posts remain visible or be hidden? -``` - -### Asking After Implementation - -Rule: Clarify before writing code, not after. - -Incorrect: - -``` -*Writes 200 lines of code* -By the way, should I have used approach A instead of B? -``` - -Correct: - -``` -Before I implement this, should I use approach A (faster) or B (more flexible)? -``` - ---- - -## Context-Aware Clarification - -Scope: Adjusting questions based on context - -Rule: Consider the user's expertise and project phase. - -**For New Projects:** - -- Ask more about architecture and patterns -- Clarify technology choices early -- Establish conventions before building - -**For Existing Codebases:** - -- Follow established patterns unless asked otherwise -- Ask only when deviating from patterns -- Reference existing code: "I'll follow the pattern in user-service.ts" - -**For Quick Fixes:** - -- Minimal questions, state assumptions -- Focus on correctness over perfection -- "I'll fix this the simplest way unless you need something specific" - -**For Major Features:** - -- More thorough clarification warranted -- Break into phases if scope is large -- "Let's align on the approach before I implement all of this" - ---- - -## Using AskUserQuestion Tool - -Scope: When to use the structured question tool - -Rule: Use AskUserQuestion for decisions with discrete options. - -Correct Usage: - -``` -AskUserQuestion with: -- header: "Auth method" -- question: "Which authentication approach should I use?" -- options: - - "JWT tokens (Recommended)" - Standard, stateless - - "Session cookies" - Better for SSR - - "OAuth only" - Delegate to providers -``` - -When NOT to Use: - -- Open-ended questions requiring explanation -- Questions needing code examples in the answer -- Trivial yes/no confirmations - ---- - -## Examples - -### Good Clarification - -User: "Add a delete button to the dashboard" - -Response: - -``` -Before adding the delete button, I want to clarify: - -1. **What gets deleted?** The dashboard itself, or items within it? -2. **Confirmation?** Should it show a confirmation dialog? -3. **Permissions?** Should any user delete, or only owners/admins? - -I'll add a delete button for dashboard items with confirmation dialog, -owner-only access, following the existing button patterns. Let me know -if you need something different. -``` - -### Good Assumption Statement - -User: "Fix the login bug" - -Response: - -``` -I found the issue - the session token isn't being refreshed properly. -I'll fix it by: -- Adding token refresh before expiry (5 min buffer) -- Keeping the existing localStorage approach -- Adding error handling for refresh failures - -Proceeding with this fix now. -``` diff --git a/.claude/skills/security-practices/SKILL.md b/.claude/skills/security-practices/SKILL.md deleted file mode 100644 index d73b19419..000000000 --- a/.claude/skills/security-practices/SKILL.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: security-practices -description: "Security practices: secrets in env vars, input validation, SSRF prevention, error sanitization, and production hardening. Use when handling authentication, secrets, user input, or preparing production deployments." ---- - -# Security Practices - -## Quick Start - -1. All secrets in environment variables (never in config files) -2. Validate inputs at system boundaries -3. Sanitize error responses (no stack traces in production) -4. Use HTTPS for all external connections - -## Key Principles - -- Environment variables for all secrets -- SSRF prevention (block internal IP ranges) -- Development vs Production mode separation -- Rigorous input validation - -## Anti-Patterns - -**"I'll use --allow-all for convenience"** -No. Only broad permissions in test files and scripts, never production. - -**"I'll hardcode the API key for now"** -No. All secrets go in environment variables. No exceptions. - -**"I'll skip the pre-commit hook this once"** -No. Never bypass hooks with `--no-verify`. - -## References - -See [rules.md](references/rules.md) for complete conventions. diff --git a/.claude/skills/security-practices/references/rules.md b/.claude/skills/security-practices/references/rules.md deleted file mode 100644 index 7cd0825b6..000000000 --- a/.claude/skills/security-practices/references/rules.md +++ /dev/null @@ -1,341 +0,0 @@ -# Security Practices - Detailed Rules - -## Project-Specific Security Warnings (CRITICAL) - -Scope: eserstack monorepo - -Rule: These are absolute prohibitions for this project. No exceptions. - -- **NEVER** commit `.env` files or secrets to the repository -- **NEVER** use `--allow-all` in production code (only acceptable in `scripts/` and `*.test.ts`) -- **NEVER** bypass pre-commit hooks with `--no-verify` -- **NEVER** push directly to `main` — always use the PR workflow -- **NEVER** modify `deno.lock` manually — let Deno manage it - -Correct: - -```bash -# Development scripts and tests can use broad permissions -deno test --allow-sys --allow-net --allow-env --allow-read --allow-write --allow-run --allow-ffi - -# Production code uses minimal permissions -deno run --allow-net=api.example.com --allow-env=API_KEY src/server.ts -``` - -Incorrect: - -```bash -# Production with --allow-all -deno run --allow-all src/server.ts - -# Skipping hooks -git commit --no-verify -m "quick fix" -``` - ---- - -## Environment Variables for Secrets - -Scope: All secrets and sensitive configuration - -Rule: All secrets MUST be in -environment variables, never in config files or code. - -**Required Environment Variables:** - -- `JWT_SECRET` - Required for authentication (validated on startup) -- `OPENAI_API_KEY`, `AZURE_OPENAI_API_KEY` - Provider API keys -- `DATABASE_URL` - Database connection string -- Other sensitive credentials - -Correct: - -```typescript -const jwtSecret = process.env.JWT_SECRET; -if (!jwtSecret) { - throw new Error("JWT_SECRET environment variable is required"); -} -``` - -```go -jwtSecret := os.Getenv("JWT_SECRET") -if jwtSecret == "" { - log.Fatal("JWT_SECRET environment variable is required") -} -``` - -Incorrect: - -```typescript -const jwtSecret = "my-super-secret-key"; // Hardcoded secret - -// config.json with secrets -{ - "jwtSecret": "my-super-secret-key" // Never commit secrets -} -``` - ---- - -## Production Deployment Checklist - -Scope: Production deployments - -Rule: Ensure all security settings are properly -configured for production. - -**Checklist:** - -- [ ] Set `APP_ENV=production` to enable strict security mode -- [ ] Ensure all API keys are from environment variables, not config files -- [ ] Never commit `config.json` with secrets -- [ ] Use TLS for all external connections -- [ ] Configure rate limiting appropriately (`RATE_LIMIT_REQUESTS`) -- [ ] Set request size limits (`MAX_REQUEST_SIZE_MB`) -- [ ] Disable debugging endpoints and verbose logging - -Correct: - -```bash -APP_ENV=production -JWT_SECRET= -RATE_LIMIT_REQUESTS=100 -MAX_REQUEST_SIZE_MB=10 -EXPOSE_INTERNAL_ERRORS=false -``` - -Incorrect: - -```bash -APP_ENV=development # Wrong mode for production -EXPOSE_INTERNAL_ERRORS=true # Leaks internal details -``` - ---- - -## SSRF Prevention - -Scope: All outbound HTTP requests (webhooks, callbacks, etc.) - -Rule: Validate -URLs before making requests. Block internal IP ranges. - -**Blocked IP Ranges:** - -- `10.x.x.x` - Private network -- `172.16.x.x` - `172.31.x.x` - Private network -- `192.168.x.x` - Private network -- `127.x.x.x` - Localhost -- `169.254.x.x` - Link-local -- `::1`, `fc00::/7` - IPv6 private - -Correct: - -```typescript -function isInternalIP(ip: string): boolean { - const parts = ip.split("."); - if (parts[0] === "10") return true; - if ( - parts[0] === "172" && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31 - ) return true; - if (parts[0] === "192" && parts[1] === "168") return true; - if (parts[0] === "127") return true; - return false; -} - -async function sendWebhook(url: string, payload: unknown) { - const hostname = new URL(url).hostname; - const resolved = await dns.resolve(hostname); - - if (resolved.some(isInternalIP)) { - throw new Error("Webhook URL resolves to internal IP"); - } - - // In production, require HTTPS - if (process.env.APP_ENV === "production" && !url.startsWith("https://")) { - throw new Error("HTTPS required for webhooks in production"); - } - - await fetch(url, { method: "POST", body: JSON.stringify(payload) }); -} -``` - -Incorrect: - -```typescript -async function sendWebhook(url: string, payload: unknown) { - await fetch(url, { method: "POST", body: JSON.stringify(payload) }); // No validation -} -``` - ---- - -## Error Sanitization - -Scope: API error responses - -Rule: Sanitize error responses in production. Never -expose stack traces or internal details. - -Correct: - -```typescript -function handleError(error: Error, env: string): ErrorResponse { - // Log full error internally - logger.error("Request failed", { - message: error.message, - stack: error.stack, - cause: error.cause, - }); - - // Return sanitized response - if (env === "production") { - return { - error: "An error occurred", - code: "INTERNAL_ERROR", - }; - } - - // Development: include details for debugging - return { - error: error.message, - stack: error.stack, - }; -} -``` - -Incorrect: - -```typescript -app.use((error, req, res, next) => { - res.status(500).json({ - error: error.message, - stack: error.stack, // Never in production - query: req.query, // Never expose request details - }); -}); -``` - ---- - -## Development vs Production Modes - -Scope: Environment-specific behavior - -Rule: Clearly separate development and -production configurations. - -**Development Mode (`APP_ENV=development`):** - -- Enables profiling endpoints -- Allows HTTP webhooks -- Detailed error messages -- Verbose logging - -**Production Mode (`APP_ENV=production`):** - -- Strict TLS requirements -- No profiling endpoints -- Sanitized error messages -- Minimal logging - -Correct: - -```typescript -const config = { - isProduction: process.env.APP_ENV === "production", - enableProfiling: process.env.APP_ENV === "development", - requireHttps: process.env.APP_ENV === "production", - exposeInternalErrors: process.env.APP_ENV === "development", -}; -``` - ---- - -## Input Validation - -Scope: All external inputs (API requests, user input, file uploads) Rule: -Validate at system boundaries. Fail fast on invalid input. - -Correct: - -```typescript -import { z } from "zod"; - -const CreateUserSchema = z.object({ - email: z.string().email(), - name: z.string().min(1).max(100), - age: z.number().int().min(0).max(150), -}); - -function createUser(input: unknown) { - const validated = CreateUserSchema.parse(input); // Throws on invalid - return userRepository.create(validated); -} -``` - -```go -func CreateUser(w http.ResponseWriter, r *http.Request) { - var req CreateUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } - - if !isValidEmail(req.Email) { - http.Error(w, "Invalid email", http.StatusBadRequest) - return - } - - // Proceed with validated input -} -``` - -Incorrect: - -```typescript -function createUser(input: any) { - return userRepository.create(input); // No validation -} -``` - ---- - -## Secure Defaults - -Scope: Security configuration - -Rule: Use secure defaults. Require explicit -opt-out for less secure options. - -**Cookie Settings:** - -```typescript -const cookieOptions = { - httpOnly: true, // Prevent XSS access - secure: true, // HTTPS only - sameSite: "strict", // CSRF protection - maxAge: 3600000, // 1 hour -}; -``` - -**JWT Settings:** - -```typescript -const jwtOptions = { - algorithm: "RS256", // Asymmetric for production - expiresIn: "1h", // Short-lived tokens - issuer: "your-app", - audience: "your-app", -}; -``` - -**CORS Settings:** - -```typescript -const corsOptions = { - origin: process.env.ALLOWED_ORIGINS?.split(",") || [], - credentials: true, - methods: ["GET", "POST", "PUT", "DELETE"], -}; -``` diff --git a/.claude/skills/tooling-standards/SKILL.md b/.claude/skills/tooling-standards/SKILL.md deleted file mode 100644 index 178bb7b42..000000000 --- a/.claude/skills/tooling-standards/SKILL.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: tooling-standards -description: "Development tooling: Deno runtime, JSR package registry, deno.json and package.json configuration, and editor settings. Use when setting up projects, choosing packages, or configuring build tools." ---- - -# Tooling Standards - -Standards for Deno runtime, JSR registry, and project configuration. - -## Quick Start - -```bash -deno install # install dependencies -deno fmt # format code -deno lint # lint code -deno test # run tests -``` - -## Key Principles - -**Deno:** Use `deno install/fmt/lint/test` instead of npm commands - -**Config files:** - -- `package.json` for dependencies and scripts -- `tsconfig.json` for TypeScript -- `deno.json` only for fmt/lint settings - -**Registry:** Prefer jsr.io over npm. Use `npm:@jsr/` prefix in package.json - -**Packages:** JSR when available, npm when no JSR alternative exists - -## References - -See [rules.md](references/rules.md) for complete guidelines with examples. diff --git a/.claude/skills/tooling-standards/references/rules.md b/.claude/skills/tooling-standards/references/rules.md deleted file mode 100644 index 1183071fb..000000000 --- a/.claude/skills/tooling-standards/references/rules.md +++ /dev/null @@ -1,446 +0,0 @@ -# Tooling Standards - Detailed Rules - -## Deno Tooling - -### Tooling Preference - -Scope: JS/TS projects using Deno - -Rule: Prefer Deno tooling. Don't use npm -directly. Use deno install for packages. Use built-in formatter, linter, -benchmarks, testing. - -Correct: - -```bash -deno install -deno fmt -deno lint -deno test -deno bench -deno task dev -``` - -Incorrect: - -```bash -npm install -npm run format -npm run lint -npm test -npx vitest -``` - ---- - -### Configuration Files - -Scope: JS/TS projects using Deno - -Rule: Use package.json for dependencies and -scripts. Use tsconfig.json for TypeScript. Use deno.json only for -formatter/linter configuration. - -package.json (dependencies and scripts): - -```json -{ - "dependencies": { - "react": "^19.0.0", - "@std/path": "npm:@jsr/std__path@^1.0.0" - }, - "scripts": { - "dev": "deno run --allow-all main.ts", - "test": "deno test --allow-all" - } -} -``` - -tsconfig.json (TypeScript config): - -```json -{ - "compilerOptions": { - "jsx": "react-jsx", - "strict": true - } -} -``` - -deno.json (formatter/linter only): - -```json -{ - "fmt": { - "lineWidth": 80, - "semiColons": true - }, - "lint": { - "rules": { - "exclude": ["no-explicit-any"] - } - } -} -``` - -Incorrect (don't use deno.json for dependencies): - -```json -{ - "imports": { - "react": "npm:react@^19.0.0" - }, - "tasks": { - "dev": "deno run main.ts" - } -} -``` - ---- - -## Package Registry - -### Registry Preference - -Scope: JS/TS projects - -Rule: Prefer jsr.io as package registry. Configure .npmrc -to support JSR packages in package.json. - -.npmrc: - -``` -@jsr:registry=https://npm.jsr.io -``` - -package.json with JSR packages: - -```json -{ - "dependencies": { - "@std/path": "npm:@jsr/std__path@^1.0.0", - "@std/fs": "npm:@jsr/std__fs@^1.0.0", - "react": "^19.0.0" - } -} -``` - -Install with: - -```bash -deno install -``` - -Incorrect (without npm: prefix or registry config): - -```json -{ - "dependencies": { - "@std/path": "^1.0.0" - } -} -``` - ---- - -### Package Selection Priority - -Scope: JS/TS projects - -Rule: Prefer JSR packages over npm when available. Use npm -packages when no JSR alternative exists. - -Correct (JSR available): - -```json -{ - "dependencies": { - "@std/path": "npm:@jsr/std__path@^1.0.0", - "@std/assert": "npm:@jsr/std__assert@^1.0.0" - } -} -``` - -Correct (no JSR alternative): - -```json -{ - "dependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - } -} -``` - ---- - -## Deno Testing - -Scope: Test configuration and tooling - -Rule: Use Deno's standard testing -libraries and consistent coverage output. - -**Configuration:** - -- Test runner: `@std/testing/bdd` for describe/it syntax -- Assertions: `@std/assert` -- Coverage output: `etc/coverage/` (HTML + LCOV) -- Test tasks in `deno.json`: `test:all`, `test:unit`, `test:integration` - -deno.json tasks: - -```json -{ - "tasks": { - "test:all": "deno test --allow-all", - "test:unit": "deno test --allow-all **/*.test.ts", - "test:integration": "deno test --allow-all **/*.integration.test.ts", - "coverage": "deno test --allow-all --coverage=etc/coverage && deno coverage etc/coverage --lcov > etc/coverage/lcov.info" - } -} -``` - -Correct: - -```typescript -import { describe, it } from "@std/testing/bdd"; -import { assertEquals, assertThrows } from "@std/assert"; - -describe("UserService", () => { - it("should create user with valid data", () => { - const user = createUser({ name: "Alice", email: "alice@example.com" }); - assertEquals(user.name, "Alice"); - }); - - it("should throw on invalid email", () => { - assertThrows( - () => createUser({ name: "Bob", email: "invalid" }), - Error, - "Invalid email", - ); - }); -}); -``` - -Incorrect: - -```typescript -import { expect } from "chai"; // external library, not standard -import assert from "node:assert"; // Node.js assert, not Deno standard - -test("user creation", () => { // non-standard test function - expect(result).to.equal(expected); -}); -``` - -**Test File Naming:** - -- Unit tests: `*.test.ts` (colocated with source) -- Integration tests: `*.integration.test.ts` -- Schema tests: `schema.test.ts` - ---- - -## Go Tooling - -### Make Commands - -Scope: Go services - -Rule: Use Makefile commands for all Go operations. Don't run go commands -directly for common tasks. - -```bash -# In /apps/services directory -make restart # Restart services after changes -make check # Run static analysis tools -make lint # Run linting (golangci-lint) -make fix # Fix formatting and linting issues -make test # Run tests -make build # Build binaries -``` - -Correct: - -```bash -cd apps/services && make lint -cd apps/services && make test -``` - -Incorrect: - -```bash -cd apps/services && go fmt ./... -cd apps/services && go test ./... -``` - ---- - -### Code Generation - -Scope: Go services with SQLC - -Rule: Use SQLC for database query generation. SQL queries live in -`etc/data/{datasource}/queries/`. - -**Structure:** - -``` -apps/services/ -├── etc/data/default/ -│ ├── migrations/ # Database migrations -│ ├── queries/ # SQL queries for SQLC -│ │ ├── profiles.sql -│ │ ├── users.sql -│ │ └── sessions.sql -│ └── seed/ # Seed data -├── pkg/api/adapters/storage/ -│ ├── *_gen.go # Generated SQLC code -│ └── repository.go # Repository wrapper -└── sqlc.yaml # SQLC configuration -``` - -Regenerate after SQL changes: - -```bash -cd apps/services && make generate -``` - ---- - -## Monorepo Commands - -Scope: Root-level operations - -Rule: Use root Makefile for cross-project operations. - -```bash -# In monorepo root -make ok # Run all quality checks -make build # Build all containers -make up # Start all services -make down # Stop all services -make logs # View service logs -``` - -**Workflow:** - -```bash -# After making changes -make ok # Ensure all checks pass -git add . && git commit -m "..." -``` - ---- - -## Editor Configuration - -Scope: Development environment - -Rule: Use project-level editor configuration for consistent formatting. - -**.editorconfig:** - -```ini -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.go] -indent_size = 4 -indent_style = tab - -[Makefile] -indent_style = tab -``` - -**VS Code Settings (.vscode/settings.json):** - -```json -{ - "editor.formatOnSave": true, - "deno.enable": true, - "deno.lint": true, - "go.lintTool": "golangci-lint", - "go.lintOnSave": "workspace" -} -``` - ---- - -## Codebase File Tools - -### File Tool Factory Pattern - -Scope: @eser/codebase file check/fix tools - -Rule: Use `createFileTool()` factory for all file-based codebase tools. Pure -`checkFile`/`checkAll` logic returns issues. Fixers return `{path, oldContent, newContent}` -mutations — never write to disk directly. - -Correct: -```typescript -export const tool: FileTool = createFileTool({ - name: "fix-eof", - description: "Ensure files end with newline", - canFix: true, - stacks: [], - defaults: {}, - checkFile(file, content) { /* pure logic */ }, - fixFile(file, content) { return { path, oldContent, newContent }; }, -}); -``` - -Incorrect: -```typescript -// Don't write files inside check/fix logic -fixFile(file, content) { - await fs.writeTextFile(file.path, fixed); // BAD — side effect -} -``` - ---- - -### @eser/functions Integration - -Scope: All codebase tools and CLI commands - -Rule: Tools use Handler/Adapter/ResponseMapper from `@eser/functions`. Handler = pure -logic, Adapter = input transform (CLI/MCP/agent), ResponseMapper = output format. - ---- - -### Runtime Abstraction - -Scope: All @eser/* packages - -Rule: Use `@eser/standards/runtime` for all filesystem, exec, and env access. Never use -`Deno.*` directly. Enables cross-runtime (Deno/Node/Bun) and testability. - -Correct: -```typescript -import { current } from "@eser/standards/runtime"; -const content = await current.fs.readTextFile(path); -``` - -Incorrect: -```typescript -const content = await Deno.readTextFile(path); // BAD — Deno-specific -``` - ---- - -### Walk-Once Pipeline - -Scope: @eser/codebase validation system - -Rule: When running multiple tools via `validate`, walk the filesystem once and pass the -file list to all validators. Fixers return mutations; the runner applies between tools -and writes to disk at the end. This enables dry-run and transactional rollback. diff --git a/.claude/skills/workflow-practices/SKILL.md b/.claude/skills/workflow-practices/SKILL.md deleted file mode 100644 index fbb87e3d9..000000000 --- a/.claude/skills/workflow-practices/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: workflow-practices -description: "Task execution workflow: implementation steps, git commit policy, quality gates, and code ownership. Use when planning tasks, making git commits, or running validation checks." ---- - -# Workflow Practices - -## Quick Start - -1. Read codebase -> Create todo list -> Check plan -> Execute -2. NEVER revert user changes or overwrite intentional code -3. Do NOT create git commits unless explicitly requested -4. Run `make ok` before considering work complete - -## Key Principles - -- Work WITH user changes, not against them -- Fully implement all functionality (no placeholders) -- Be concise, minimize prose -- If uncertain, say so -- Respect project's tooling decisions - -## Anti-Patterns - -**"I'll revert the user's code to fix this"** -No. Never overwrite intentional user changes. Work with their code. - -**"Let me just commit these changes"** -No. Never create commits unless explicitly requested by the user. - -**"I'll skip validate, it's a small change"** -No. Run `make ok` before considering any work complete. - -## References - -See [rules.md](references/rules.md) for complete conventions. diff --git a/.claude/skills/workflow-practices/references/rules.md b/.claude/skills/workflow-practices/references/rules.md deleted file mode 100644 index 5d5ee34a5..000000000 --- a/.claude/skills/workflow-practices/references/rules.md +++ /dev/null @@ -1,457 +0,0 @@ -# Workflow Practices - Detailed Rules - -## Standard Workflow - -Scope: All development tasks - -Rule: Follow a consistent workflow for task -execution. - -**Workflow Steps:** - -1. Read the codebase for relevant files -2. Create a todo list with actionable items -3. Check plan with user before executing -4. Execute todo items, marking complete as you go - ---- - -## Never Revert User Changes - -Scope: All code modifications - -Rule: NEVER revert, undo, or overwrite code that -user intentionally wrote. - -**Critical Rules:** - -- NEVER revert, undo, or overwrite code that user intentionally wrote -- NEVER pull code from git history or previous versions -- ALWAYS work with current state of files as they exist -- ALWAYS respect user's architectural decisions and naming choices -- If user has removed code, do NOT add it back -- If user has renamed fields, do NOT change them back -- Work WITH user changes, not against them - -Correct: - -``` -User removes a function -> Do not add it back -User renames a variable -> Use the new name -User changes architecture -> Follow the new pattern -``` - -Incorrect: - -``` -User removes a function -> "I noticed this was deleted, let me restore it" -User renames userId to id -> "I'll change this back to userId for consistency" -``` - ---- - -## Never Use Git to Revert File Contents - -Scope: All code modifications - -Rule: NEVER use `git checkout`, `git restore`, or any git command to revert -file contents. Parallel tasks run with parallel agents, so git-based reverting -can destroy work from other agents. Always undo changes using Edit operations. - -**Critical Rules:** - -- NEVER use `git checkout -- ` to revert changes -- NEVER use `git restore ` to revert changes -- NEVER use `git stash` to discard or shelve changes -- If you need to undo edits you just made, use the Edit tool to manually revert - each change -- This protects parallel agent workflows from losing each other's work - -Correct: - -``` -# Undo a change you just made using Edit tool -Edit: old_string="new code" new_string="original code" -``` - -Incorrect: - -``` -git checkout -- path/to/file -git restore path/to/file -git checkout HEAD -- path/to/file -``` - ---- - -## Git Commit Policy - -Scope: Version control - -Rule: Do NOT create git commits unless explicitly -requested by user. - -Correct: - -``` -User: "Please commit these changes" --> Create commit with descriptive message - -User: "Fix this bug" --> Fix the bug, do NOT commit -``` - -Incorrect: - -``` -User: "Fix this bug" --> Fix the bug AND create a commit without asking -``` - ---- - -## Code Quality Principles - -Scope: All code changes - -Rule: Write complete, correct, readable code. - -**Requirements:** - -- Follow user requirements carefully and to the letter -- Think step-by-step, describe plan in detail before implementing -- Write correct, idiomatic, bug-free, fully functional code -- Focus on readable code over premature optimization -- Fully implement all requested functionality (NO todos, placeholders, or - missing pieces) -- Include all required imports and proper naming -- Be concise, minimize prose -- If uncertain about correctness or don't know the answer, say so - -Correct: - -```typescript -// Complete implementation with all edge cases -function processOrder(order: Order): Result { - if (!order.items.length) { - return { success: false, error: "Empty order" }; - } - - const total = order.items.reduce( - (sum, item) => sum + item.price * item.quantity, - 0, - ); - const tax = total * order.taxRate; - - return { - success: true, - total: total + tax, - breakdown: { subtotal: total, tax }, - }; -} -``` - -Incorrect: - -```typescript -function processOrder(order: Order): Result { - // TODO: implement validation - // TODO: calculate tax - return { success: true, total: 0 }; // Placeholder -} -``` - ---- - -## Tooling Preferences - -Scope: Project tooling - -Rule: Never change native tooling without explicit -permission. - -**General Rule:** - -- Don't replace native tooling (Node.js, Deno, Go, Make) with alternatives -- Respect the project's architectural decisions about tooling -- Ask before changing any tool commands -- Each service has its own tooling philosophy - honor it - -Correct: - -``` -Project uses Make -> Use make commands -Project uses Deno -> Use deno commands -Project uses npm scripts -> Use `node --run` commands -``` - -Incorrect: - -``` -Project uses Make -> "Let me replace this with a shell script" -Project uses Deno -> "Let me add npm for this task" -``` - ---- - -## Handling Uncertainty - -Scope: All situations with incomplete information - -Rule: When uncertain, ask or -state the uncertainty. Never guess. - -Correct: - -``` -"I'm not certain about the expected behavior in this edge case. -Should the function return null or throw an error?" - -"I don't know the answer to that specific API behavior. -Let me check the documentation." -``` - -Incorrect: - -``` -// Making assumptions without stating them -function handleEdgeCase() { - return null; // Guessed behavior -} -``` - ---- - -## Implementation Completeness - -Scope: All feature implementations - -Rule: Fully implement all requested -functionality. No partial implementations. - -**Checklist:** - -- [ ] All requested features implemented -- [ ] All edge cases handled -- [ ] All imports included -- [ ] All types properly defined -- [ ] Error handling complete -- [ ] No TODO comments for required functionality -- [ ] No placeholder values -- [ ] No "implement later" notes - -Correct: - -```typescript -// Feature: User registration with email validation - -import { z } from "zod"; -import { hash } from "bcrypt"; -import { db } from "./database.ts"; - -const UserSchema = z.object({ - email: z.string().email(), - password: z.string().min(8), - name: z.string().min(1), -}); - -export async function registerUser(input: unknown): Promise { - const validated = UserSchema.parse(input); - - const existingUser = await db.user.findByEmail(validated.email); - if (existingUser) { - throw new Error("Email already registered"); - } - - const hashedPassword = await hash(validated.password, 10); - - return db.user.create({ - email: validated.email, - password: hashedPassword, - name: validated.name, - }); -} -``` - -Incorrect: - -```typescript -// Incomplete implementation -export async function registerUser(input: any) { - // TODO: add validation - // TODO: check for existing user - return db.user.create(input); -} -``` - ---- - -## Quality Gates (MANDATORY) - -Scope: All code changes - -Rule: ALWAYS run tests and linters when making changes. Fix linter errors -immediately. Never commit without passing all quality gates. - -**Before Committing Checklist:** - -- [ ] All linters pass (`deno lint`, `make lint`) -- [ ] All formatters pass (`deno fmt --check`, `make fix`) -- [ ] TypeScript compilation successful -- [ ] All tests pass (unit + integration) -- [ ] No TODO comments or placeholders left -- [ ] Endpoints tested with curl/manual testing - -**Frontend Quality Gates:** - -```bash -# In /apps/webclient directory -deno task lint # Run Deno linter -deno fmt --check # Check formatting -deno task build # Build for production -``` - -**Backend Quality Gates:** - -```bash -# In /apps/services directory -make check # Run static analysis tools -make lint # Run linting -make fix # Fix formatting and linting issues -make test # Run tests -``` - -**Root Quality Gates:** - -```bash -# In monorepo root -make ok # Run all quality checks -``` - -Correct: - -```bash -# Full quality check before committing -make lint && make test && make check -``` - -Incorrect: - -```bash -# Skipping quality gates -git add . && git commit -m "Quick fix" -``` - ---- - -## Testing Requirements - -Scope: All code changes - -Rule: Business logic MUST have unit tests. Adapters should have integration -tests. Use dependency injection for testable code. - -**Test Structure:** - -- Unit tests: Test business logic with mock adapters -- Integration tests: Test adapters with real dependencies -- Test files co-located with `_test` suffix (Go) or `.test.ts` (JS/TS) - -Correct: - -```go -// profiles_test.go - Unit test with mock -type mockRepository struct{} - -func (m *mockRepository) GetProfile(ctx context.Context, id string) (*Profile, error) { - return &Profile{ID: id, Title: "Test"}, nil -} - -func TestProfileService_GetProfile(t *testing.T) { - service := &Service{repo: &mockRepository{}} - profile, err := service.GetProfile(context.Background(), "test-id") - // Assert results -} -``` - -```typescript -// user-service.test.ts - Unit test with mock -const mockRepo = { - findUser: async (id: string) => ({ id, name: "Test" }), -}; - -Deno.test("UserService.getUser - returns user", async () => { - const service = new UserService(mockRepo); - const user = await service.getUser("test-id"); - assertEquals(user.name, "Test"); -}); -``` - -Incorrect: - -```typescript -// No tests for business logic -export function calculateDiscount(price: number, percent: number): number { - return price * (1 - percent / 100); -} -``` - ---- - -## Avoid Over-Engineering - -Scope: All code changes - -Rule: Only make changes that are directly requested or clearly necessary. Keep -solutions simple and focused. - -**Anti-patterns to Avoid:** - -- Don't add features beyond what was asked -- Don't refactor code that wasn't part of the request -- Don't add docstrings/comments to unchanged code -- Don't add error handling for scenarios that can't happen -- Don't create abstractions for one-time operations -- Don't design for hypothetical future requirements - -Correct: - -```typescript -// User asked: "Fix the null check in getUserName" -function getUserName(user: User | null): string { - if (user === null) { - return "Unknown"; - } - return user.name; -} -``` - -Incorrect: - -```typescript -// User asked: "Fix the null check in getUserName" -// But developer added unnecessary complexity - -/** Gets the user's display name with fallback support */ -type GetUserNameOptions = { - fallback?: string; - includeTitle?: boolean; -}; - -function getUserName(user: User | null, options: GetUserNameOptions = {}): string { - const { fallback = "Unknown", includeTitle = false } = options; - if (user === null) { - return fallback; - } - return includeTitle && user.title - ? `${user.title} ${user.name}` - : user.name; -} -``` - -**Guidelines:** - -- Three similar lines of code is better than a premature abstraction -- A bug fix doesn't need surrounding code cleaned up -- A simple feature doesn't need extra configurability -- Trust internal code and framework guarantees diff --git a/.dockerignore b/.dockerignore index 9f2691168..a5c2be716 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +.eser/.state/ .github/ .vscode/ etc/coverage/ @@ -7,7 +8,6 @@ etc/temp/ .editorconfig .gitattributes .gitignore -.manifest.yml .tool-versions Dockerfile LICENSE diff --git a/.eser/.gitignore b/.eser/.gitignore new file mode 100644 index 000000000..38ca6cd26 --- /dev/null +++ b/.eser/.gitignore @@ -0,0 +1,2 @@ +# eser toolchain runtime state — not tracked by git +.state/ diff --git a/.eser/baselines/code-conventions.json b/.eser/baselines/code-conventions.json new file mode 100644 index 000000000..e26436f6c --- /dev/null +++ b/.eser/baselines/code-conventions.json @@ -0,0 +1,599 @@ +{ + "namespace-import": { + ".agents/skills/eser-rules-manager/scripts/validate-skills.ts": 3, + "etc/scripts/dist-utils_test.ts": 1, + "etc/scripts/release-notes-from-changelog.ts": 3, + "pkg/@eserstack/agents/adapter.ts": 1, + "pkg/@eserstack/agents/adapters/claude-code.ts": 1, + "pkg/@eserstack/agents/adapters/codex.ts": 1, + "pkg/@eserstack/agents/adapters/opencode.ts": 1, + "pkg/@eserstack/agents/guards/git-bypass.test.ts": 2, + "pkg/@eserstack/agents/guards/git-config.test.ts": 2, + "pkg/@eserstack/agents/guards/git.test.ts": 2, + "pkg/@eserstack/agents/registry.ts": 3, + "pkg/@eserstack/ai/adapters/ajan-bridge.test.ts": 2, + "pkg/@eserstack/ai/adapters/claude-code.test.ts": 2, + "pkg/@eserstack/ai/adapters/factory-fallback.test.ts": 2, + "pkg/@eserstack/ai/adapters/factory-lookup.test.ts": 2, + "pkg/@eserstack/ai/commands/ask.ts": 1, + "pkg/@eserstack/ai/commands/list.ts": 1, + "pkg/@eserstack/ai/config.test.ts": 2, + "pkg/@eserstack/ai/content.test.ts": 2, + "pkg/@eserstack/ai/errors-wire.test.ts": 2, + "pkg/@eserstack/ai/errors.test.ts": 2, + "pkg/@eserstack/ai/generation.test.ts": 2, + "pkg/@eserstack/ai/main.ts": 2, + "pkg/@eserstack/ai/module.ts": 1, + "pkg/@eserstack/ai/registry.test.ts": 3, + "pkg/@eserstack/ai/streams/layers.test.ts": 2, + "pkg/@eserstack/ai/streams/sources.test.ts": 2, + "pkg/@eserstack/ai/types.test.ts": 2, + "pkg/@eserstack/ajan/ffi/client_test.ts": 1, + "pkg/@eserstack/ajan/ffi/client.ts": 1, + "pkg/@eserstack/ajan/ffi/mod_test.ts": 1, + "pkg/@eserstack/bundler/aot-snapshot.test.ts": 2, + "pkg/@eserstack/bundler/aot-snapshot.ts": 2, + "pkg/@eserstack/bundler/backends/deno-bundler.test.ts": 1, + "pkg/@eserstack/bundler/backends/deno-bundler.ts": 2, + "pkg/@eserstack/bundler/backends/mod.ts": 2, + "pkg/@eserstack/bundler/backends/rolldown.test.ts": 1, + "pkg/@eserstack/bundler/backends/rolldown.ts": 1, + "pkg/@eserstack/bundler/build-id.test.ts": 1, + "pkg/@eserstack/bundler/chunk-manifest.test.ts": 1, + "pkg/@eserstack/bundler/css/fonts.test.ts": 1, + "pkg/@eserstack/bundler/css/lightning.test.ts": 1, + "pkg/@eserstack/bundler/css/lightning.ts": 3, + "pkg/@eserstack/bundler/css/modules.test.ts": 1, + "pkg/@eserstack/bundler/css/modules.ts": 2, + "pkg/@eserstack/bundler/css/tailwind-plugin.test.ts": 1, + "pkg/@eserstack/bundler/manifest.test.ts": 1, + "pkg/@eserstack/bundler/manifest.ts": 1, + "pkg/@eserstack/bundler/module-map.test.ts": 1, + "pkg/@eserstack/cache/cache.test.ts": 1, + "pkg/@eserstack/cache/cache.ts": 1, + "pkg/@eserstack/cli/bin.ts": 1, + "pkg/@eserstack/cli/dispatch.ts": 1, + "pkg/@eserstack/cli/main.ts": 10, + "pkg/@eserstack/cli/plugins.test.ts": 2, + "pkg/@eserstack/cli/scripts.ts": 1, + "pkg/@eserstack/cli/scripts/npm-build-externals.test.ts": 2, + "pkg/@eserstack/cli/scripts/npm-build.ts": 2, + "pkg/@eserstack/cli/scripts/release-naming.test.ts": 1, + "pkg/@eserstack/codebase/ajan-ffi-parity.test.ts": 1, + "pkg/@eserstack/codebase/changelog-gen.test.ts": 1, + "pkg/@eserstack/codebase/changelog-gen.ts": 2, + "pkg/@eserstack/codebase/changelog-text.test.ts": 2, + "pkg/@eserstack/codebase/cli-support.ts": 2, + "pkg/@eserstack/codebase/cli-system/handlers/doctor.ts": 1, + "pkg/@eserstack/codebase/cli-system/handlers/info.ts": 1, + "pkg/@eserstack/codebase/cli-system/handlers/install.ts": 2, + "pkg/@eserstack/codebase/cli-system/handlers/uninstall.ts": 1, + "pkg/@eserstack/codebase/cli-system/handlers/update.test.ts": 2, + "pkg/@eserstack/codebase/cli-system/handlers/update.ts": 1, + "pkg/@eserstack/codebase/cli-system/layer.test.ts": 3, + "pkg/@eserstack/codebase/cli-system/mod.ts": 1, + "pkg/@eserstack/codebase/commitmsg.test.ts": 2, + "pkg/@eserstack/codebase/directive-analysis.test.ts": 1, + "pkg/@eserstack/codebase/directive-analysis.ts": 1, + "pkg/@eserstack/codebase/file-tool.ts": 3, + "pkg/@eserstack/codebase/file-tools-shared.ts": 3, + "pkg/@eserstack/codebase/gh-contributors.ts": 1, + "pkg/@eserstack/codebase/gh.ts": 1, + "pkg/@eserstack/codebase/git.ts": 1, + "pkg/@eserstack/codebase/main.ts": 3, + "pkg/@eserstack/codebase/module.ts": 1, + "pkg/@eserstack/codebase/npm-workspace-specifiers.test.ts": 2, + "pkg/@eserstack/codebase/package/loader.ts": 3, + "pkg/@eserstack/codebase/package/workspace.ts": 1, + "pkg/@eserstack/codebase/package/writer.ts": 2, + "pkg/@eserstack/codebase/release-contract.test.ts": 1, + "pkg/@eserstack/codebase/release-notes.test.ts": 2, + "pkg/@eserstack/codebase/release-notes.ts": 2, + "pkg/@eserstack/codebase/release-tag.ts": 3, + "pkg/@eserstack/codebase/release.ts": 2, + "pkg/@eserstack/codebase/validate-bom.ts": 1, + "pkg/@eserstack/codebase/validate-case-conflict.ts": 1, + "pkg/@eserstack/codebase/validate-circular-deps.ts": 2, + "pkg/@eserstack/codebase/validate-code-conventions.ts": 2, + "pkg/@eserstack/codebase/validate-commit-msg.ts": 2, + "pkg/@eserstack/codebase/validate-docs.ts": 2, + "pkg/@eserstack/codebase/validate-eof.ts": 1, + "pkg/@eserstack/codebase/validate-error-coverage.ts": 1, + "pkg/@eserstack/codebase/validate-export-names.ts": 2, + "pkg/@eserstack/codebase/validate-filenames.ts": 1, + "pkg/@eserstack/codebase/validate-json.ts": 1, + "pkg/@eserstack/codebase/validate-large-files.ts": 1, + "pkg/@eserstack/codebase/validate-licenses_test.ts": 2, + "pkg/@eserstack/codebase/validate-licenses.ts": 3, + "pkg/@eserstack/codebase/validate-line-endings.ts": 1, + "pkg/@eserstack/codebase/validate-merge-conflict.ts": 1, + "pkg/@eserstack/codebase/validate-mod-exports.ts": 2, + "pkg/@eserstack/codebase/validate-package-configs.ts": 2, + "pkg/@eserstack/codebase/validate-runtime-js-apis.ts": 2, + "pkg/@eserstack/codebase/validate-secrets.test.ts": 2, + "pkg/@eserstack/codebase/validate-secrets.ts": 1, + "pkg/@eserstack/codebase/validate-self-imports.test.ts": 1, + "pkg/@eserstack/codebase/validate-self-imports.ts": 2, + "pkg/@eserstack/codebase/validate-server-loc.ts": 1, + "pkg/@eserstack/codebase/validate-shebangs.ts": 1, + "pkg/@eserstack/codebase/validate-stream.ts": 1, + "pkg/@eserstack/codebase/validate-submodules.ts": 1, + "pkg/@eserstack/codebase/validate-symlinks.ts": 1, + "pkg/@eserstack/codebase/validate-toml.ts": 1, + "pkg/@eserstack/codebase/validate-trailing-whitespace.ts": 1, + "pkg/@eserstack/codebase/validate-yaml.ts": 1, + "pkg/@eserstack/codebase/validation/mod.ts": 4, + "pkg/@eserstack/codebase/validation/validators/circular-deps.ts": 1, + "pkg/@eserstack/codebase/validation/validators/docs.ts": 1, + "pkg/@eserstack/codebase/validation/validators/error-coverage.ts": 1, + "pkg/@eserstack/codebase/validation/validators/export-names.ts": 1, + "pkg/@eserstack/codebase/validation/validators/mod-exports.ts": 1, + "pkg/@eserstack/codebase/validation/validators/package-configs.ts": 1, + "pkg/@eserstack/codebase/validation/validators/server-loc.ts": 1, + "pkg/@eserstack/codebase/versions.ts": 2, + "pkg/@eserstack/codebase/walk-stream.ts": 1, + "pkg/@eserstack/codebase/workspace-discovery.ts": 1, + "pkg/@eserstack/collector/collector.ts": 1, + "pkg/@eserstack/collector/manifest.ts": 2, + "pkg/@eserstack/config/adapters/ffi/loader.ts": 2, + "pkg/@eserstack/config/dotenv/configure.ts": 3, + "pkg/@eserstack/config/dotenv/loader.ts": 2, + "pkg/@eserstack/config/dotenv/reader.ts": 1, + "pkg/@eserstack/config/file/loader.ts": 1, + "pkg/@eserstack/config/manifest.ts": 1, + "pkg/@eserstack/crypto/adapters/ffi/loader.ts": 2, + "pkg/@eserstack/crypto/crypto.ffi.test.ts": 2, + "pkg/@eserstack/crypto/hash.test.ts": 1, + "pkg/@eserstack/crypto/hash.ts": 1, + "pkg/@eserstack/cs/cli.ts": 2, + "pkg/@eserstack/cs/generate.test.ts": 1, + "pkg/@eserstack/cs/generate.ts": 1, + "pkg/@eserstack/cs/sync.test.ts": 1, + "pkg/@eserstack/cs/sync.ts": 1, + "pkg/@eserstack/di/container.test.ts": 3, + "pkg/@eserstack/di/container.ts": 2, + "pkg/@eserstack/di/decorators.test.ts": 2, + "pkg/@eserstack/di/decorators.ts": 1, + "pkg/@eserstack/di/fluent-api-factory.test.ts": 2, + "pkg/@eserstack/di/invoker.test.ts": 2, + "pkg/@eserstack/di/services.ts": 2, + "pkg/@eserstack/events/container.test.ts": 1, + "pkg/@eserstack/events/events.ts": 2, + "pkg/@eserstack/formats/adapters/ffi/loader.ts": 2, + "pkg/@eserstack/formats/deserializer.ts": 3, + "pkg/@eserstack/formats/formats.ffi.test.ts": 2, + "pkg/@eserstack/formats/formats.test.ts": 10, + "pkg/@eserstack/formats/formats/csv.ts": 1, + "pkg/@eserstack/formats/formats/json.ts": 1, + "pkg/@eserstack/formats/formats/jsonl.ts": 1, + "pkg/@eserstack/formats/formats/toml.ts": 1, + "pkg/@eserstack/formats/formats/yaml.ts": 1, + "pkg/@eserstack/formats/mod.ts": 6, + "pkg/@eserstack/formats/serializer.ts": 3, + "pkg/@eserstack/fp/append-to-array.bench.ts": 1, + "pkg/@eserstack/fp/append-to-array.test.ts": 1, + "pkg/@eserstack/fp/append-to-object.bench.ts": 1, + "pkg/@eserstack/fp/append-to-object.test.ts": 1, + "pkg/@eserstack/fp/associate-array.test.ts": 1, + "pkg/@eserstack/fp/associate-object.test.ts": 1, + "pkg/@eserstack/fp/chunk.bench.ts": 2, + "pkg/@eserstack/fp/chunk.test.ts": 1, + "pkg/@eserstack/fp/compose.test.ts": 1, + "pkg/@eserstack/fp/curry-right.test.ts": 1, + "pkg/@eserstack/fp/curry.test.ts": 1, + "pkg/@eserstack/fp/decorate.test.ts": 1, + "pkg/@eserstack/fp/deep-copy.bench.ts": 1, + "pkg/@eserstack/fp/deep-copy.test.ts": 1, + "pkg/@eserstack/fp/deep-merge.test.ts": 1, + "pkg/@eserstack/fp/dispatcher.test.ts": 1, + "pkg/@eserstack/fp/distinct-array.test.ts": 1, + "pkg/@eserstack/fp/distinct-object.test.ts": 1, + "pkg/@eserstack/fp/drop-from-array.test.ts": 1, + "pkg/@eserstack/fp/drop-from-array.ts": 1, + "pkg/@eserstack/fp/drop-from-object.test.ts": 1, + "pkg/@eserstack/fp/emitter.test.ts": 1, + "pkg/@eserstack/fp/filter-array.test.ts": 1, + "pkg/@eserstack/fp/filter-array.ts": 1, + "pkg/@eserstack/fp/filter-object.test.ts": 1, + "pkg/@eserstack/fp/flow.test.ts": 1, + "pkg/@eserstack/fp/get.bench.ts": 2, + "pkg/@eserstack/fp/get.test.ts": 1, + "pkg/@eserstack/fp/group-by.bench.ts": 2, + "pkg/@eserstack/fp/group-by.test.ts": 1, + "pkg/@eserstack/fp/iterate.test.ts": 2, + "pkg/@eserstack/fp/key-by.bench.ts": 2, + "pkg/@eserstack/fp/key-by.test.ts": 1, + "pkg/@eserstack/fp/map-array.test.ts": 1, + "pkg/@eserstack/fp/map-object.test.ts": 1, + "pkg/@eserstack/fp/match.test.ts": 1, + "pkg/@eserstack/fp/memoize.bench.ts": 2, + "pkg/@eserstack/fp/memoize.test.ts": 1, + "pkg/@eserstack/fp/merge-arrays.test.ts": 1, + "pkg/@eserstack/fp/merge-objects.test.ts": 1, + "pkg/@eserstack/fp/mutate.bench.ts": 1, + "pkg/@eserstack/fp/mutate.test.ts": 1, + "pkg/@eserstack/fp/mutate.ts": 1, + "pkg/@eserstack/fp/pick-from-array.test.ts": 1, + "pkg/@eserstack/fp/pick-from-array.ts": 1, + "pkg/@eserstack/fp/pick-from-object.test.ts": 1, + "pkg/@eserstack/fp/pipe.test.ts": 1, + "pkg/@eserstack/fp/prepend-to-array.test.ts": 1, + "pkg/@eserstack/fp/prepend-to-object.test.ts": 1, + "pkg/@eserstack/fp/remove-first-match-from-array.test.ts": 1, + "pkg/@eserstack/fp/remove-first-match-from-object.test.ts": 1, + "pkg/@eserstack/fp/remove-index-from-array.test.ts": 1, + "pkg/@eserstack/fp/remove-index-from-array.ts": 1, + "pkg/@eserstack/fp/remove-key-from-object.test.ts": 1, + "pkg/@eserstack/fp/remove-value-from-array.test.ts": 1, + "pkg/@eserstack/fp/remove-value-from-array.ts": 1, + "pkg/@eserstack/fp/remove-value-from-object.test.ts": 1, + "pkg/@eserstack/fp/reverse-array.test.ts": 1, + "pkg/@eserstack/fp/reverse-object.test.ts": 1, + "pkg/@eserstack/fp/split-array.test.ts": 1, + "pkg/@eserstack/fp/split-object.test.ts": 1, + "pkg/@eserstack/fp/take-from-array.test.ts": 1, + "pkg/@eserstack/fp/take-from-array.ts": 1, + "pkg/@eserstack/fp/take-from-object.test.ts": 1, + "pkg/@eserstack/fp/wth.bench.ts": 1, + "pkg/@eserstack/fp/wth.test.ts": 1, + "pkg/@eserstack/fp/wthout.bench.ts": 2, + "pkg/@eserstack/fp/wthout.test.ts": 1, + "pkg/@eserstack/functions/fn.test.ts": 2, + "pkg/@eserstack/functions/handler.test.ts": 4, + "pkg/@eserstack/functions/handler.ts": 1, + "pkg/@eserstack/functions/resources.test.ts": 1, + "pkg/@eserstack/functions/task.test.ts": 3, + "pkg/@eserstack/functions/task.ts": 1, + "pkg/@eserstack/functions/types.test.ts": 1, + "pkg/@eserstack/http/middlewares/cors.test.ts": 1, + "pkg/@eserstack/http/middlewares/csp.test.ts": 1, + "pkg/@eserstack/http/middlewares/csrf.test.ts": 1, + "pkg/@eserstack/http/middlewares/rate-limiter.test.ts": 2, + "pkg/@eserstack/httpclient/client-stream.test.ts": 2, + "pkg/@eserstack/httpclient/client.test.ts": 2, + "pkg/@eserstack/httpclient/errors.test.ts": 2, + "pkg/@eserstack/httpclient/go-client.test.ts": 2, + "pkg/@eserstack/httpclient/go-client.ts": 1, + "pkg/@eserstack/httpclient/interceptors.test.ts": 2, + "pkg/@eserstack/jsx-runtime/jsx-runtime.test.ts": 1, + "pkg/@eserstack/jsx-runtime/mod.ts": 1, + "pkg/@eserstack/kit/commands/add_test.ts": 2, + "pkg/@eserstack/kit/commands/clone_test.ts": 1, + "pkg/@eserstack/kit/commands/list_test.ts": 2, + "pkg/@eserstack/kit/commands/new_test.ts": 2, + "pkg/@eserstack/kit/ffi-client.ts": 1, + "pkg/@eserstack/kit/main.ts": 2, + "pkg/@eserstack/kit/module.ts": 1, + "pkg/@eserstack/kit/recipes/handlers/add-recipe.ts": 1, + "pkg/@eserstack/kit/recipes/handlers/clone-recipe.ts": 1, + "pkg/@eserstack/kit/recipes/handlers/list-recipes.ts": 1, + "pkg/@eserstack/kit/recipes/handlers/new-project.ts": 1, + "pkg/@eserstack/kit/recipes/handlers/update-recipe.ts": 1, + "pkg/@eserstack/kit/recipes/processor_test.ts": 3, + "pkg/@eserstack/kit/recipes/processor.ts": 1, + "pkg/@eserstack/kit/recipes/providers/github_test.ts": 2, + "pkg/@eserstack/kit/recipes/providers/jsr_test.ts": 2, + "pkg/@eserstack/kit/recipes/providers/npm_test.ts": 2, + "pkg/@eserstack/kit/recipes/recipe-applier_test.ts": 1, + "pkg/@eserstack/kit/recipes/registry-fetcher_test.ts": 1, + "pkg/@eserstack/kit/recipes/requires-resolver_test.ts": 1, + "pkg/@eserstack/kit/recipes/tar_test.ts": 3, + "pkg/@eserstack/kit/recipes/variable-processor_test.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/fonts/font-optimizer.ts": 1, + "pkg/@eserstack/laroux-bundler/adapters/lightningcss/css-processor.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/react/plugin.ts": 5, + "pkg/@eserstack/laroux-bundler/adapters/react/rsc-analyze.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/react/rsc-client-entry.ts": 1, + "pkg/@eserstack/laroux-bundler/adapters/react/rsc-module-map.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/react/rsc-rewrite-imports.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/runtime-bundler/runtime-bundler.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/sharp/image-optimizer.ts": 1, + "pkg/@eserstack/laroux-bundler/adapters/tailwindcss/compile.ts": 2, + "pkg/@eserstack/laroux-bundler/adapters/tailwindcss/critical-page-css.ts": 1, + "pkg/@eserstack/laroux-bundler/adapters/tailwindcss/plugin.ts": 5, + "pkg/@eserstack/laroux-bundler/css-modules.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/build-cache.ts": 1, + "pkg/@eserstack/laroux-bundler/domain/build-validator.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/bundler.test.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/bundler.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/chunk-manifest.ts": 1, + "pkg/@eserstack/laroux-bundler/domain/client-action-stub.ts": 1, + "pkg/@eserstack/laroux-bundler/domain/import-map.test.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/route-generator.ts": 2, + "pkg/@eserstack/laroux-bundler/domain/route-scanner.ts": 3, + "pkg/@eserstack/laroux-bundler/domain/server-action-transform.ts": 1, + "pkg/@eserstack/laroux-bundler/domain/virtual-source.ts": 2, + "pkg/@eserstack/laroux-bundler/import-map-resolver-plugin.test.ts": 2, + "pkg/@eserstack/laroux-bundler/import-map-resolver-plugin.ts": 2, + "pkg/@eserstack/laroux-bundler/system.ts": 20, + "pkg/@eserstack/laroux-react/client.test.ts": 3, + "pkg/@eserstack/laroux-react/client.ts": 2, + "pkg/@eserstack/laroux-react/client/bootstrap/entry.tsx": 7, + "pkg/@eserstack/laroux-react/client/bootstrap/error-boundary.tsx": 1, + "pkg/@eserstack/laroux-react/client/bootstrap/error-overlay.tsx": 1, + "pkg/@eserstack/laroux-react/protocol.test.ts": 2, + "pkg/@eserstack/laroux-react/runtime/image/image.tsx": 1, + "pkg/@eserstack/laroux-react/runtime/navigation/link.tsx": 1, + "pkg/@eserstack/laroux-react/runtime/navigation/use-router.ts": 2, + "pkg/@eserstack/laroux-react/runtime/router/router.tsx": 1, + "pkg/@eserstack/laroux-react/span-renderer.tsx": 1, + "pkg/@eserstack/laroux-server/adapters/react/error-chunk.test.ts": 5, + "pkg/@eserstack/laroux-server/adapters/react/html-shell.ts": 1, + "pkg/@eserstack/laroux-server/adapters/react/inline-rsc-emitter.ts": 3, + "pkg/@eserstack/laroux-server/adapters/react/render-app.tsx": 1, + "pkg/@eserstack/laroux-server/adapters/react/renderer.ts": 5, + "pkg/@eserstack/laroux-server/adapters/react/rsc-flight-renderer.ts": 2, + "pkg/@eserstack/laroux-server/adapters/react/rsc-handler.ts": 2, + "pkg/@eserstack/laroux-server/adapters/react/script-json.test.ts": 5, + "pkg/@eserstack/laroux-server/adapters/react/ssr-renderer.ts": 5, + "pkg/@eserstack/laroux-server/bin-main.ts": 3, + "pkg/@eserstack/laroux-server/cli.ts": 2, + "pkg/@eserstack/laroux-server/config/load-config.ts": 3, + "pkg/@eserstack/laroux-server/domain/html-shell.test.ts": 2, + "pkg/@eserstack/laroux-server/domain/renderer.test.ts": 2, + "pkg/@eserstack/laroux-server/error-formatting.test.ts": 2, + "pkg/@eserstack/laroux-server/main.ts": 12, + "pkg/@eserstack/laroux-server/module.ts": 1, + "pkg/@eserstack/laroux-server/runtime/hmr.test.ts": 2, + "pkg/@eserstack/laroux-server/runtime/html-shell.ts": 1, + "pkg/@eserstack/laroux-server/runtime/path-containment.test.ts": 4, + "pkg/@eserstack/laroux-server/runtime/server.ts": 8, + "pkg/@eserstack/laroux-server/scripts/npm-build.ts": 1, + "pkg/@eserstack/laroux/image/image-config.ts": 1, + "pkg/@eserstack/laroux/image/picture-sources.ts": 1, + "pkg/@eserstack/laroux/image/srcset-builder.ts": 1, + "pkg/@eserstack/laroux/router/matcher.ts": 1, + "pkg/@eserstack/logging/config.ts": 2, + "pkg/@eserstack/logging/context.ts": 1, + "pkg/@eserstack/logging/ffi-client.ts": 1, + "pkg/@eserstack/logging/filters.ts": 1, + "pkg/@eserstack/logging/formatters.ts": 1, + "pkg/@eserstack/logging/go-logger.ts": 1, + "pkg/@eserstack/logging/logger.test.ts": 1, + "pkg/@eserstack/logging/logger.ts": 4, + "pkg/@eserstack/logging/sinks.ts": 1, + "pkg/@eserstack/logging/tracer.ts": 2, + "pkg/@eserstack/mux/engine/geometry.test.ts": 3, + "pkg/@eserstack/mux/engine/reducer.test.ts": 2, + "pkg/@eserstack/mux/engine/reducer.ts": 1, + "pkg/@eserstack/mux/engine/sanitize.test.ts": 2, + "pkg/@eserstack/mux/engine/split-tree.test.ts": 1, + "pkg/@eserstack/mux/examples/demo.ts": 1, + "pkg/@eserstack/mux/frontends/tui/input-source.test.ts": 3, + "pkg/@eserstack/mux/frontends/tui/input-source.ts": 1, + "pkg/@eserstack/mux/frontends/tui/tui-frontend.ts": 4, + "pkg/@eserstack/mux/ipc/transport-ws.test.ts": 2, + "pkg/@eserstack/mux/keybindings/defaults.ts": 1, + "pkg/@eserstack/mux/keybindings/keybindings.test.ts": 4, + "pkg/@eserstack/mux/runner/inprocess.ts": 6, + "pkg/@eserstack/mux/scene/scene.test.ts": 5, + "pkg/@eserstack/mux/scene/scene.ts": 2, + "pkg/@eserstack/mux/server/server.test.ts": 4, + "pkg/@eserstack/mux/server/server.ts": 5, + "pkg/@eserstack/noskills-client/attach.ts": 1, + "pkg/@eserstack/noskills-server-worker/mux-worker.ts": 3, + "pkg/@eserstack/noskills-web/auth.test.ts": 2, + "pkg/@eserstack/noskills-web/routes/pages.ts": 3, + "pkg/@eserstack/noskills-web/server-auth.test.ts": 2, + "pkg/@eserstack/noskills-web/server.test.ts": 4, + "pkg/@eserstack/noskills-web/server.ts": 3, + "pkg/@eserstack/noskills-web/templates/components.ts": 1, + "pkg/@eserstack/noskills-web/templates/dashboard.ts": 1, + "pkg/@eserstack/noskills-web/templates/layout.test.ts": 2, + "pkg/@eserstack/noskills-web/templates/layout.ts": 1, + "pkg/@eserstack/noskills-web/templates/spec-detail.ts": 2, + "pkg/@eserstack/noskills-web/terminal/mux-host.ts": 1, + "pkg/@eserstack/noskills-web/terminal/ws-bridge.ts": 1, + "pkg/@eserstack/noskills/bin-main.ts": 3, + "pkg/@eserstack/noskills/commands/agentless.test.ts": 3, + "pkg/@eserstack/noskills/commands/approve.ts": 1, + "pkg/@eserstack/noskills/commands/block.ts": 1, + "pkg/@eserstack/noskills/commands/concern.ts": 2, + "pkg/@eserstack/noskills/commands/config.ts": 1, + "pkg/@eserstack/noskills/commands/done.ts": 1, + "pkg/@eserstack/noskills/commands/free.ts": 1, + "pkg/@eserstack/noskills/commands/git-guard-hook.test.ts": 2, + "pkg/@eserstack/noskills/commands/hook-decisions.test.ts": 2, + "pkg/@eserstack/noskills/commands/init.ts": 4, + "pkg/@eserstack/noskills/commands/invoke-hook.ts": 2, + "pkg/@eserstack/noskills/commands/ledger.ts": 1, + "pkg/@eserstack/noskills/commands/manager.ts": 2, + "pkg/@eserstack/noskills/commands/next.test.ts": 2, + "pkg/@eserstack/noskills/commands/next.ts": 1, + "pkg/@eserstack/noskills/commands/pack.ts": 2, + "pkg/@eserstack/noskills/commands/plan-export.test.ts": 3, + "pkg/@eserstack/noskills/commands/purge.test.ts": 2, + "pkg/@eserstack/noskills/commands/rule.ts": 1, + "pkg/@eserstack/noskills/commands/run.test.ts": 2, + "pkg/@eserstack/noskills/commands/run.ts": 1, + "pkg/@eserstack/noskills/commands/session.ts": 1, + "pkg/@eserstack/noskills/commands/spec-plan.test.ts": 2, + "pkg/@eserstack/noskills/commands/spec.ts": 1, + "pkg/@eserstack/noskills/commands/status.ts": 2, + "pkg/@eserstack/noskills/commands/validate.test.ts": 3, + "pkg/@eserstack/noskills/commands/validate.ts": 1, + "pkg/@eserstack/noskills/commands/watch.test.ts": 3, + "pkg/@eserstack/noskills/context/behavioral.test.ts": 3, + "pkg/@eserstack/noskills/context/compiler.test.ts": 3, + "pkg/@eserstack/noskills/context/compiler.ts": 2, + "pkg/@eserstack/noskills/context/concerns.test.ts": 2, + "pkg/@eserstack/noskills/context/concerns.ts": 1, + "pkg/@eserstack/noskills/context/interaction-hints.test.ts": 7, + "pkg/@eserstack/noskills/context/question-similarity.test.ts": 3, + "pkg/@eserstack/noskills/context/questions.test.ts": 3, + "pkg/@eserstack/noskills/context/split-detector.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/confidence.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/delegation.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/diagrams.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/events.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/followup.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/learnings.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/promotion.test.ts": 2, + "pkg/@eserstack/noskills/dashboard/state.test.ts": 2, + "pkg/@eserstack/noskills/defaults/concerns/mod.ts": 17, + "pkg/@eserstack/noskills/defaults/packs/mod.ts": 5, + "pkg/@eserstack/noskills/detect/detect.test.ts": 3, + "pkg/@eserstack/noskills/go-noskills.ts": 1, + "pkg/@eserstack/noskills/main.ts": 2, + "pkg/@eserstack/noskills/module.ts": 1, + "pkg/@eserstack/noskills/output/cmd.ts": 1, + "pkg/@eserstack/noskills/pack/pack.test.ts": 3, + "pkg/@eserstack/noskills/scripts/npm-build.ts": 1, + "pkg/@eserstack/noskills/spec/generator.ts": 1, + "pkg/@eserstack/noskills/spec/living.test.ts": 2, + "pkg/@eserstack/noskills/spec/template.test.ts": 3, + "pkg/@eserstack/noskills/state/agentless-flow.test.ts": 3, + "pkg/@eserstack/noskills/state/debt.test.ts": 3, + "pkg/@eserstack/noskills/state/decision-ledger.test.ts": 2, + "pkg/@eserstack/noskills/state/decisions.test.ts": 3, + "pkg/@eserstack/noskills/state/discovery-features.test.ts": 1, + "pkg/@eserstack/noskills/state/flow-shortcuts.test.ts": 3, + "pkg/@eserstack/noskills/state/identity.test.ts": 2, + "pkg/@eserstack/noskills/state/iteration.test.ts": 1, + "pkg/@eserstack/noskills/state/lifecycle.test.ts": 2, + "pkg/@eserstack/noskills/state/machine.test.ts": 2, + "pkg/@eserstack/noskills/state/machine.ts": 1, + "pkg/@eserstack/noskills/state/persistence.ts": 1, + "pkg/@eserstack/noskills/state/ralph-loop.test.ts": 2, + "pkg/@eserstack/noskills/state/schema.test.ts": 2, + "pkg/@eserstack/noskills/state/session-id.test.ts": 1, + "pkg/@eserstack/noskills/sync/adapters/codex.test.ts": 2, + "pkg/@eserstack/noskills/sync/adapters/copilot-cli.test.ts": 2, + "pkg/@eserstack/noskills/sync/adapters/kiro.test.ts": 2, + "pkg/@eserstack/noskills/sync/adapters/opencode.test.ts": 2, + "pkg/@eserstack/noskills/sync/engine.test.ts": 2, + "pkg/@eserstack/noskills/sync/hooks.test.ts": 2, + "pkg/@eserstack/noskills/sync/interaction-sync.test.ts": 2, + "pkg/@eserstack/parsing/adapters/ffi/loader.ts": 2, + "pkg/@eserstack/parsing/lexer/lexer.test.ts": 2, + "pkg/@eserstack/parsing/lexer/lexer.ts": 1, + "pkg/@eserstack/parsing/lexer/tokens/simple.ts": 3, + "pkg/@eserstack/parsing/parser.test.ts": 3, + "pkg/@eserstack/parsing/parsing.ffi.test.ts": 2, + "pkg/@eserstack/posts/adapters/ai/agent.test.ts": 2, + "pkg/@eserstack/posts/adapters/ai/agent.ts": 2, + "pkg/@eserstack/posts/adapters/ai/tool-definitions.test.ts": 1, + "pkg/@eserstack/posts/adapters/ai/triggers.test.ts": 2, + "pkg/@eserstack/posts/adapters/bluesky/mappers.ts": 2, + "pkg/@eserstack/posts/adapters/bluesky/social-api.ts": 3, + "pkg/@eserstack/posts/adapters/cli/commands/thread.ts": 1, + "pkg/@eserstack/posts/adapters/cli/output.test.ts": 1, + "pkg/@eserstack/posts/adapters/cli/wiring.ts": 1, + "pkg/@eserstack/posts/adapters/token-store/file-token-store.test.ts": 2, + "pkg/@eserstack/posts/adapters/tui/app.ts": 2, + "pkg/@eserstack/posts/adapters/tui/callback-server.test.ts": 2, + "pkg/@eserstack/posts/adapters/twitter/auth-provider.ts": 1, + "pkg/@eserstack/posts/adapters/twitter/mappers.ts": 2, + "pkg/@eserstack/posts/adapters/twitter/social-api.ts": 4, + "pkg/@eserstack/posts/application/feed-aggregator.test.ts": 1, + "pkg/@eserstack/posts/application/handlers.ts": 2, + "pkg/@eserstack/posts/application/post-service.test.ts": 1, + "pkg/@eserstack/posts/application/thread-post-error.test.ts": 2, + "pkg/@eserstack/posts/application/token-utils.test.ts": 2, + "pkg/@eserstack/posts/application/with-fresh-tokens.test.ts": 3, + "pkg/@eserstack/posts/application/with-fresh-tokens.ts": 2, + "pkg/@eserstack/posts/config.test.ts": 2, + "pkg/@eserstack/posts/go-posts.ts": 1, + "pkg/@eserstack/posts/module.ts": 1, + "pkg/@eserstack/shell/args/command.test.ts": 2, + "pkg/@eserstack/shell/args/command.ts": 3, + "pkg/@eserstack/shell/completions/mod.ts": 3, + "pkg/@eserstack/shell/env/executable.test.ts": 2, + "pkg/@eserstack/shell/env/executable.ts": 1, + "pkg/@eserstack/shell/exec/child-go.ts": 1, + "pkg/@eserstack/shell/exec/command.test.ts": 2, + "pkg/@eserstack/shell/exec/command.ts": 2, + "pkg/@eserstack/shell/exec/exec.ts": 2, + "pkg/@eserstack/shell/exec/parser.test.ts": 2, + "pkg/@eserstack/shell/exec/pty-go.ts": 1, + "pkg/@eserstack/shell/exec/pty.ts": 2, + "pkg/@eserstack/shell/go-exec.ts": 1, + "pkg/@eserstack/shell/module.ts": 1, + "pkg/@eserstack/shell/tui/ansi.test.ts": 2, + "pkg/@eserstack/shell/tui/box.test.ts": 2, + "pkg/@eserstack/shell/tui/keypress-go.ts": 1, + "pkg/@eserstack/shell/tui/keypress.ts": 1, + "pkg/@eserstack/shell/tui/layout.test.ts": 2, + "pkg/@eserstack/shell/tui/mouse.test.ts": 2, + "pkg/@eserstack/shell/tui/scroll-container.test.ts": 2, + "pkg/@eserstack/shell/tui/tab-bar.test.ts": 2, + "pkg/@eserstack/shell/tui/textarea.test.ts": 2, + "pkg/@eserstack/shell/tui/vterm-snapshots.test.ts": 1, + "pkg/@eserstack/shell/tui/vterm-widget.ts": 1, + "pkg/@eserstack/shell/vterm/renderer.ts": 1, + "pkg/@eserstack/shell/vterm/terminal.ts": 1, + "pkg/@eserstack/shell/vterm/vterm.test.ts": 4, + "pkg/@eserstack/standards/collections/builder.ts": 1, + "pkg/@eserstack/standards/collections/immutable-map.bench.ts": 1, + "pkg/@eserstack/standards/collections/indexed.bench.ts": 1, + "pkg/@eserstack/standards/collections/registry.bench.ts": 1, + "pkg/@eserstack/standards/collections/registry.test.ts": 1, + "pkg/@eserstack/standards/cross-runtime/adapters/browser.ts": 2, + "pkg/@eserstack/standards/cross-runtime/adapters/deno.ts": 3, + "pkg/@eserstack/standards/cross-runtime/adapters/node-shared.ts": 3, + "pkg/@eserstack/standards/cross-runtime/adapters/node.ts": 3, + "pkg/@eserstack/standards/cross-runtime/adapters/shared.ts": 2, + "pkg/@eserstack/standards/cross-runtime/adapters/workerd.ts": 2, + "pkg/@eserstack/standards/cross-runtime/execution-context.test.ts": 1, + "pkg/@eserstack/standards/cross-runtime/mod.test.ts": 2, + "pkg/@eserstack/standards/cross-runtime/mod.ts": 1, + "pkg/@eserstack/standards/datetimes/format.test.ts": 1, + "pkg/@eserstack/standards/formatters/format-duration.test.ts": 1, + "pkg/@eserstack/standards/formatters/format-number.test.ts": 1, + "pkg/@eserstack/standards/formatters/format-percent.test.ts": 1, + "pkg/@eserstack/standards/formatters/format-size.test.ts": 1, + "pkg/@eserstack/standards/http-error.test.ts": 1, + "pkg/@eserstack/standards/i18n/locales.test.ts": 1, + "pkg/@eserstack/standards/routes/matcher.test.ts": 1, + "pkg/@eserstack/standards/strings/interpolate.test.ts": 1, + "pkg/@eserstack/standards/versions.test.ts": 1, + "pkg/@eserstack/streams/layers/filter.ts": 1, + "pkg/@eserstack/streams/layers/map.ts": 1, + "pkg/@eserstack/streams/layers/tap.ts": 1, + "pkg/@eserstack/streams/output.ts": 4, + "pkg/@eserstack/streams/pipeline.ts": 2, + "pkg/@eserstack/streams/renderers/ansi.ts": 1, + "pkg/@eserstack/streams/renderers/plain.ts": 1, + "pkg/@eserstack/streams/sources/events.ts": 1, + "pkg/@eserstack/streams/sources/readable.ts": 1, + "pkg/@eserstack/streams/sources/stdin.ts": 1, + "pkg/@eserstack/streams/sources/values.ts": 1, + "pkg/@eserstack/streams/streams.test.ts": 13, + "pkg/@eserstack/streams/untrusted-text.test.ts": 1, + "pkg/@eserstack/testing/fake-fs.test.ts": 2, + "pkg/@eserstack/testing/fake-fs.ts": 1, + "pkg/@eserstack/testing/fake-server.test.ts": 1, + "pkg/@eserstack/testing/temp-dir.test.ts": 2, + "pkg/@eserstack/testing/temp-dir.ts": 1, + "pkg/@eserstack/workflows/builder_test.ts": 1, + "pkg/@eserstack/workflows/composition_test.ts": 2, + "pkg/@eserstack/workflows/engine_test.ts": 2, + "pkg/@eserstack/workflows/list.ts": 3, + "pkg/@eserstack/workflows/loader_test.ts": 1, + "pkg/@eserstack/workflows/main.ts": 2, + "pkg/@eserstack/workflows/module.ts": 1, + "pkg/@eserstack/workflows/registry_test.ts": 1, + "pkg/@eserstack/workflows/run.ts": 5 + }, + "or-default": { + "pkg/@eserstack/ai/adapters/anthropic.ts": 1, + "pkg/@eserstack/ai/adapters/openai.ts": 1, + "pkg/@eserstack/bundler/css/fonts.test.ts": 1, + "pkg/@eserstack/laroux-bundler/domain/route-scanner.ts": 1, + "pkg/@eserstack/laroux-server/adapters/react/ssr-renderer.ts": 1, + "pkg/@eserstack/laroux-server/domain/middleware-dispatcher.ts": 1, + "pkg/@eserstack/laroux-server/domain/route-dispatcher.ts": 1, + "pkg/@eserstack/laroux/image/image-config.ts": 1, + "pkg/@eserstack/noskills-web/static/client.js": 1, + "pkg/@eserstack/noskills-web/static/mux-render.js": 2, + "pkg/@eserstack/noskills/commands/block.ts": 1, + "pkg/@eserstack/noskills/commands/next.ts": 2, + "pkg/@eserstack/noskills/commands/spec.ts": 1, + "pkg/@eserstack/noskills/state/iteration.ts": 1, + "pkg/@eserstack/noskills/sync/adapters/kiro.ts": 2, + "pkg/@eserstack/posts/adapters/token-store/file-token-store.ts": 1, + "pkg/@eserstack/shell/tui/vterm-snapshots.test.ts": 1, + "pkg/@eserstack/shell/vterm/renderer.ts": 1, + "pkg/@eserstack/workflows/shell-tool.ts": 1 + } +} diff --git a/.eser/manifest.yml b/.eser/manifest.yml new file mode 100644 index 000000000..fd540cacc --- /dev/null +++ b/.eser/manifest.yml @@ -0,0 +1,378 @@ +stack: + - javascript + - golang + +workflows: + - id: default + on: [precommit, prepush] + steps: + # Internal tools + # docs/api/ is `deno doc --html` output, regenerated wholesale by + # `deno task cli docs`. Linting it means every regeneration re-breaks the + # gate and has to be re-fixed by hand -- which is exactly what the + # previously committed, newline-fixed generated files show happened. + - validate-eof: + exclude: ["docs/api/"] + - validate-trailing-whitespace: + exclude: ["docs/api/"] + - validate-bom: + exclude: ["docs/api/"] + - validate-line-endings: + exclude: ["docs/api/"] + - validate-large-files: + maxKb: 1024 + exclude: ["docs/api/"] + - validate-case-conflict + - validate-merge-conflict + - validate-json: + exclude: + - ".eser/recipes/" + - "tsconfig.json" + - validate-toml + - validate-yaml: + allowMultipleDocuments: true + - validate-symlinks + - validate-shebangs + - validate-secrets: + allowMissingCredentials: true + exclude: + # Agent skill definitions whose security rules teach by + # counter-example -- the "secrets" they contain are the hardcoded + # credentials the docs tell you never to write. .claude is a + # symlink to .agents (see AGENTS.md). + - ".claude/skills/" + - ".agents/skills/" + - validate-filenames: + rules: + - directory: "cmd/" + convention: "snake_case" + exclude: + - "Makefile" + - "README.md" + - directory: "pkg/ajan/" + convention: "snake_case" + exclude: + - "Makefile" + - "README.md" + - directory: "pkg/@eserstack/ajan/" + convention: "snake_case" + exclude: + - "Dockerfile" + - "Makefile" + - "README.md" + - directory: "*" + convention: "kebab-case" + exclude: + # SKILL.md is a fixed filename required by the agent-skills + # convention, so the tree cannot satisfy kebab-case (.claude is + # a symlink to .agents). + - ".claude/" + - ".agents/" + # Backlog.md names task files "task-1 - Title.md" by design. + - "backlog/" + - ".github/" + - ".git/COMMIT_EDITMSG" + - "AGENTS.md" + - "CHANGELOG.md" + - "CONTRIBUTING.md" + - "Makefile" + - "PHILOSOPHY.md" + - "VERSION" + - "cmd/" + - "pkg/ajan/" + - "pkg/@eserstack/ajan/" + - "docs/" + - "etc/coverage/" + - "etc/temp/" + - ".eser/recipes/" + - "Dockerfile" + - "LICENSE" + - "README.md" + - "TODOS.md" + - "WIRING.md" + - "pkg/*/README.md" + - "pkg/@eserstack/noskills/README-HOW.md" + - "*_test.ts" + - "*_test.go" + - validate-submodules + - validate-licenses: + exclude: + - ".eser/recipes/" + - validate-runtime-js-apis: + exclude: + - "pkg/@eserstack/standards/cross-runtime/" + - "pkg/@eserstack/cli/scripts/compile.ts" + - "etc/scripts/hybrid-sniffer.ts" + # Bootstrap: generates the package manifests, so it runs before the + # package graph exists and cannot import from it. + - "etc/scripts/gen-jsr-manifests.ts" + # Release tooling: runs from a fresh checkout after publish and must + # not depend on the workspace it just released (Deno-only scripts). + - "etc/scripts/update-homebrew-formula.ts" + - "etc/scripts/update-nix-hashes.ts" + - "etc/scripts/preflight-npm-consumer.ts" + # Tests and benches run only under Deno — exempt from the portable-API rule. + - "_test.ts" + - ".test.ts" + - validate-self-imports: + exclude: + # Tests are not part of the published module graph. + - "_test.ts" + - ".test.ts" + # Namespace imports and `??` defaults (javascript-practices). Existing + # violations are listed in .eser/baselines/code-conventions.json; only + # new ones fail. Shrink the baseline after migrating files with + # `deno run -A ./pkg/@eserstack/codebase/validate-code-conventions.ts --write-baseline`. + - validate-code-conventions + - validate-server-loc: + directory: "pkg/ajan/noskillsserverfx" + - validate-error-coverage: + file: "pkg/ajan/noskillsserverfx/errors.go" + errorObjects: + - NSError + + # Regenerate the per-package JSR publish manifests. + # + # pkg/@eserstack/*/deno.json is derived from each package's package.json + # plus the root VERSION file (and deno.extra.json where a package needs + # `publish.include` or `tasks`, which package.json cannot express). They + # are gitignored: keeping them checked in duplicated every exports map in + # two places, which is what made a one-line version bump rewrite ~70 + # files and had already drifted once. + - shell: + name: gen-jsr-manifests + command: deno run -A ./etc/scripts/gen-jsr-manifests.ts + timeout: 60 + + # Build the native FFI library before any Deno step that loads it. + # + # ~20 TS packages reach Go through pkg/@eserstack/ajan's c-shared library. + # Without this step the *.ffi.test.ts suites fail with "native library + # unavailable" on a clean checkout, and CI otherwise type-checks and tests + # the whole TS half against whatever binary was last *published* -- so an + # ABI change cannot fail the build that introduces it. + # + # GOWORK=off: the artifact must resolve from pkg/@eserstack/ajan/go.mod + # alone, not the repo-root workspace (see go.work). + - shell: + name: build-native-lib + command: deno run -A ./pkg/@eserstack/ajan/scripts/build.ts + timeout: 300 + + # Deno checks + - shell: + name: deno-formatter + command: deno fmt --check + fixCommand: deno fmt + - shell: + name: deno-linter + command: deno lint . + fixCommand: deno lint --fix . + - shell: + name: deno-type-check + command: deno check ./pkg/@eserstack/*/mod.ts + timeout: 120 + - shell: + name: deno-tests + command: deno test --allow-sys --allow-net --allow-env --allow-read + --allow-write --allow-run --allow-ffi --parallel --trace-leaks + --coverage=./etc/coverage/ + timeout: 300 + - shell: + name: deno-publish-check + command: deno publish --dry-run --allow-dirty + timeout: 120 + + # Go checks. + # + # This repo is TWO Go modules: github.com/eser/stack (root) and + # github.com/eser/ajan (pkg/@eserstack/ajan, the cgo/WASI FFI bridge). + # `./...` never crosses a module boundary, so each module is checked in + # its own directory -- otherwise the bridge (bridge.go + main*.go, ~5.9k + # LOC) and its tests go completely unchecked, which was the case until + # 2026-08. + # + # There is deliberately NO go.work. A workspace would let one command + # cover both modules, but it also merges golangci-lint's dependency graph + # into the bridge's, which produced an "ambiguous import" on + # cloud.google.com/go/compute that needed a `replace` to paper over, and + # forced GOWORK=off on release builds so shipped artifacts still resolved + # from the bridge's own go.mod. Two `cd`s are cheaper than that. + - shell: + name: go-check + command: "go vet ./... && go build ./... && + cd pkg/@eserstack/ajan && go vet ./... && go build ./..." + timeout: 180 + # golangci-lint is declared in the ROOT module's `tool` block, so + # `go tool golangci-lint` cannot resolve it from inside the bridge + # module. Build it once from root, then run that binary in both. + - shell: + name: go-lint + command: "go build -o etc/temp/golangci-lint + github.com/golangci/golangci-lint/v2/cmd/golangci-lint && + ./etc/temp/golangci-lint run ./... && + cd pkg/@eserstack/ajan && ../../../etc/temp/golangci-lint run ./..." + fixCommand: "gofmt -w . && go build -o etc/temp/golangci-lint + github.com/golangci/golangci-lint/v2/cmd/golangci-lint && + ./etc/temp/golangci-lint run --fix ./... && + cd pkg/@eserstack/ajan && ../../../etc/temp/golangci-lint run --fix + ./..." + timeout: 300 + - shell: + name: go-tests + command: "go test -race ./... && + cd pkg/@eserstack/ajan && go test -race ./..." + timeout: 420 + + # Release preflight: every release-only step, run on main BEFORE a tag exists. + # + # Each entry here is a step that used to run only after the tag was pushed + # and that failed at least one release in 2026 (wasip1 build, npm bundle + # externals, generated manifests, publish dry-run, release tooling). A tag + # is pushed onto a commit that already passed this, so the release run never + # learns anything new. Kept under ten minutes on CI; heavier checks belong in + # precommit or in the release run itself. + - id: preflight + on: [preflight] + steps: + - shell: + name: gen-jsr-manifests + command: deno run -A ./etc/scripts/gen-jsr-manifests.ts + timeout: 60 + - shell: + name: deno-publish-check + command: deno publish --dry-run --allow-dirty + timeout: 120 + - shell: + name: npm-build-cli + command: deno run --allow-all ./pkg/@eserstack/cli/scripts/npm-build.ts + timeout: 180 + - shell: + name: npm-build-noskills + command: deno run --allow-all ./pkg/@eserstack/noskills/scripts/npm-build.ts + timeout: 180 + - shell: + name: npm-build-laroux + command: deno run --allow-all ./pkg/@eserstack/laroux-server/scripts/npm-build.ts + timeout: 180 + # Installs the packed CLI OUTSIDE the repo and runs the no-Deno smoke + # commands; inside the monorepo, Node would resolve undeclared + # dependencies from the workspace and hide them. + - shell: + name: npm-consumer-install + command: deno run --allow-all ./etc/scripts/preflight-npm-consumer.ts + timeout: 300 + # Native (this platform) plus both WASM modules; the c-shared build is + # what compile-binaries ships, the wasip1 build is what broke v4.5.0. + - shell: + name: ajan-native-build + command: deno run --allow-all ./pkg/@eserstack/ajan/scripts/build.ts + timeout: 300 + - shell: + name: ajan-wasm-build + command: deno run --allow-all ./pkg/@eserstack/ajan/scripts/build.ts --wasm + timeout: 300 + - shell: + name: homebrew-dry-run + command: deno run --allow-all ./etc/scripts/update-homebrew-formula.ts --dry-run + timeout: 60 + - shell: + name: nix-dry-run + command: deno run --allow-all ./etc/scripts/update-nix-hashes.ts --dry-run + timeout: 60 + # The release run's `deno compile`, for this host only. Caught nothing + # before v4.5.2, where Deno's dependency-age gate refused a day-old + # package at compile time and the release died after publish preflight. + - shell: + name: compile-current + command: deno run --allow-all ./pkg/@eserstack/cli/scripts/compile.ts --current-only + timeout: 600 + + - id: commit-validation + on: [commitmsg] + steps: + - validate-commit-msg: + forceScope: true + strict: true + types: + - ci + - chore + - docs + - feat + - fix + - perf + - refactor + - revert + - test + +scripts: + # Validation + ok: eser workflows run -e precommit + fix: eser workflows run -e precommit --fix + # Everything the release run does that precommit does not; run before tagging. + preflight: eser workflows run -e preflight + + # Setup + init: pnpm install && eser codebase install + + # Testing + test: + command: deno test --allow-sys --allow-net --allow-env --allow-read + --allow-write --allow-run --allow-ffi --parallel --trace-leaks + --coverage=./etc/coverage/ + description: Run tests with coverage + test-watch: + command: deno test --allow-sys --allow-net --allow-env --allow-read + --allow-write --allow-run --allow-ffi --parallel --watch + description: Run tests in watch mode + + # Code quality + lint: deno lint . + fmt: deno fmt + check: deno check ./pkg/@eserstack/*/mod.ts + + # Build + build: deno run --allow-all ./pkg/@eserstack/cli/scripts/npm-build.ts + compile: deno run --allow-all ./pkg/@eserstack/cli/scripts/compile.ts + + # Documentation + # Generates into docs/api/, NOT docs/. docs/ also holds hand-written content -- + # docs/adr/ and docs/schema/ -- and the previous `rm -rf ./docs/` deleted it. + # pkg/@cool/ was also globbed here and does not exist. + docs: "rm -rf ./docs/api/ && deno doc --name='eserstack' --output=./docs/api/ + --html ./pkg/@eserstack/*/mod.ts" + docs-lint: deno doc --lint ./pkg/@eserstack/*/mod.ts + + # Docker + container-build: docker build -t eservm . + container-run: docker run --interactive --tty --rm eservm + + # Maintenance + clean: "rm -rf ./etc/coverage/* ./etc/coverage.lcov ./pnpm-lock.yaml && deno + check ./pkg/@eserstack/*/mod.ts" + repl: deno repl --allow-all --unstable-bundle --eval-file=./repl-init.ts + + # Coverage + coverage: deno coverage ./etc/coverage/ + coverage-gen: deno coverage ./etc/coverage --lcov --output=./etc/coverage.lcov + + # Release + # `release ` bumps, changelogs, commits, pushes, then + # pushes the v tag. The TAG PUSH is what triggers build.yml's release + # run — CI never creates tags, because a tag pushed by its own GITHUB_TOKEN + # dispatches nothing. + release: eser codebase release + rerelease: eser codebase rerelease + unrelease: eser codebase unrelease + version-bump: eser codebase versions + # Escape hatch: tag by hand when a release commit already landed without one. + tag: eser codebase gh release-tag + release-notes: eser codebase gh release-notes + changelog: eser codebase changelog-gen + + # Go shortcuts + go-ok: make ok + go-test: make test-coverage + go-lint: make lint + go-fmt: make fix + go-build: make build diff --git a/.eser/packs.json b/.eser/packs.json new file mode 100644 index 000000000..b1c0459a5 --- /dev/null +++ b/.eser/packs.json @@ -0,0 +1,3 @@ +{ + "installed": [] +} diff --git a/.eser/recipes.json b/.eser/recipes.json new file mode 100644 index 000000000..7663f1995 --- /dev/null +++ b/.eser/recipes.json @@ -0,0 +1,602 @@ +{ + "$schema": "https://eser.live/registry/v1.json", + "name": "eser", + "description": "Terminal client for Eser's work", + "author": "Eser Ozvataf", + "registryUrl": "https://raw.githubusercontent.com/eser/stack/main/.eser", + "recipes": [ + { + "name": "library-pkg", + "description": "Deno library package with tests and README", + "language": "typescript", + "scale": "project", + "tags": ["deno", "library", "typescript", "jsr"], + "files": [ + { "source": "recipes/library-pkg/mod.ts", "target": "mod.ts" }, + { + "source": "recipes/library-pkg/mod.test.ts", + "target": "mod.test.ts" + }, + { "source": "recipes/library-pkg/deno.json", "target": "deno.json" }, + { "source": "recipes/library-pkg/README.md", "target": "README.md" } + ] + }, + { + "name": "fp-pipe", + "description": "Functional pipe and compose utilities", + "language": "typescript", + "scale": "utility", + "tags": ["fp", "functional", "pipe", "compose"], + "files": [ + { "source": "recipes/fp-pipe/pipe.ts", "target": "lib/fp/pipe.ts" } + ], + "dependencies": { + "jsr": ["jsr:@eserstack/fp@^4.1.0"] + } + }, + { + "name": "laroux-app", + "description": "Laroux.js web application with SSR and React", + "language": "typescript", + "scale": "project", + "tags": ["laroux", "react", "ssr", "web"], + "variables": [ + { + "name": "project_name", + "description": "Project name", + "default": "my-laroux-app" + } + ], + "files": [ + { "source": "recipes/laroux-app/.gitignore", "target": ".gitignore" }, + { "source": "recipes/laroux-app/.npmrc", "target": ".npmrc" }, + { + "source": "recipes/laroux-app/laroux.config.ts", + "target": "laroux.config.ts" + }, + { + "source": "recipes/laroux-app/package.json", + "target": "package.json" + }, + { + "source": "recipes/laroux-app/tsconfig.json", + "target": "tsconfig.json" + }, + { + "source": "recipes/laroux-app/public/favicon.svg", + "target": "public/favicon.svg" + }, + { + "source": "recipes/laroux-app/public/llms.txt", + "target": "public/llms.txt" + }, + { + "source": "recipes/laroux-app/public/robots.txt", + "target": "public/robots.txt" + }, + { + "source": "recipes/laroux-app/src/app/error.tsx", + "target": "src/app/error.tsx" + }, + { + "source": "recipes/laroux-app/src/app/layout.tsx", + "target": "src/app/layout.tsx" + }, + { + "source": "recipes/laroux-app/src/app/not-found.tsx", + "target": "src/app/not-found.tsx" + }, + { + "source": "recipes/laroux-app/src/app/routes/home/page.tsx", + "target": "src/app/routes/home/page.tsx" + }, + { + "source": "recipes/laroux-app/src/app/routes/index.ts", + "target": "src/app/routes/index.ts" + }, + { + "source": "recipes/laroux-app/src/app/styles/global.css", + "target": "src/app/styles/global.css" + } + ], + "dependencies": { + "npm": [ + "@eserstack/laroux", + "@eserstack/laroux-server", + "@eserstack/laroux-react" + ] + } + }, + { + "name": "go-service", + "description": "Go microservice with hexagonal architecture", + "language": "go", + "scale": "project", + "tags": ["go", "hexagonal", "microservice", "api"], + "variables": [ + { + "name": "module_name", + "description": "Go module path", + "default": "github.com/user/service" + } + ], + "files": [ + { + "source": "recipes/go-service/cmd/serve/main.go", + "target": "cmd/serve/main.go" + }, + { + "source": "recipes/go-service/pkg/api/adapters/appcontext/appcontext.go", + "target": "pkg/api/adapters/appcontext/appcontext.go" + }, + { "source": "recipes/go-service/Makefile", "target": "Makefile" }, + { "source": "recipes/go-service/README.md", "target": "README.md" }, + { + "source": "recipes/go-service/go.mod.tmpl", + "target": "go.mod.tmpl" + } + ] + }, + { + "name": "cf-workers-app", + "description": "Cloudflare Workers application", + "language": "typescript", + "scale": "project", + "tags": ["cloudflare", "workers", "edge", "serverless"], + "files": [ + { + "source": "recipes/cf-workers-app/.gitignore", + "target": ".gitignore" + }, + { "source": "recipes/cf-workers-app/.npmrc", "target": ".npmrc" }, + { + "source": "recipes/cf-workers-app/.prettierrc", + "target": ".prettierrc" + }, + { + "source": "recipes/cf-workers-app/package.json", + "target": "package.json" + }, + { + "source": "recipes/cf-workers-app/src/index.ts", + "target": "src/index.ts" + }, + { + "source": "recipes/cf-workers-app/test/index.spec.ts", + "target": "test/index.spec.ts" + }, + { + "source": "recipes/cf-workers-app/test/tsconfig.json", + "target": "test/tsconfig.json" + }, + { + "source": "recipes/cf-workers-app/tsconfig.json", + "target": "tsconfig.json" + }, + { + "source": "recipes/cf-workers-app/vitest.config.mts", + "target": "vitest.config.mts" + }, + { + "source": "recipes/cf-workers-app/worker-configuration.d.ts", + "target": "worker-configuration.d.ts" + }, + { + "source": "recipes/cf-workers-app/wrangler.toml", + "target": "wrangler.toml" + } + ], + "dependencies": { + "npm": ["@cloudflare/workers-types", "wrangler"] + } + }, + { + "name": "vite-app", + "description": "React + Vite single-page application", + "language": "typescript", + "scale": "project", + "tags": ["react", "vite", "spa", "frontend"], + "files": [ + { + "source": "recipes/vite-app/.eslintrc.cjs", + "target": ".eslintrc.cjs" + }, + { "source": "recipes/vite-app/.gitignore", "target": ".gitignore" }, + { "source": "recipes/vite-app/README.md", "target": "README.md" }, + { "source": "recipes/vite-app/index.html", "target": "index.html" }, + { + "source": "recipes/vite-app/package.json", + "target": "package.json" + }, + { + "source": "recipes/vite-app/public/vite.svg", + "target": "public/vite.svg" + }, + { "source": "recipes/vite-app/src/app.css", "target": "src/app.css" }, + { "source": "recipes/vite-app/src/app.tsx", "target": "src/app.tsx" }, + { + "source": "recipes/vite-app/src/assets/react.svg", + "target": "src/assets/react.svg" + }, + { + "source": "recipes/vite-app/src/index.css", + "target": "src/index.css" + }, + { + "source": "recipes/vite-app/src/main.tsx", + "target": "src/main.tsx" + }, + { + "source": "recipes/vite-app/src/vite-env.d.ts", + "target": "src/vite-env.d.ts" + }, + { + "source": "recipes/vite-app/tsconfig.app.json", + "target": "tsconfig.app.json" + }, + { + "source": "recipes/vite-app/tsconfig.json", + "target": "tsconfig.json" + }, + { + "source": "recipes/vite-app/tsconfig.node.json", + "target": "tsconfig.node.json" + }, + { + "source": "recipes/vite-app/vite.config.ts", + "target": "vite.config.ts" + } + ], + "dependencies": { + "npm": ["react", "react-dom", "vite", "@vitejs/plugin-react"] + } + }, + { + "name": "cool-lime-app", + "description": "Cool Lime framework application", + "language": "typescript", + "scale": "project", + "tags": ["lime", "deno", "web"], + "files": [ + { + "source": "recipes/cool-lime-app/build.ts", + "target": "build.ts" + }, + { "source": "recipes/cool-lime-app/main.ts", "target": "main.ts" }, + { + "source": "recipes/cool-lime-app/manifest.jsonc", + "target": "manifest.jsonc" + }, + { + "source": "recipes/cool-lime-app/manifest.yaml", + "target": "manifest.yaml" + }, + { + "source": "recipes/cool-lime-app/pkg/app/mod.tsx", + "target": "pkg/app/mod.tsx" + } + ] + }, + { + "name": "jsx-runtime-app", + "description": "JSX server-side rendering application", + "language": "typescript", + "scale": "project", + "tags": ["jsx", "ssr", "typescript"], + "files": [ + { "source": "recipes/jsx-runtime-app/.npmrc", "target": ".npmrc" }, + { + "source": "recipes/jsx-runtime-app/package.json", + "target": "package.json" + }, + { + "source": "recipes/jsx-runtime-app/root.test.tsx", + "target": "root.test.tsx" + }, + { + "source": "recipes/jsx-runtime-app/root.tsx", + "target": "root.tsx" + }, + { + "source": "recipes/jsx-runtime-app/tsconfig.json", + "target": "tsconfig.json" + } + ] + }, + { + "name": "vanilla-app", + "description": "Minimal TypeScript application", + "language": "typescript", + "scale": "project", + "tags": ["minimal", "typescript"], + "files": [ + { "source": "recipes/vanilla-app/main.ts", "target": "main.ts" }, + { + "source": "recipes/vanilla-app/package.json", + "target": "package.json" + } + ] + }, + { + "name": "ajan-lib", + "description": "Ajan core library utilities", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "core"], + "files": [ + { + "source": "lib/", + "target": "pkg/ajan/lib", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-results", + "description": "Ajan result type utilities", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "results"], + "requires": ["ajan-lib"], + "files": [ + { + "source": "results/", + "target": "pkg/ajan/results", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-types", + "description": "Ajan shared type definitions", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "types"], + "files": [ + { + "source": "types/", + "target": "pkg/ajan/types", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-di", + "description": "Ajan dependency injection container", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "di", "dependency-injection"], + "files": [ + { + "source": "di/", + "target": "pkg/ajan/di", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-configfx", + "description": "Ajan configuration management", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "config"], + "requires": ["ajan-lib"], + "files": [ + { + "source": "configfx/", + "target": "pkg/ajan/configfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-connfx", + "description": "Ajan database connection management", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "database", "connections"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "connfx/", + "target": "pkg/ajan/connfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-datafx", + "description": "Ajan data access layer", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "data", "repository"], + "requires": ["ajan-configfx", "ajan-connfx"], + "files": [ + { + "source": "datafx/", + "target": "pkg/ajan/datafx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-eventsfx", + "description": "Ajan event system", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "events", "pubsub"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "eventsfx/", + "target": "pkg/ajan/eventsfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-logfx", + "description": "Ajan structured logging", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "logging"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "logfx/", + "target": "pkg/ajan/logfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-metricsfx", + "description": "Ajan metrics and observability", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "metrics", "observability"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "metricsfx/", + "target": "pkg/ajan/metricsfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-tracesfx", + "description": "Ajan distributed tracing", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "tracing", "opentelemetry"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "tracesfx/", + "target": "pkg/ajan/tracesfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-httpfx", + "description": "Ajan HTTP server framework", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "http", "server"], + "requires": ["ajan-lib", "ajan-configfx", "ajan-results"], + "files": [ + { + "source": "httpfx/", + "target": "pkg/ajan/httpfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-grpcfx", + "description": "Ajan gRPC server framework", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "grpc"], + "requires": ["ajan-configfx", "ajan-connfx"], + "files": [ + { + "source": "grpcfx/", + "target": "pkg/ajan/grpcfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-httpclient", + "description": "Ajan HTTP client with retry and timeout", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "http", "client"], + "requires": ["ajan-configfx"], + "files": [ + { + "source": "httpclient/", + "target": "pkg/ajan/httpclient", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + }, + { + "name": "ajan-processfx", + "description": "Ajan process lifecycle management", + "language": "go", + "scale": "utility", + "tags": ["go", "ajan", "process", "lifecycle"], + "files": [ + { + "source": "processfx/", + "target": "pkg/ajan/processfx", + "kind": "folder", + "provider": "github" + } + ], + "dependencies": { + "go": ["github.com/eser/ajan"] + } + } + ] +} diff --git a/etc/templates/cf-workers-app/.gitignore b/.eser/recipes/cf-workers-app/.gitignore similarity index 100% rename from etc/templates/cf-workers-app/.gitignore rename to .eser/recipes/cf-workers-app/.gitignore diff --git a/etc/templates/cf-workers-app/.npmrc b/.eser/recipes/cf-workers-app/.npmrc similarity index 100% rename from etc/templates/cf-workers-app/.npmrc rename to .eser/recipes/cf-workers-app/.npmrc diff --git a/etc/templates/cf-workers-app/.prettierrc b/.eser/recipes/cf-workers-app/.prettierrc similarity index 100% rename from etc/templates/cf-workers-app/.prettierrc rename to .eser/recipes/cf-workers-app/.prettierrc diff --git a/.eser/recipes/cf-workers-app/package.json b/.eser/recipes/cf-workers-app/package.json new file mode 100644 index 000000000..b046ff2cf --- /dev/null +++ b/.eser/recipes/cf-workers-app/package.json @@ -0,0 +1,19 @@ +{ + "name": "cf-workers-app", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev", + "test": "vitest", + "cf-typegen": "wrangler types" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.13.0", + "@cloudflare/workers-types": "^4.20260313.1", + "typescript": "^7.0.2", + "vitest": "^4.1.0", + "wrangler": "^4.73.0" + } +} diff --git a/etc/templates/cf-workers-app/src/index.ts b/.eser/recipes/cf-workers-app/src/index.ts similarity index 100% rename from etc/templates/cf-workers-app/src/index.ts rename to .eser/recipes/cf-workers-app/src/index.ts diff --git a/etc/templates/cf-workers-app/test/index.spec.ts b/.eser/recipes/cf-workers-app/test/index.spec.ts similarity index 100% rename from etc/templates/cf-workers-app/test/index.spec.ts rename to .eser/recipes/cf-workers-app/test/index.spec.ts diff --git a/etc/templates/cf-workers-app/test/tsconfig.json b/.eser/recipes/cf-workers-app/test/tsconfig.json similarity index 100% rename from etc/templates/cf-workers-app/test/tsconfig.json rename to .eser/recipes/cf-workers-app/test/tsconfig.json diff --git a/etc/templates/cf-workers-app/tsconfig.json b/.eser/recipes/cf-workers-app/tsconfig.json similarity index 100% rename from etc/templates/cf-workers-app/tsconfig.json rename to .eser/recipes/cf-workers-app/tsconfig.json diff --git a/etc/templates/cf-workers-app/vitest.config.mts b/.eser/recipes/cf-workers-app/vitest.config.mts similarity index 100% rename from etc/templates/cf-workers-app/vitest.config.mts rename to .eser/recipes/cf-workers-app/vitest.config.mts diff --git a/etc/templates/cf-workers-app/worker-configuration.d.ts b/.eser/recipes/cf-workers-app/worker-configuration.d.ts similarity index 100% rename from etc/templates/cf-workers-app/worker-configuration.d.ts rename to .eser/recipes/cf-workers-app/worker-configuration.d.ts diff --git a/etc/templates/cf-workers-app/wrangler.toml b/.eser/recipes/cf-workers-app/wrangler.toml similarity index 100% rename from etc/templates/cf-workers-app/wrangler.toml rename to .eser/recipes/cf-workers-app/wrangler.toml diff --git a/.eser/recipes/fp-pipe/pipe.ts b/.eser/recipes/fp-pipe/pipe.ts new file mode 100644 index 000000000..917bf5652 --- /dev/null +++ b/.eser/recipes/fp-pipe/pipe.ts @@ -0,0 +1,50 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +/** + * Functional pipe utility — compose functions left to right. + * + * @module + */ + +type UnaryFn = (input: T) => R; + +/** + * Pipe a value through a sequence of functions, left to right. + * + * ```typescript + * const result = pipe( + * 5, + * (x) => x * 2, + * (x) => x + 1, + * (x) => `Result: ${x}`, + * ); + * // result === "Result: 11" + * ``` + */ +const pipe = ( + initial: T, + ...fns: ReadonlyArray> +): unknown => { + return fns.reduce((acc, fn) => fn(acc), initial as unknown); +}; + +/** + * Create a composed function from a sequence of functions (left to right). + * + * ```typescript + * const transform = compose( + * (x: number) => x * 2, + * (x: number) => x + 1, + * ); + * transform(5); // 11 + * ``` + */ +const compose = ( + ...fns: ReadonlyArray> +): UnaryFn => { + return (input: T) => pipe(input, ...fns); +}; + +export { compose, pipe }; + +export type { UnaryFn }; diff --git a/etc/templates/go-service/Makefile b/.eser/recipes/go-service/Makefile similarity index 100% rename from etc/templates/go-service/Makefile rename to .eser/recipes/go-service/Makefile diff --git a/etc/templates/go-service/README.md b/.eser/recipes/go-service/README.md similarity index 100% rename from etc/templates/go-service/README.md rename to .eser/recipes/go-service/README.md diff --git a/etc/templates/go-service/cmd/serve/main.go b/.eser/recipes/go-service/cmd/serve/main.go similarity index 100% rename from etc/templates/go-service/cmd/serve/main.go rename to .eser/recipes/go-service/cmd/serve/main.go diff --git a/.eser/recipes/go-service/go.mod.tmpl b/.eser/recipes/go-service/go.mod.tmpl new file mode 100644 index 000000000..438b23e4f --- /dev/null +++ b/.eser/recipes/go-service/go.mod.tmpl @@ -0,0 +1,3 @@ +module github.com/eser/{{SERVICE_NAME}} + +go 1.24 diff --git a/etc/templates/go-service/pkg/api/adapters/appcontext/appcontext.go b/.eser/recipes/go-service/pkg/api/adapters/appcontext/appcontext.go similarity index 100% rename from etc/templates/go-service/pkg/api/adapters/appcontext/appcontext.go rename to .eser/recipes/go-service/pkg/api/adapters/appcontext/appcontext.go diff --git a/etc/templates/jsx-runtime-app/.npmrc b/.eser/recipes/jsx-runtime-app/.npmrc similarity index 100% rename from etc/templates/jsx-runtime-app/.npmrc rename to .eser/recipes/jsx-runtime-app/.npmrc diff --git a/.eser/recipes/jsx-runtime-app/package.json b/.eser/recipes/jsx-runtime-app/package.json new file mode 100644 index 000000000..bf1f25d41 --- /dev/null +++ b/.eser/recipes/jsx-runtime-app/package.json @@ -0,0 +1,15 @@ +{ + "name": "jsx-runtime-app", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@eserstack/jsx-runtime": "workspace:*", + "@eserstack/standards": "workspace:*" + }, + "devDependencies": { + "@std/assert": "npm:@jsr/std__assert@^1.0.16", + "@std/testing": "npm:@jsr/std__testing@^1.0.16", + "@types/react": "^19.2.18" + } +} diff --git a/etc/templates/jsx-runtime-app/root.test.tsx b/.eser/recipes/jsx-runtime-app/root.test.tsx similarity index 92% rename from etc/templates/jsx-runtime-app/root.test.tsx rename to .eser/recipes/jsx-runtime-app/root.test.tsx index 03c028c6b..5e4f87713 100644 --- a/etc/templates/jsx-runtime-app/root.test.tsx +++ b/.eser/recipes/jsx-runtime-app/root.test.tsx @@ -2,7 +2,7 @@ import * as assert from "@std/assert"; // import * as mock from "@std/testing/mock"; -// import * as jsxRuntimeInternals from "@eser/jsx-runtime/internals"; +// import * as jsxRuntimeInternals from "@eserstack/jsx-runtime/internals"; import * as root from "./root.tsx"; Deno.test("should return the root component", () => { diff --git a/etc/templates/jsx-runtime-app/root.tsx b/.eser/recipes/jsx-runtime-app/root.tsx similarity index 100% rename from etc/templates/jsx-runtime-app/root.tsx rename to .eser/recipes/jsx-runtime-app/root.tsx diff --git a/.eser/recipes/jsx-runtime-app/tsconfig.json b/.eser/recipes/jsx-runtime-app/tsconfig.json new file mode 100644 index 000000000..3005f2516 --- /dev/null +++ b/.eser/recipes/jsx-runtime-app/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "@eserstack/jsx-runtime", + "jsxImportSourceTypes": "@types/react", + "strict": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/etc/templates/laroux-app/.gitignore b/.eser/recipes/laroux-app/.gitignore similarity index 100% rename from etc/templates/laroux-app/.gitignore rename to .eser/recipes/laroux-app/.gitignore diff --git a/etc/templates/laroux-app/.npmrc b/.eser/recipes/laroux-app/.npmrc similarity index 100% rename from etc/templates/laroux-app/.npmrc rename to .eser/recipes/laroux-app/.npmrc diff --git a/etc/templates/laroux-app/laroux.config.ts b/.eser/recipes/laroux-app/laroux.config.ts similarity index 92% rename from etc/templates/laroux-app/laroux.config.ts rename to .eser/recipes/laroux-app/laroux.config.ts index d75f27bab..044846ccc 100644 --- a/etc/templates/laroux-app/laroux.config.ts +++ b/.eser/recipes/laroux-app/laroux.config.ts @@ -1,4 +1,4 @@ -import type { UserConfig } from "@eser/laroux-core/config/schema"; +import type { UserConfig } from "@eserstack/laroux-core/config/schema"; const config: UserConfig = { // server: { diff --git a/.eser/recipes/laroux-app/package.json b/.eser/recipes/laroux-app/package.json new file mode 100644 index 000000000..c5d21b8d0 --- /dev/null +++ b/.eser/recipes/laroux-app/package.json @@ -0,0 +1,23 @@ +{ + "name": "@test/laroux-app", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "deno run --allow-all --unstable-bundle jsr:@eserstack/cli laroux dev", + "build": "deno run --allow-all --unstable-bundle jsr:@eserstack/cli laroux build", + "serve": "deno run --allow-all --unstable-bundle jsr:@eserstack/cli laroux serve" + }, + "dependencies": { + "@eserstack/laroux-server": "npm:@jsr/eser__laroux-server@^4.0.43", + "@eserstack/laroux": "npm:@jsr/eser__laroux@^4.0.43", + "@eserstack/laroux-react": "npm:@jsr/eser__laroux-react@^4.0.43", + "@eserstack/logging": "npm:@jsr/eser__logging@^4.0.43", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "tailwindcss": "^4.3.3" + }, + "devDependencies": { + "@types/react": "^19.2.18" + } +} diff --git a/etc/templates/laroux-app/public/favicon.svg b/.eser/recipes/laroux-app/public/favicon.svg similarity index 100% rename from etc/templates/laroux-app/public/favicon.svg rename to .eser/recipes/laroux-app/public/favicon.svg diff --git a/etc/templates/laroux-app/public/llms.txt b/.eser/recipes/laroux-app/public/llms.txt similarity index 100% rename from etc/templates/laroux-app/public/llms.txt rename to .eser/recipes/laroux-app/public/llms.txt diff --git a/etc/templates/laroux-app/public/robots.txt b/.eser/recipes/laroux-app/public/robots.txt similarity index 100% rename from etc/templates/laroux-app/public/robots.txt rename to .eser/recipes/laroux-app/public/robots.txt diff --git a/etc/templates/laroux-app/src/app/error.tsx b/.eser/recipes/laroux-app/src/app/error.tsx similarity index 100% rename from etc/templates/laroux-app/src/app/error.tsx rename to .eser/recipes/laroux-app/src/app/error.tsx diff --git a/etc/templates/laroux-app/src/app/layout.tsx b/.eser/recipes/laroux-app/src/app/layout.tsx similarity index 100% rename from etc/templates/laroux-app/src/app/layout.tsx rename to .eser/recipes/laroux-app/src/app/layout.tsx diff --git a/etc/templates/laroux-app/src/app/not-found.tsx b/.eser/recipes/laroux-app/src/app/not-found.tsx similarity index 100% rename from etc/templates/laroux-app/src/app/not-found.tsx rename to .eser/recipes/laroux-app/src/app/not-found.tsx diff --git a/etc/templates/laroux-app/src/app/routes/home/page.tsx b/.eser/recipes/laroux-app/src/app/routes/home/page.tsx similarity index 100% rename from etc/templates/laroux-app/src/app/routes/home/page.tsx rename to .eser/recipes/laroux-app/src/app/routes/home/page.tsx diff --git a/etc/templates/laroux-app/src/app/routes/index.ts b/.eser/recipes/laroux-app/src/app/routes/index.ts similarity index 87% rename from etc/templates/laroux-app/src/app/routes/index.ts rename to .eser/recipes/laroux-app/src/app/routes/index.ts index 398f2fb11..568219843 100644 --- a/etc/templates/laroux-app/src/app/routes/index.ts +++ b/.eser/recipes/laroux-app/src/app/routes/index.ts @@ -2,7 +2,7 @@ // NOTE: Routes are auto-generated at build time to dist/server/_generated-routes.ts // The server loads directly from that file - this file is for manual overrides only -import type { RouteDefinition } from "@eser/laroux-core/router"; +import type { RouteDefinition } from "@eserstack/laroux-core/router"; /** * Manual route overrides diff --git a/etc/templates/laroux-app/src/app/styles/global.css b/.eser/recipes/laroux-app/src/app/styles/global.css similarity index 100% rename from etc/templates/laroux-app/src/app/styles/global.css rename to .eser/recipes/laroux-app/src/app/styles/global.css diff --git a/etc/templates/laroux-app/tsconfig.json b/.eser/recipes/laroux-app/tsconfig.json similarity index 100% rename from etc/templates/laroux-app/tsconfig.json rename to .eser/recipes/laroux-app/tsconfig.json diff --git a/.eser/recipes/laroux-runtime-app/build.ts b/.eser/recipes/laroux-runtime-app/build.ts new file mode 100644 index 000000000..6c76aee11 --- /dev/null +++ b/.eser/recipes/laroux-runtime-app/build.ts @@ -0,0 +1,11 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +import * as collector from "@eserstack/collector"; + +await collector.buildManifest( + Deno.stdout.writable, + { + baseDir: import.meta.dirname ?? ".", + globFilter: "pkg/**/*", + }, +); diff --git a/.eser/recipes/laroux-runtime-app/main.ts b/.eser/recipes/laroux-runtime-app/main.ts new file mode 100644 index 000000000..4854951e2 --- /dev/null +++ b/.eser/recipes/laroux-runtime-app/main.ts @@ -0,0 +1,28 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +import * as runtime from "@eserstack/laroux-runtime"; + +// TODO(@eser): laroux-runtime traverses all files in the directory and looks for +// exported function named "metadata". when it finds it, it calls it and +// registers the result into a manifest. + +// export const metadata = (registry) => { +// registry.addReactRoot("/hello", ); +// registry.addHook("/hook", fn); +// registry.addDb("postgres", "postgres://localhost:5432"); +// registry.addStaticAsset("/static", "./static"); +// ... +// }; + +export const instance = runtime.createRuntime() + .setBaseUrl(import.meta.url) + .loadManifest(); +// .setOptions(options) // (set defaults ... or load from file?) +// .loadEnvironment() // (deserialize options from env, dev mode vs) + +if (import.meta.main) { + await instance.execute(); +} + +console.log(instance.state.baseUrl); +console.log(instance.state.manifests); diff --git a/etc/templates/cool-lime-app/manifest.jsonc b/.eser/recipes/laroux-runtime-app/manifest.jsonc similarity index 100% rename from etc/templates/cool-lime-app/manifest.jsonc rename to .eser/recipes/laroux-runtime-app/manifest.jsonc diff --git a/etc/templates/cool-lime-app/manifest.yaml b/.eser/recipes/laroux-runtime-app/manifest.yaml similarity index 100% rename from etc/templates/cool-lime-app/manifest.yaml rename to .eser/recipes/laroux-runtime-app/manifest.yaml diff --git a/etc/templates/cool-lime-app/pkg/app/mod.tsx b/.eser/recipes/laroux-runtime-app/pkg/app/mod.tsx similarity index 100% rename from etc/templates/cool-lime-app/pkg/app/mod.tsx rename to .eser/recipes/laroux-runtime-app/pkg/app/mod.tsx diff --git a/.eser/recipes/library-pkg/README.md b/.eser/recipes/library-pkg/README.md new file mode 100644 index 000000000..d8accc65d --- /dev/null +++ b/.eser/recipes/library-pkg/README.md @@ -0,0 +1,15 @@ +# @scope/package-name + +A Deno library package. + +## Usage + +```typescript +import * as pkg from "@scope/package-name"; + +pkg.greet("World"); // "Hello, World!" +``` + +## License + +Apache-2.0 diff --git a/.eser/recipes/library-pkg/deno.json b/.eser/recipes/library-pkg/deno.json new file mode 100644 index 000000000..7c3360b70 --- /dev/null +++ b/.eser/recipes/library-pkg/deno.json @@ -0,0 +1,9 @@ +{ + "name": "@scope/package-name", + "version": "0.1.0", + "exports": "./mod.ts", + "imports": { + "@std/assert": "jsr:@std/assert@^1.0.16", + "@std/testing": "jsr:@std/testing@^1.0.16" + } +} diff --git a/.eser/recipes/library-pkg/mod.test.ts b/.eser/recipes/library-pkg/mod.test.ts new file mode 100644 index 000000000..9ae7fd63f --- /dev/null +++ b/.eser/recipes/library-pkg/mod.test.ts @@ -0,0 +1,10 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +import * as assert from "@std/assert"; +import * as mod from "./mod.ts"; + +Deno.test("greet returns greeting message", () => { + const result = mod.greet("World"); + + assert.assertEquals(result, "Hello, World!"); +}); diff --git a/.eser/recipes/library-pkg/mod.ts b/.eser/recipes/library-pkg/mod.ts new file mode 100644 index 000000000..3d7cefe56 --- /dev/null +++ b/.eser/recipes/library-pkg/mod.ts @@ -0,0 +1,13 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. + +/** + * Main module entry point. + * + * @module + */ + +const greet = (name: string): string => { + return `Hello, ${name}!`; +}; + +export { greet }; diff --git a/.eser/recipes/vanilla-app/main.ts b/.eser/recipes/vanilla-app/main.ts new file mode 100644 index 000000000..d46b51491 --- /dev/null +++ b/.eser/recipes/vanilla-app/main.ts @@ -0,0 +1,11 @@ +// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license. +import { runtime } from "@eserstack/standards/cross-runtime"; + +export function main() { + console.log(runtime.name); +} + +// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts +if (import.meta.main) { + main(); +} diff --git a/.eser/recipes/vanilla-app/package.json b/.eser/recipes/vanilla-app/package.json new file mode 100644 index 000000000..d5c7f151b --- /dev/null +++ b/.eser/recipes/vanilla-app/package.json @@ -0,0 +1,9 @@ +{ + "name": "vanilla-app", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@eserstack/standards": "workspace:*" + } +} diff --git a/etc/templates/vite-app/.eslintrc.cjs b/.eser/recipes/vite-app/.eslintrc.cjs similarity index 100% rename from etc/templates/vite-app/.eslintrc.cjs rename to .eser/recipes/vite-app/.eslintrc.cjs diff --git a/etc/templates/vite-app/.gitignore b/.eser/recipes/vite-app/.gitignore similarity index 100% rename from etc/templates/vite-app/.gitignore rename to .eser/recipes/vite-app/.gitignore diff --git a/etc/templates/vite-app/README.md b/.eser/recipes/vite-app/README.md similarity index 100% rename from etc/templates/vite-app/README.md rename to .eser/recipes/vite-app/README.md diff --git a/etc/templates/vite-app/index.html b/.eser/recipes/vite-app/index.html similarity index 100% rename from etc/templates/vite-app/index.html rename to .eser/recipes/vite-app/index.html diff --git a/.eser/recipes/vite-app/package.json b/.eser/recipes/vite-app/package.json new file mode 100644 index 000000000..46e981a43 --- /dev/null +++ b/.eser/recipes/vite-app/package.json @@ -0,0 +1,28 @@ +{ + "name": "vite-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "typescript": "^7.0.2", + "vite": "^8.0.7" + } +} diff --git a/etc/templates/vite-app/public/vite.svg b/.eser/recipes/vite-app/public/vite.svg similarity index 100% rename from etc/templates/vite-app/public/vite.svg rename to .eser/recipes/vite-app/public/vite.svg diff --git a/etc/templates/vite-app/src/app.css b/.eser/recipes/vite-app/src/app.css similarity index 100% rename from etc/templates/vite-app/src/app.css rename to .eser/recipes/vite-app/src/app.css diff --git a/etc/templates/vite-app/src/app.tsx b/.eser/recipes/vite-app/src/app.tsx similarity index 100% rename from etc/templates/vite-app/src/app.tsx rename to .eser/recipes/vite-app/src/app.tsx diff --git a/etc/templates/vite-app/src/assets/react.svg b/.eser/recipes/vite-app/src/assets/react.svg similarity index 100% rename from etc/templates/vite-app/src/assets/react.svg rename to .eser/recipes/vite-app/src/assets/react.svg diff --git a/etc/templates/vite-app/src/index.css b/.eser/recipes/vite-app/src/index.css similarity index 100% rename from etc/templates/vite-app/src/index.css rename to .eser/recipes/vite-app/src/index.css diff --git a/etc/templates/vite-app/src/main.tsx b/.eser/recipes/vite-app/src/main.tsx similarity index 100% rename from etc/templates/vite-app/src/main.tsx rename to .eser/recipes/vite-app/src/main.tsx diff --git a/etc/templates/vite-app/src/vite-env.d.ts b/.eser/recipes/vite-app/src/vite-env.d.ts similarity index 100% rename from etc/templates/vite-app/src/vite-env.d.ts rename to .eser/recipes/vite-app/src/vite-env.d.ts diff --git a/etc/templates/vite-app/tsconfig.app.json b/.eser/recipes/vite-app/tsconfig.app.json similarity index 100% rename from etc/templates/vite-app/tsconfig.app.json rename to .eser/recipes/vite-app/tsconfig.app.json diff --git a/etc/templates/vite-app/tsconfig.json b/.eser/recipes/vite-app/tsconfig.json similarity index 100% rename from etc/templates/vite-app/tsconfig.json rename to .eser/recipes/vite-app/tsconfig.json diff --git a/etc/templates/vite-app/tsconfig.node.json b/.eser/recipes/vite-app/tsconfig.node.json similarity index 100% rename from etc/templates/vite-app/tsconfig.node.json rename to .eser/recipes/vite-app/tsconfig.node.json diff --git a/etc/templates/vite-app/vite.config.ts b/.eser/recipes/vite-app/vite.config.ts similarity index 100% rename from etc/templates/vite-app/vite.config.ts rename to .eser/recipes/vite-app/vite.config.ts diff --git a/.gitattributes b/.gitattributes index 23df35871..e502b2cb1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,7 +5,7 @@ # Treat lock files as binary (don't diff) *.lock binary go.sum linguist-generated -deno.lock linguist-generated +pnpm-lock.yaml linguist-generated # Prevent lock files from skewing GitHub language statistics *.lock linguist-vendored diff --git a/.github/ARCHITECTURE.md b/.github/ARCHITECTURE.md index c7c683d65..2f14d2f96 100644 --- a/.github/ARCHITECTURE.md +++ b/.github/ARCHITECTURE.md @@ -6,35 +6,43 @@ This guide explains how the eserstack codebase is organized. ``` stack/ -├── apps/ -│ └── services/ # Go services (independent git-tag versioning) -│ ├── cmd/ # Application entrypoints -│ ├── pkg/api/ # Core application logic -│ │ ├── adapters/ # External integrations (composition root) -│ │ └── business/ # Pure business logic (no external deps) -│ ├── go.mod, go.sum # Go module definition + tool directives -│ ├── Makefile # Go-specific build targets -│ └── .golangci.yaml # Go linting rules -├── pkg/ # All publishable TS packages (unified versioning) -│ ├── @eser/ # Core packages (29 packages, published to JSR) -│ └── @cool/ # Community packages (2 packages, published to JSR) +├── cmd/ +│ └── noskills-server/ # The only Go executable (a daemon) +├── pkg/ +│ ├── ajan/ # Go library (github.com/eser/stack/pkg/ajan) +│ │ ├── acpfx/ # Agent Client Protocol: client, agent, in-process shim +│ │ ├── aifx/ # AI providers (reaches acpfx/shim in-process) +│ │ ├── noskillsserverfx/ # The daemon: sessions, ledger, workers +│ │ ├── httpclient/ # HTTP client with circuit breaker +│ │ ├── logfx/ # Structured logging (also metrics + tracing) +│ │ ├── configfx/ # Configuration loading +│ │ └── ... # Other Go utilities +│ └── @eserstack/ # TypeScript packages (published to JSR + npm) ├── etc/ -│ ├── templates/ # Project starter templates (library-pkg, go-service) -│ ├── scripts/ # Automation scripts (version-bump, release-notes) +│ ├── scripts/ # install.sh, install.ps1, manifest + formula generators +│ ├── temp/ # Build output (gitignored) │ └── coverage/ # Test coverage output (gitignored) -├── docs/ # Generated HTML documentation -├── .claude/ -│ ├── skills/ # Claude Code skill definitions (14 skills) -│ └── hooks/ # Claude Code PostToolUse hooks +├── docs/ +│ ├── api/ # Generated HTML reference (deno task docs) +│ ├── adr/ # Architecture decision records (hand-written) +│ └── schema/ # JSON schemas (hand-written) +├── .eser/ +│ ├── recipes/ # Project templates (9) +│ ├── recipes.json # Recipe registry +│ └── manifest.yml # Workflows, validators, scripts +├── .agents/ +│ ├── skills/ # Agent skill definitions (15 skills) +│ ├── hooks/ # Claude Code PostToolUse hooks +│ └── settings.json # Claude Code project settings +├── .claude -> .agents # Symlink so Claude Code reads the same tree ├── .github/ -│ ├── workflows/ # CI/CD pipelines (7 workflows) +│ ├── workflows/ # CI/CD pipelines (6 workflows) │ ├── ARCHITECTURE.md # This file │ ├── pr-labeler.yml # PR auto-labeling rules │ └── issue-labels.yml # Issue label definitions ├── deno.json # Root Deno config (lint, format, excludes) ├── package.json # npm workspace root + deno task scripts -├── CLAUDE.md # AI development guidelines (symlink → AGENTS.md) -├── AGENTS.md # Multi-agent coordination rules +├── AGENTS.md # AI development guidelines └── CHANGELOG.md # Release history ``` @@ -42,82 +50,144 @@ stack/ ``` Layer 0 — Foundation (no internal deps) -├── @eser/standards # Cross-platform abstractions (Runtime interface) -├── @eser/primitives # Primitive data structures -└── @eser/directives # Ground rules/directives +├── @eserstack/standards # Cross-platform abstractions (Runtime interface) +├── @eserstack/primitives # Primitive data structures +└── @eserstack/directives # Ground rules/directives Layer 1 — Core Utilities (depend on Layer 0) -├── @eser/fp # Functional programming (116+ modules) -├── @eser/crypto # Cryptographic utilities -├── @eser/cs # Computer science utilities -└── @eser/parsing # String/stream parsing +├── @eserstack/fp # Functional programming (116+ modules) +├── @eserstack/crypto # Cryptographic utilities +└── @eserstack/parsing # String/stream parsing Layer 2 — Infrastructure (depend on Layers 0-1) -├── @eser/di # Dependency injection container -├── @eser/events # Event system -├── @eser/config # Configuration management -├── @eser/cache # Caching utilities -├── @eser/logging # Logging system -├── @eser/http # HTTP utilities -├── @eser/functions # Function utilities -├── @eser/testing # Testing utilities -├── @eser/writer # Output/file writing -├── @eser/shell # Shell interaction -├── @eser/collector # Data collection -└── @eser/codebase # Codebase analysis/validation +├── @eserstack/di # Dependency injection container +├── @eserstack/events # Event system +├── @eserstack/config # Configuration management +├── @eserstack/cache # Caching utilities +├── @eserstack/logging # Logging system +├── @eserstack/http # HTTP utilities +├── @eserstack/functions # Function utilities +├── @eserstack/testing # Testing utilities +├── @eserstack/formats # Bidirectional format conversion (JSON, YAML, CSV, TOML, JSONL) +├── @eserstack/streams # Universal I/O streaming with composable middleware +├── @eserstack/shell # Shell interaction +├── @eserstack/collector # Data collection +├── @eserstack/cs # Config storage (Kubernetes ConfigMap/Secret sync) +└── @eserstack/codebase # Codebase analysis/validation Layer 3 — Framework (depend on Layers 0-2) -├── @eser/jsx-runtime # Custom JSX runtime -├── @eser/laroux # Framework core -├── @eser/laroux-server # Server-side runtime -├── @eser/laroux-bundler # Bundler adapters -├── @eser/laroux-react # React integration -├── @eser/bundler # Bundling system (esbuild WASM) -└── @eser/app-runtime # Runtime abstraction - -Layer 4 — Application (depend on all layers) -└── @eser/cli # Main CLI tool (published to npm as `eser`) - -Community -├── @cool/cli # Cool CLI -└── @cool/lime # Lime utilities - -Go Services (independent versioning, apps/services/) -└── cmd/serve # HTTP server (hexagonal architecture) +├── @eserstack/jsx-runtime # Custom JSX runtime +├── @eserstack/laroux # Framework core +├── @eserstack/laroux-server # Server-side runtime +├── @eserstack/laroux-bundler # Bundler adapters +├── @eserstack/laroux-react # React integration +├── @eserstack/laroux-runtime # Application runtime (manifest loading, dev mode) +├── @eserstack/bundler # Bundling system (esbuild WASM) +└── @eserstack/app-runtime # Runtime abstraction + +Layer 4 — Shipped binaries (depend on all layers) +├── @eserstack/cli # `eser` — the full CLI +├── @eserstack/noskills # `noskills` — the same module `eser` mounts, re-rooted +└── @eserstack/laroux-server # `laroux` — likewise + +All three are `deno compile` entry points over the SAME module objects; see +BINARIES in pkg/@eserstack/cli/scripts/compile.ts. They are not forks. + +Go (module github.com/eser/stack, root go.mod) +├── cmd/noskills-server # `noskills-server` — the daemon, the only Go executable +└── pkg/ajan # Go library (acpfx, aifx, httpclient, logfx, configfx...) ``` +## Agent execution (ACP) + +Agent sessions speak the Agent Client Protocol, and the shim that translates ACP +to a vendor CLI is **linked in, not spawned**: + +``` +aifx / noskillsserverfx + │ acpfx.InProcess (net.Pipe — no subprocess, nothing on PATH) + ▼ +pkg/ajan/acpfx/shim + │ + ▼ +claude --print --output-format stream-json (or kiro / opencode) +``` + +The shim is Go code compiled into the same binary as its callers, so it is +reached in process rather than over a subprocess pipe: spawning it would mean +shipping, installing and PATH-resolving a program to talk to a struct already in +memory. `acpfx.Spawn` is for agents that genuinely are other programs +(`gemini --acp`, `claude-agent-acp`), selected with `NOSKILLS_ACP_COMMAND`. + +Only mux sessions still spawn a TypeScript worker, because a terminal pane's +content channel is VT bytes rather than protocol messages. That worker runs +under whichever JS runtime is present — Deno, Bun, or Node ≥ 26, which strips +types natively. + +## Shared `system` command tree + +`pkg/@eserstack/codebase/cli-system/` owns `install`, `uninstall`, `update`, +`completions`, `version`, `doctor` and `info`, parameterised by a `CliApp` so +each binary gets a tree describing itself. `attachStandardCommands` binds it to +a binary's ROOT command — which is what gives `noskills version` without +`eser noskills version`, since `eser` mounts the very same module object the +standalone binary re-roots. + ## Build Pipeline +One pipeline builds and releases the whole family. GoReleaser and a separate +`release.yml` used to publish the Go binaries independently, which is why +releases once carried either the TypeScript artifacts or the Go ones and never +both; both are gone. + +An ordinary push runs the Integrity Pipeline and stops there. A release starts +when an annotated `v*.*.*` tag reaches origin — and the CLI pushes that tag, not +CI. A tag pushed with a workflow's own `GITHUB_TOKEN` dispatches nothing +(GitHub's recursion guard), so a tag created in CI would sit in the repository +publishing nothing; pushing it under the developer's own credentials is what +starts the release run. + ``` Developer runs: eser codebase release patch ├─ versions.ts (bump VERSION + sync packages) ├─ changelog-gen.ts (auto-generate CHANGELOG) - └─ git commit + git push (commit only, no tag) - │ - ▼ -┌─ PUSH TO MAIN ────────────────────────┐ -│ build.yml (Integrity Pipeline) │ -│ ├─ integration.yml (reusable) │ -│ │ └─ deno task cli ok │ -│ └─ tag-release (if release commit │ -│ && integration passed) │ -│ └─ creates + pushes v*.*.* tag │ -└───────────────────────────────────────┘ - │ tag push - ▼ -┌─ TAG v*.*.* ──────────────────────────┐ -│ deployment.yml (Deployment Pipeline) │ -│ ├─ version-check (tag == VERSION) │ -│ ├─ smoke-test (node dist/eser.js) │ -│ │ └─ uploads npm bundle artifact │ -│ └─ publish (JSR + npm + summary) │ -│ └─ downloads npm bundle artifact│ -└───────────────────────────────────────┘ - -┌─ TAG v* ──────────────────────────────┐ -│ release-notes-sync.yml │ -│ └─ CHANGELOG.md → GitHub Release │ -└───────────────────────────────────────┘ + ├─ git commit "chore(codebase): release v" + ├─ git push ──▶ PUSH TO BRANCH + └─ git tag -a v + push ──▶ TAG PUSH v*.*.* + +┌─ PUSH TO BRANCH ────────────────────────────────────────────┐ +│ build.yml (Integrity Pipeline) — every ordinary push │ +│ ├─ validate deno task cli ok (Deno + Go) │ +│ ├─ cross-runtime-test deno / node / bun × linux / macos │ +│ └─ windows-smoke compiles the Windows artifacts and │ +│ runs etc/scripts/install.ps1 │ +└─────────────────────────────────────────────────────────────┘ + +┌─ TAG PUSH v*.*.* ───────────────────────────────────────────┐ +│ build.yml (same file) — the release run │ +│ validate │ +│ └─ release-gate tag == VERSION, and CHANGELOG.md has │ +│ │ the matching section — both checked │ +│ │ BEFORE anything is published │ +│ ├─ smoke-test │ +│ │ └─ npm-no-deno-test │ +│ │ └─ publish JSR + npm │ +│ │ └─ release-notes changelog section │ +│ │ → GitHub Release │ +│ └─ build-ajan-darwin │ +│ └─ compile-binaries eser, noskills, laroux │ +│ │ (deno compile) + noskills-server (go) │ +│ │ → -v-.tar.gz │ +│ │ and SHA256SUMS.txt │ +│ ├─ publish-ajan @eserstack/ajan platform │ +│ │ packages (leaf) │ +│ └─ upload-assets cosign signatures + │ +│ │ SHA256SUMS. Also needs release-notes: │ +│ │ gh cannot attach assets to a Release │ +│ │ that does not exist yet. │ +│ ├─ update-homebrew → eser/tap │ +│ └─ update-nix-hashes → flake.nix │ +└─────────────────────────────────────────────────────────────┘ ┌─ OTHER ────────────────────────────────┐ │ pr-labeler.yml (PRs only) │ @@ -125,6 +195,14 @@ Developer runs: eser codebase release patch └────────────────────────────────────────┘ ``` +`eser codebase rerelease` deletes the tag and recreates it at HEAD, re-firing +the whole run; `eser codebase unrelease` deletes the tag and the GitHub Release, +leaving the release commit in history. Neither is safe once a registry has +accepted the version — JSR is immutable (yank only) and npm answers a republish +with 403 — so after `publish` or `publish-ajan` has written anything, re-run the +failed jobs for an infrastructure error or cut a new patch version for a code +fix. + ## Design Principles ### Hexagonal Architecture @@ -137,9 +215,9 @@ Each package follows a double-layered structure: ### Portability -The `@eser/standards/runtime` module provides a cross-platform abstraction -layer, enabling code to run on Deno, browsers, Supabase, Netlify, AWS Lambda, -and Cloudflare Workers. +The `@eserstack/standards/cross-runtime` module provides a cross-platform +abstraction layer, enabling code to run on Deno, browsers, Supabase, Netlify, +AWS Lambda, and Cloudflare Workers. ### Functional Programming First @@ -150,5 +228,5 @@ and Cloudflare Workers. ### Dependency Injection -The `@eser/di` package provides a container for managing dependencies, enhancing -testability by allowing mock injection without modifying source code. +The `@eserstack/di` package provides a container for managing dependencies, +enhancing testability by allowing mock injection without modifying source code. diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 9fae8aa83..8fb9ba439 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ # Code of Conduct -See [@eser/directives](./pkg/directives/README.md) for our sets of rules and -recommendations we follow. +See [@eserstack/directives](./pkg/directives/README.md) for our sets of rules +and recommendations we follow. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3218f9982..31b3131a2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -12,9 +12,9 @@ in a pull request. ### Code of Conduct This project and everyone participating in it is governed by the -[@eser/directives](./pkg/directives/README.md). By participating, you are -expected to uphold this code. Please report unacceptable behavior as in -specified in the link. +[@eserstack/directives](../pkg/@eserstack/directives/README.md). By +participating, you are expected to uphold this code. Please report unacceptable +behavior as in specified in the link. ### Technical Requirements @@ -53,7 +53,7 @@ from: ## Submitting a Pull Request -- Adhere to [@eser/directives](./pkg/directives/README.md) +- Adhere to [@eserstack/directives](../pkg/@eserstack/directives/README.md) - Fork the repo - Make your changes in a new branch - Provide tests for your changes, particularly for new features or fixes diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index e8e091c84..5695b04ef 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -22,7 +22,7 @@ body: label: Steps to Reproduce description: How can we reproduce the issue? placeholder: | - 1. Install package with `deno add @eser/...` + 1. Install package with `deno add @eserstack/...` 2. Call function `...` 3. See error validations: diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml index 8740454f7..ee06c3d17 100644 --- a/.github/ISSUE_TEMPLATE/02_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -36,15 +36,15 @@ body: label: Related Package description: Which package does this relate to? options: - - "@eser/fp" - - "@eser/di" - - "@eser/events" - - "@eser/config" - - "@eser/cli" - - "@eser/standards" - - "@eser/laroux" - - "@eser/shell" - - "@eser/http" + - "@eserstack/fp" + - "@eserstack/di" + - "@eserstack/events" + - "@eserstack/config" + - "@eserstack/cli" + - "@eserstack/standards" + - "@eserstack/laroux" + - "@eserstack/shell" + - "@eserstack/http" - "Go services" - "Other / Multiple" diff --git a/.github/ISSUE_TEMPLATE/03_dx.yml b/.github/ISSUE_TEMPLATE/03_dx.yml new file mode 100644 index 000000000..5a8632d89 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_dx.yml @@ -0,0 +1,45 @@ +name: Developer Experience +description: Report a friction point, confusing behavior, or DX improvement idea +labels: ["dx"] +body: + - type: markdown + attributes: + value: | + DX reports help keep noskills-server delightful to use. Paste your `noskills-server doctor --json` output below. + + - type: input + id: version + attributes: + label: Version + description: Output of `noskills-server version` + placeholder: "noskills-server v1.0.0 (darwin/arm64)" + validations: + required: true + + - type: textarea + id: doctor + attributes: + label: Doctor output + description: Run `noskills-server doctor --json` and paste the result + render: json + + - type: textarea + id: friction + attributes: + label: What was the friction? + description: What step confused you, felt slow, or required unexpected effort? + placeholder: "When I ran 'noskills-server start' for the first time, the mkcert sudo prompt appeared without explanation…" + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect? + placeholder: "I expected to see a message explaining what mkcert is and why a password is needed." + + - type: textarea + id: context + attributes: + label: Additional context + description: Screenshots, terminal recordings, or anything else that helps diff --git a/.github/SECURITY.md b/.github/SECURITY.md index cc5d72204..47b7bb051 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -7,9 +7,8 @@ versions: | Version | Supported | | ------- | ------------------ | -| 0.8.x | :white_check_mark: | -| 0.7.x | :white_check_mark: | -| < 0.7 | :x: | +| 4.1.x | :white_check_mark: | +| < 4.1 | :x: | ## Reporting a Vulnerability diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000..54f564378 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,6 @@ +# Runner labels that are not GitHub-hosted, so actionlint can check +# `runs-on` values instead of flagging every ubicloud job. +self-hosted-runner: + labels: + - ubicloud-standard-2-arm + - ubicloud-standard-4-arm diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 61362f95b..0ecd0b68e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,7 +19,7 @@ updates: - "*" - package-ecosystem: "gomod" - directory: "/apps/services" + directory: "/" schedule: interval: "weekly" commit-message: diff --git a/.github/issue-labels.yml b/.github/issue-labels.yml index 2b219e034..1e44766bd 100644 --- a/.github/issue-labels.yml +++ b/.github/issue-labels.yml @@ -58,47 +58,47 @@ # Package labels - name: "pkg:fp" color: "6f42c1" - description: "Changes to @eser/fp" + description: "Changes to @eserstack/fp" - name: "pkg:di" color: "6f42c1" - description: "Changes to @eser/di" + description: "Changes to @eserstack/di" - name: "pkg:cli" color: "6f42c1" - description: "Changes to @eser/cli" + description: "Changes to @eserstack/cli" - name: "pkg:events" color: "6f42c1" - description: "Changes to @eser/events" + description: "Changes to @eserstack/events" - name: "pkg:config" color: "6f42c1" - description: "Changes to @eser/config" + description: "Changes to @eserstack/config" - name: "pkg:http" color: "6f42c1" - description: "Changes to @eser/http" + description: "Changes to @eserstack/http" - name: "pkg:shell" color: "6f42c1" - description: "Changes to @eser/shell" + description: "Changes to @eserstack/shell" - name: "pkg:standards" color: "6f42c1" - description: "Changes to @eser/standards" + description: "Changes to @eserstack/standards" - name: "pkg:laroux" color: "6f42c1" - description: "Changes to @eser/laroux and related packages" + description: "Changes to @eserstack/laroux and related packages" - name: "pkg:bundler" color: "6f42c1" - description: "Changes to @eser/bundler" + description: "Changes to @eserstack/bundler" - name: "pkg:jsx-runtime" color: "6f42c1" - description: "Changes to @eser/jsx-runtime" + description: "Changes to @eserstack/jsx-runtime" # Areas - name: go @@ -116,7 +116,3 @@ - name: templates color: "c2e0c6" description: Project template changes - -- name: cool - color: "bfdadc" - description: "Changes to @cool/* packages" diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index 883bda7f8..48930f5c7 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -15,8 +15,8 @@ ci/cd: claude-code: - changed-files: - any-glob-to-any-file: - - ".claude/**" - - "CLAUDE.md" + - ".agents/**" + - ".claude" - "AGENTS.md" templates: @@ -27,100 +27,96 @@ templates: pkg:fp: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/fp/**" + - "pkg/@eserstack/fp/**" pkg:di: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/di/**" + - "pkg/@eserstack/di/**" pkg:cli: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/cli/**" + - "pkg/@eserstack/cli/**" pkg:events: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/events/**" + - "pkg/@eserstack/events/**" pkg:config: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/config/**" + - "pkg/@eserstack/config/**" pkg:http: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/http/**" + - "pkg/@eserstack/http/**" pkg:shell: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/shell/**" + - "pkg/@eserstack/shell/**" pkg:standards: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/standards/**" + - "pkg/@eserstack/standards/**" pkg:testing: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/testing/**" + - "pkg/@eserstack/testing/**" pkg:bundler: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/bundler/**" + - "pkg/@eserstack/bundler/**" pkg:laroux: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/laroux/**" - - "pkg/@eser/laroux-*/**" + - "pkg/@eserstack/laroux/**" + - "pkg/@eserstack/laroux-*/**" pkg:jsx-runtime: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/jsx-runtime/**" + - "pkg/@eserstack/jsx-runtime/**" pkg:cache: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/cache/**" + - "pkg/@eserstack/cache/**" pkg:logging: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/logging/**" + - "pkg/@eserstack/logging/**" pkg:primitives: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/primitives/**" + - "pkg/@eserstack/primitives/**" pkg:parsing: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/parsing/**" + - "pkg/@eserstack/parsing/**" pkg:crypto: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/crypto/**" + - "pkg/@eserstack/crypto/**" pkg:writer: - changed-files: - any-glob-to-any-file: - - "pkg/@eser/writer/**" + - "pkg/@eserstack/writer/**" go: - changed-files: - any-glob-to-any-file: - - "apps/services/**" - -cool: - - changed-files: - - any-glob-to-any-file: - - "pkg/@cool/**" + - "cmd/ajan/**" + - "pkg/ajan/**" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 743fc4054..c7ae1e74e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,14 @@ on: push: branches: - "**" + # Releases are driven by the tag itself, not by a commit-message string. + # branches and tags are OR'd, so this adds tag runs without narrowing the + # branch runs. The tag must be pushed with a human's credentials: GitHub + # deliberately does not dispatch workflows for refs pushed by a job using + # GITHUB_TOKEN (the recursion guard), which is exactly why CI can no longer + # create the tag for us. + tags: + - "v*" pull_request: types: @@ -13,21 +21,55 @@ on: branches: - "**" + # Resuming a release: pick the tag and the chain runs against that tag's tree + # with no git involvement. `post-publish` skips validate, the smoke tests and + # the registry publish (all idempotent anyway) and goes straight to release + # notes, binaries, assets, ajan platform packages, Homebrew and Nix — the + # stages that used to require delete-and-repush of the tag to retry. workflow_dispatch: + inputs: + tag: + description: "Existing release tag to resume (e.g. v4.5.1). Empty = plain validation run of the branch." + required: false + type: string + stage: + description: "Which part of the release chain to run for the tag" + required: false + type: choice + default: post-publish + options: + - post-publish + - full permissions: contents: write id-token: write concurrency: - group: ${{ github.ref }}-integrity - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + # Tag runs are never cancelled. Re-pushing a tag mid-release would otherwise + # cancel the run somewhere between `publish` (irreversible — JSR and npm keep + # the version forever) and `upload-assets`, leaving a released version with no + # binaries attached. + group: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }}-integrity + cancel-in-progress: ${{ inputs.tag == '' && !startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main' }} jobs: # ── Always runs ───────────────────────────────────────────────── + # Deliberately runs on tag pushes too. `release` pushes the commit and the tag + # back to back, so the branch run and the tag run start almost together and the + # tag run never waits on the branch one -- re-running validate here is what + # makes the release verified by the same run that publishes it, rather than by + # a separate run that may still be in flight (or already red). validate: name: Validate - runs-on: ubuntu-latest + # Skipped when resuming a tag's post-publish stages: that tree was already + # validated by the run that published it. + if: >- + !(github.event_name == 'workflow_dispatch' && inputs.tag != '' && + inputs.stage == 'post-publish') + # ubicloud: the longest job of every run; cheaper ARM minutes, and it only + # needs Deno, Go and pnpm which we install ourselves. + runs-on: ubicloud-standard-4-arm timeout-minutes: 20 permissions: @@ -35,20 +77,49 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 2 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 with: - deno-version: v2.x + deno-version: v2.9.5 + + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: - go-version: "1.25" - cache-dependency-path: apps/services/go.sum + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum - name: Run validation run: deno task cli ok @@ -59,49 +130,190 @@ jobs: - name: Upload coverage if: hashFiles('etc/coverage.lcov') != '' - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ./etc/coverage.lcov - # ── Release: tag ──────────────────────────────────────────────── - tag-release: - name: Tag Release + # ── Release preflight ────────────────────────────────────────── + # Runs the release-only steps (npm bundles installed outside the repo, + # publish dry-run, wasm + native ajan builds, release tooling dry-runs) on + # every push to main and on every tag, so a tag never discovers something a + # main push could have. Not on PRs or feature branches: it costs minutes and + # network, and main is the branch tags are cut from. + preflight: + name: Release Preflight needs: validate if: >- - github.event_name == 'push' && - github.ref == 'refs/heads/main' && - startsWith(github.event.head_commit.message, 'chore(codebase): release v') - runs-on: ubuntu-latest + !cancelled() && needs.validate.result == 'success' && + ( + github.ref == 'refs/heads/main' || + startsWith(github.ref, 'refs/tags/v') || + (github.event_name == 'workflow_dispatch' && inputs.tag != '' && inputs.stage == 'full') + ) + # GitHub-hosted: talks to the npm registry and needs Go for the wasm builds. + runs-on: ubuntu-24.04-arm + timeout-minutes: 15 + permissions: - contents: write + contents: read steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - - name: Create and push version tag - run: | - VERSION=$(cat VERSION) - TAG="v$VERSION" + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 - # Remove existing tag if present (idempotent for re-releases) - git tag -d "$TAG" 2>/dev/null || true - git push origin ":refs/tags/$TAG" 2>/dev/null || true + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "$TAG" -m "Release $TAG" - git push origin "$TAG" + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" - echo "### Tagged $TAG" >> $GITHUB_STEP_SUMMARY + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Run release preflight + run: deno task cli preflight + + # ── Release: gate ─────────────────────────────────────────────── + # Preflight for everything downstream. It creates nothing (hence contents: + # read) — its whole job is to fail BEFORE the irreversible steps run. + # + # No job output for the version: the `needs` context is not transitive, so + # only direct dependents could read it anyway, and every consumer already + # does VERSION=$(cat VERSION) against a checkout of the tag's tree — which + # this gate has just proven equals the tag. + release-gate: + name: Release Gate + needs: [validate, preflight] + # Two ways in: a human pushed a v* tag, or a human dispatched the workflow + # naming an existing tag to resume. A plain dispatch with no tag never + # reaches the release chain. Validate AND preflight must have passed on this + # tree; in post-publish resume both are skipped by design, and only then is + # `skipped` accepted. + if: >- + !cancelled() && + ( + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + && needs.validate.result == 'success' && needs.preflight.result == 'success') + || + (github.event_name == 'workflow_dispatch' && inputs.tag != '' && + ((needs.validate.result == 'success' && needs.preflight.result == 'success') || + (needs.validate.result == 'skipped' && needs.preflight.result == 'skipped' + && inputs.stage == 'post-publish'))) + ) + outputs: + tag: ${{ steps.mode.outputs.tag }} + resume: ${{ steps.mode.outputs.resume }} + # ubicloud: a few shell lines over the checkout, no toolchain needed. + runs-on: ubicloud-standard-2-arm + timeout-minutes: 5 + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Resolve release mode + id: mode + run: | + set -euo pipefail + if [ -n "${INPUT_TAG}" ]; then + TAG="${INPUT_TAG}" + if [ "${INPUT_STAGE}" = "post-publish" ]; then RESUME=true; else RESUME=false; fi + else + TAG="${GITHUB_REF#refs/tags/}" + RESUME=false + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "resume=$RESUME" >> "$GITHUB_OUTPUT" + echo "Release mode: tag=$TAG resume=$RESUME" + env: + INPUT_TAG: ${{ inputs.tag }} + INPUT_STAGE: ${{ inputs.stage }} + + - name: Verify tag against tree + run: | + set -euo pipefail + TAG="${{ steps.mode.outputs.tag }}" + VERSION="${TAG#v}" + FILE_VERSION="$(cat VERSION)" + + # The tag is what people install by; VERSION is what gets stamped into + # every published package. If they disagree the release ships packages + # under a version nobody can find from the tag. + if [ "$VERSION" != "$FILE_VERSION" ]; then + echo "::error::tag $TAG says $VERSION but the VERSION file says $FILE_VERSION." + echo "::error::Run 'deno task cli codebase release ' so tag and tree agree, then retag." + exit 1 + fi + + # release-notes runs AFTER npm and JSR are written, so a missing + # CHANGELOG section used to surface only once the registries were + # irreversibly published — that is how the v4.3.0 release died. + # + # This pattern MIRRORS HEADING_PATTERN in + # pkg/@eserstack/codebase/changelog-text.ts, which is the parser that + # actually runs later. Keep the two in step: a gate that accepts more + # than the parser lets a release publish and then fail on notes (the + # very failure this job exists to prevent), and one that accepts less + # blocks a release the parser would have handled. In particular it is + # ANCHORED AT BOTH ENDS — "## 4.3.0 - 2026-08-08 (hotfix)" is rejected + # by the parser, so it must be rejected here too — and it tolerates the + # optional "v" and brackets of the legacy headings still in the file + # ("## [v4.1.61] - …", which changelog.go also still accepts). + ESCAPED="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" + HEADING="^##[[:space:]]+\[?v?${ESCAPED}\]?[[:space:]]*-?[[:space:]]*([0-9]{4}-[0-9]{2}-[0-9]{2})?[[:space:]]*$" + + if ! grep -Eq "$HEADING" CHANGELOG.md; then + echo "::error::CHANGELOG.md has no section matching '## $VERSION - '." + echo "::error::Note the heading must end after the date — trailing text is not parsed." + exit 1 + fi + + echo "### Releasing $TAG" >> $GITHUB_STEP_SUMMARY # ── Release: smoke test ───────────────────────────────────────── smoke-test: name: npm Smoke Test - needs: tag-release - runs-on: ubuntu-latest + needs: release-gate + if: needs.release-gate.outputs.resume != 'true' + # ubicloud: builds three bundles with Deno + pnpm we install ourselves. + runs-on: ubicloud-standard-4-arm timeout-minutes: 10 permissions: @@ -109,36 +321,409 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: "latest" + cache: "pnpm" - - name: Build npm package + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build eser npm package run: deno task cli build - - name: Smoke test — help - run: node pkg/@eser/cli/dist/eser.js --help + - name: Build noskills npm package + run: deno run --allow-all pkg/@eserstack/noskills/scripts/npm-build.ts + + - name: Build laroux npm package + run: deno run --allow-all pkg/@eserstack/laroux-server/scripts/npm-build.ts + + - name: Smoke test — eser help + run: node pkg/@eserstack/cli/dist/eser.js --help + + - name: Smoke test — eser validate-licenses + run: node pkg/@eserstack/cli/dist/eser.js codebase validate-licenses || true + + - name: Smoke test — noskills help + run: node pkg/@eserstack/noskills/dist/noskills.js --help + + - name: Smoke test — laroux help + run: node pkg/@eserstack/laroux-server/dist/laroux.js --help + + - name: Upload eser npm bundle + uses: actions/upload-artifact@v7 + with: + name: npm-eser + path: pkg/@eserstack/cli/dist/ - - name: Smoke test — validate-licenses - run: node pkg/@eser/cli/dist/eser.js codebase validate-licenses || true + - name: Upload noskills npm bundle + uses: actions/upload-artifact@v7 + with: + name: npm-noskills + path: pkg/@eserstack/noskills/dist/ - - name: Upload npm bundle + - name: Upload laroux npm bundle uses: actions/upload-artifact@v7 with: - name: npm-bundle - path: pkg/@eser/cli/dist/ + name: npm-laroux + path: pkg/@eserstack/laroux-server/dist/ + + # ── npm-only smoke test (no Deno) ────────────────────────── + npm-no-deno-test: + name: npm Install Test (no Deno) + needs: smoke-test + # GitHub-hosted: the point of this job is a stock Node image with no Deno. + runs-on: ubuntu-24.04-arm + timeout-minutes: 10 + + permissions: + contents: read + + steps: + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + # No `cache: "pnpm"`: this job never checks out the repo -- it installs a + # downloaded artifact -- and setup-node resolves the pnpm cache key from + # pnpm-lock.yaml in the workspace, so it hard-fails with "Dependencies + # lock file is not found" before any test runs. + - name: Setup Node.js only + uses: actions/setup-node@v7 + with: + node-version: "26" + + - name: Verify Deno is NOT installed + run: | + if command -v deno &>/dev/null; then + echo "::error::Deno is installed — this job must run without Deno" + exit 1 + fi + echo "Confirmed: Deno is not installed" + + - name: Download eser npm bundle + uses: actions/download-artifact@v8 + with: + name: npm-eser + path: npm-eser/ + + # Installed as a dependency of a scratch project, not with `pnpm add -g`. + # + # What this job needs to prove is that the published package resolves, + # declares its bin correctly, and runs under Node with no Deno present. + # A global install tests all of that PLUS the runner's global-bin PATH + # wiring, which is not ours and which broke this job twice: pnpm refuses + # to link a global binary when its global bin directory is not already on + # PATH, and `pnpm bin -g` cannot be used to discover that directory + # because it exits non-zero in exactly that situation. + # + # A local install has no such coupling: pnpm links the bin into + # node_modules/.bin, which is a fixed path we invoke directly. + # + # This job had never once executed before the tag-driven pipeline made it + # a barrier in front of `publish` — so every bug in it was latent. + - name: Install eser from artifact into a scratch consumer + run: | + set -euo pipefail + mkdir -p consumer + cd consumer + printf '{"name":"consumer","private":true,"version":"1.0.0"}\n' > package.json + pnpm add ../npm-eser/ + + # Fixed location, exported once for the smoke tests below. + echo "ESER_BIN=$PWD/node_modules/.bin/eser" >> "$GITHUB_ENV" + + - name: Smoke test — eser --help + run: | + "$ESER_BIN" --help + + - name: Smoke test — eser version + run: | + "$ESER_BIN" version + + - name: Smoke test — eser ajan version (graceful failure) + run: | + set +e + OUTPUT=$("$ESER_BIN" ajan version 2>&1) + EXIT_CODE=$? + echo "$OUTPUT" + + if echo "$OUTPUT" | grep -qi "deno"; then + echo "::error::Error message mentions Deno — npm package must not require Deno" + exit 1 + fi + + if [ $EXIT_CODE -eq 0 ]; then + echo "eser ajan version succeeded (unexpected but acceptable)" + else + echo "eser ajan version failed as expected (no shared library in npm package)" + echo "Error output does NOT mention Deno — PASS" + fi + + # ── Cross-runtime testing ────────────────────────────────── + cross-runtime-test: + name: Cross-Runtime Test (${{ matrix.runtime }} on ${{ matrix.os }}) + needs: validate + # Skipped on tags: nothing in the release chain depends on this matrix, and + # six more runners per tag is real cost. Note this is a cost trade, not a + # guarantee -- the branch run covering this commit may still be running (or + # failing) when the tag run publishes. `validate` is the gate that holds. + if: "!startsWith(github.ref, 'refs/tags/') && inputs.tag == ''" + # One GitHub-hosted image per OS in the matrix: the job exists to prove the + # FFI loads on stock Node, Bun and Deno installs, not on our own setup. + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04-arm, macos-latest] + runtime: [deno, node, bun] + include: + - runtime: deno + version: "2.9.5" + - runtime: node + version: "26" + - runtime: bun + version: "1.3" + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Setup Node.js + if: matrix.runtime == 'node' + uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.version }} + + - name: Setup Bun + if: matrix.runtime == 'bun' + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ matrix.version }} + + - name: Build Go shared library (native) + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts + + # Node and Bun get the bundle the way a user does: packed, installed with + # npm into a scratch project outside the repo, then run from there. In + # place under pkg/@eserstack/cli/dist the bundle cannot find koffi (pnpm + # keeps it under @eserstack/ajan), which kept the Node entries red for + # months behind continue-on-error. --require-native: with the library + # just built and named in ESER_AJAN_LIB_PATH, a graceful "no native + # library" is a failure here, not an acceptable outcome. + - name: Verify FFI and CLI — eser ajan version, version, --help + env: + ESER_AJAN_WASM: disabled + ESER_AJAN_LIB_PATH: ${{ github.workspace }}/pkg/@eserstack/ajan/dist/${{ runner.arch == 'ARM64' && 'aarch64' || 'x86_64' }}-${{ runner.os == 'macOS' && 'darwin' || 'linux' }}/libeser_ajan.${{ runner.os == 'macOS' && 'dylib' || 'so' }} + run: | + if [ "${{ matrix.runtime }}" = "deno" ]; then + deno task cli ajan version + deno task cli --help + else + deno task cli build + deno run --allow-all etc/scripts/preflight-npm-consumer.ts \ + --runtime="${{ matrix.runtime }}" --require-native + fi + + # ── Windows ───────────────────────────────────────────────────── + # The only job that runs on Windows at all. Two things were unexercised + # before it existed, both of which fail late and expensively: + # + # 1. pin_image_windows.go. Its cgo constructor pins the shared library so + # FreeLibrary can never unmap it -- the fix for the Go-runtime crash when + # a host dlclose/dlopens the image. It is compiled ONLY by + # publish-ajan.yml, which is workflow_dispatch-only, so a mistake there + # surfaced during a manual publish rather than on the PR that caused it. + # 2. etc/scripts/install.ps1. Windows binaries have always been + # built and archived, but nothing ever unpacked that archive and checked + # the binaries land somewhere resolvable. + # + # Installing into a scratch directory and asserting Get-Command finds the + # binaries is the whole point: "extracted" is not the property that matters -- + # "on PATH" is. + windows-smoke: + name: Windows Smoke Test + # Skipped on tags for the same reason as cross-runtime-test: no release job + # depends on it, and it is a cost trade rather than a guarantee -- a red + # windows-smoke on the branch run does not block the tag run. + if: "!startsWith(github.ref, 'refs/tags/') && inputs.tag == ''" + # Windows is the only place pin_image_windows.go and install.ps1 can run. + runs-on: windows-latest + timeout-minutes: 20 + + permissions: + contents: read + + # Explicit rather than relying on the Windows runner default, so the + # PowerShell in every step below is unambiguous. + defaults: + run: + shell: pwsh + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Build Windows binaries + run: | + New-Item -ItemType Directory -Path dist-windows -Force | Out-Null + go build -o dist-windows/ ./cmd/noskills-server + Get-ChildItem dist-windows + + # Compiles pin_image_windows.go for the first time outside a manual + # publish. CGO_ENABLED=1 is required: the pin is a cgo constructor, and + # with cgo off the file is dropped and the check becomes vacuous. + - name: Build ajan shared library (compiles pin_image_windows.go) + working-directory: pkg/@eserstack/ajan + env: + CGO_ENABLED: "1" + run: go build -buildmode=c-shared -o eser_ajan.dll . + + - name: Verify the pin symbol is actually in the DLL + working-directory: pkg/@eserstack/ajan + run: | + if (-not (Test-Path eser_ajan.dll)) { throw "eser_ajan.dll was not produced" } + # A constructor that compiled but got stripped would leave the crash + # unfixed while the build stayed green, so assert the symbol survived + # into the artifact rather than trusting the exit code. + $found = & go tool nm eser_ajan.dll 2>$null | Select-String 'eserAjanPinImage' + if (-not $found) { throw "eserAjanPinImage not present in eser_ajan.dll" } + Write-Host "pin constructor present: $found" + + - name: Run Go tests for the ajan module + working-directory: pkg/@eserstack/ajan + env: + CGO_ENABLED: "1" + run: go test ./... + + # A release-shaped archive, for realism. Note this does NOT exercise the + # installer's own name construction or its download and checksum path: + # -ArchivePath deliberately bypasses both so the job tests the current tree + # rather than whatever is already published. Those two remain covered only + # by the first real release. + - name: Package a release-shaped archive + run: | + Compress-Archive -Path dist-windows/* -DestinationPath noskills-server-v0.0.0-x86_64-pc-windows-msvc.zip -Force + + - name: Install via etc/scripts/install.ps1 + run: | + ./etc/scripts/install.ps1 ` + -Product noskills-server ` + -ArchivePath noskills-server-v0.0.0-x86_64-pc-windows-msvc.zip ` + -InstallDir "$env:RUNNER_TEMP\eser-bin" + + - name: Assert the installed binaries resolve on PATH + run: | + # Re-read the stored user PATH the installer wrote. A fresh step gets a + # new process whose $env:PATH is the value from when the job started, + # so without this the assertion would test the installer's in-session + # mutation rather than the persisted registry change it makes. + $env:PATH = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + + [Environment]::GetEnvironmentVariable('Path', 'User') + + foreach ($bin in @('noskills-server')) { + $cmd = Get-Command $bin -ErrorAction SilentlyContinue + + if (-not $cmd) { throw "$bin did not resolve on PATH after install" } + + Write-Host "$bin -> $($cmd.Source)" + } # ── Release: publish ──────────────────────────────────────────── publish: name: Publish - needs: [tag-release, smoke-test] - runs-on: ubuntu-latest + # npm-no-deno-test is a barrier, not just an ancestor: it used to be a leaf, + # so a broken no-Deno install was only ever discovered AFTER npm already had + # the version. Nothing reaches a registry until both smoke tests are green. + # publish-ajan comes first: the bundles pin their platform packages to the + # exact version, so those packages must exist on npm before eser@VERSION + # does. A platform publish failure therefore blocks the bundles instead of + # shipping a CLI whose native half is missing. + needs: [release-gate, smoke-test, npm-no-deno-test, publish-ajan] + # GitHub-hosted: npm OIDC trusted publishing and JSR provenance are tied + # to GitHub-issued id-tokens from GitHub-hosted runners. + runs-on: ubuntu-24.04-arm timeout-minutes: 15 permissions: @@ -147,31 +732,100 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: - fetch-depth: 2 + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 - - name: Publish to JSR - run: deno publish + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: "latest" registry-url: "https://registry.npmjs.org" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # pkg/@eserstack/*/deno.json are generated from each package's + # package.json + the root VERSION file and are gitignored, so a fresh + # checkout has none. Without this step `deno publish` finds no packages. + - name: Generate JSR manifests + run: deno run -A ./etc/scripts/gen-jsr-manifests.ts - - name: Download npm bundle + - name: Publish to JSR + run: deno publish + + - name: Download eser npm bundle uses: actions/download-artifact@v8 with: - name: npm-bundle - path: pkg/@eser/cli/dist/ + name: npm-eser + path: npm-dist/eser/ - - name: Publish to npm - run: npm publish --provenance --access public - working-directory: ./pkg/@eser/cli/dist + - name: Download noskills npm bundle + uses: actions/download-artifact@v8 + with: + name: npm-noskills + path: npm-dist/noskills/ + + - name: Download laroux npm bundle + uses: actions/download-artifact@v8 + with: + name: npm-laroux + path: npm-dist/laroux/ + + - name: Publish eser to npm + run: | + NAME=$(node -p "require('./package.json').name") + VERSION=$(node -p "require('./package.json').version") + if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then + echo "$NAME@$VERSION is already on npm — skipping so a later stage can be re-run" + else + pnpm publish --provenance --access public --no-git-checks + fi + working-directory: npm-dist/eser + + - name: Publish noskills to npm + run: | + NAME=$(node -p "require('./package.json').name") + VERSION=$(node -p "require('./package.json').version") + if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then + echo "$NAME@$VERSION is already on npm — skipping so a later stage can be re-run" + else + pnpm publish --provenance --access public --no-git-checks + fi + working-directory: npm-dist/noskills + + - name: Publish laroux to npm + run: | + NAME=$(node -p "require('./package.json').name") + VERSION=$(node -p "require('./package.json').version") + if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then + echo "$NAME@$VERSION is already on npm — skipping so a later stage can be re-run" + else + pnpm publish --provenance --access public --no-git-checks + fi + working-directory: npm-dist/laroux - name: Summary run: | @@ -179,36 +833,172 @@ jobs: echo "### Published v$VERSION" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **JSR:** https://jsr.io/@eser" >> $GITHUB_STEP_SUMMARY - echo "- **npm:** https://www.npmjs.com/package/eser/v/$VERSION" >> $GITHUB_STEP_SUMMARY + echo "- **npm (eser):** https://www.npmjs.com/package/eser/v/$VERSION" >> $GITHUB_STEP_SUMMARY + echo "- **npm (noskills):** https://www.npmjs.com/package/noskills/v/$VERSION" >> $GITHUB_STEP_SUMMARY + echo "- **npm (laroux):** https://www.npmjs.com/package/laroux/v/$VERSION" >> $GITHUB_STEP_SUMMARY # ── Release: sync release notes ──────────────────────────────── release-notes: name: Sync Release Notes - needs: publish - runs-on: ubuntu-latest + needs: [release-gate, publish] + # On a post-publish resume `publish` is skipped by design; a FAILED publish + # still blocks, so a half-published version never gets release notes. + if: >- + !cancelled() && needs.release-gate.result == 'success' && + (needs.publish.result == 'success' || + (needs.publish.result == 'skipped' && needs.release-gate.outputs.resume == 'true')) + # GitHub-hosted: this job drives `gh` against the release, and the image + # guarantees the CLI version; the same class upload-assets uses. + runs-on: ubuntu-24.04-arm + timeout-minutes: 10 permissions: contents: write steps: + # Only CHANGELOG.md, VERSION and the dependency-free notes extractor are + # read from the checkout. No token is written to .git/config: nothing in + # this job pushes, and gh reads GH_TOKEN from the step env instead. - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: - fetch-depth: 0 + fetch-depth: 1 + persist-credentials: false + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - - name: Setup Deno - uses: denoland/setup-deno@v2 + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "26" + + # This job holds contents: write, so it runs nothing resolved from a + # registry at release time. It used to install eser@$VERSION from npm into a + # scratch dir with no lockfile and install scripts enabled, which ran the + # newest release of every caret-range dependency next to the write token. + # The notes now come from the pure CHANGELOG parser in the tag tree + # (codebase/changelog-text.ts, the same code `eser codebase gh + # release-notes` uses), run by a bare node, and gh is called directly. + - name: Extract release notes from CHANGELOG + run: | + set -euo pipefail + VERSION=$(cat VERSION) + node etc/scripts/release-notes-from-changelog.ts "v$VERSION" "$RUNNER_TEMP/release-notes.md" + + # A failing `gh` used to surface only as "exit code 1" from the wrapper, so + # print the CLI version and what the token can do BEFORE relying on it. + - name: Show gh version and token access + run: | + gh --version + gh auth status + env: + GH_TOKEN: ${{ github.token }} + # Same create-or-edit semantics as syncReleaseNotes in + # codebase/release-notes.ts: edit an existing release, otherwise create + # it, and fall back to edit only when someone else created it between the + # check and the create. - name: Sync CHANGELOG to GitHub Release - run: deno task cli codebase gh release-notes --create-if-missing + run: | + set -euo pipefail + TAG="v$(cat VERSION)" + NOTES="$RUNNER_TEMP/release-notes.md" + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --notes-file "$NOTES" + echo "Updated release notes for $TAG" + elif gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "eserstack $TAG" --notes-file "$NOTES"; then + echo "Created release $TAG" + elif gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --notes-file "$NOTES" + echo "Updated release notes for $TAG (created concurrently)" + else + echo "::error::gh release create $TAG failed" + exit 1 + fi env: GH_TOKEN: ${{ github.token }} + # ── Release: build ajan native libs (macOS) ─────────────────── + build-ajan-darwin: + name: Build ajan (darwin) + needs: release-gate + # Explicit, with !cancelled(): a job without its own condition is skipped + # whenever ANY job upstream in the chain was skipped, and validate + preflight + # are skipped by design on a post-publish resume. Only the direct needs + # decide here. + if: "!cancelled() && needs.release-gate.result == 'success'" + # macOS is the only place the darwin shared libraries can be built. + runs-on: macos-latest + timeout-minutes: 10 + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" + cache: "pnpm" + + # build.ts imports @eserstack/standards (→ @std/path); under + # nodeModulesDir:manual deno needs node_modules populated to resolve it. + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Build aarch64-darwin (native) + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=aarch64-darwin + + - name: Build x86_64-darwin (cross-compile via clang -arch x86_64) + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-darwin + + - name: Upload darwin dist + uses: actions/upload-artifact@v7 + with: + name: ajan-dist-darwin + path: | + pkg/@eserstack/ajan/dist/aarch64-darwin/ + pkg/@eserstack/ajan/dist/x86_64-darwin/ + # ── Release: compile standalone binaries ────────────────────── compile-binaries: name: Compile Binaries - needs: tag-release - runs-on: ubuntu-latest + needs: [release-gate, build-ajan-darwin] + # Explicit, with !cancelled(): a job without its own condition is skipped + # whenever ANY job upstream in the chain was skipped, and validate + preflight + # are skipped by design on a post-publish resume. Only the direct needs + # decide here. + if: >- + !cancelled() && needs.release-gate.result == 'success' && + needs.build-ajan-darwin.result == 'success' + # GitHub-hosted: cross-compilers from apt and the Go toolchain on a known + # image; the artifacts it produces are what gets signed and shipped. + runs-on: ubuntu-24.04-arm timeout-minutes: 15 permissions: @@ -216,15 +1006,73 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 with: - deno-version: v2.x + deno-version: v2.9.5 + + - name: Cache Deno dependencies + uses: actions/cache@v6 + with: + path: | + ~/.cache/deno + ~/.deno + node_modules + key: deno-${{ runner.arch }}-${{ hashFiles('deno.json', '**/deno.json', '**/package.json') }} + restore-keys: deno-${{ runner.arch }}- + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Install cross-compilers + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq \ + gcc-x86-64-linux-gnu \ + gcc-mingw-w64-x86-64 + + - name: Download darwin dist + uses: actions/download-artifact@v8 + with: + name: ajan-dist-darwin + path: pkg/@eserstack/ajan/dist/ + + - name: Build Go shared libraries (linux + windows) + run: | + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=aarch64-linux + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-linux + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-windows + + - name: Build Go WASM modules + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --wasm + + # Copies each built library into its workspace member + # (pkg/@eserstack/ajan-/); the manifests are committed. + - name: Stage platform libraries into their packages + run: deno run --allow-all pkg/@eserstack/ajan/npm/generate-packages.ts - name: Compile for all platforms - run: deno run --allow-all ./pkg/@eser/cli/scripts/compile.ts + run: deno run --allow-all ./pkg/@eserstack/cli/scripts/compile.ts - name: Upload binary bundle uses: actions/upload-artifact@v7 @@ -232,18 +1080,152 @@ jobs: name: binary-bundle path: etc/temp/binaries/ + # The least-common-ancestor of the paths is pkg/@eserstack/, so the + # artifact extracts as ajan-/ directories — each a complete, + # publishable package (committed manifest + staged binary). + - name: Upload ajan platform packages + uses: actions/upload-artifact@v7 + with: + name: ajan-npm-packages + path: | + pkg/@eserstack/ajan-*/package.json + pkg/@eserstack/ajan-*/README.md + pkg/@eserstack/ajan-*/libeser_ajan.* + pkg/@eserstack/ajan-*/*.wasm + if-no-files-found: error + + # ── Release: publish ajan npm packages ───────────────────── + publish-ajan: + name: Publish ajan packages + # Same barrier as `publish`. compile-binaries alone does not depend on + # either smoke test, so in the failed v4.3.0 run this job was publishing to + # npm while smoke-test was already red. + needs: [release-gate, compile-binaries, smoke-test, npm-no-deno-test] + # On a post-publish resume the smoke tests are skipped by design; this is + # the job that publishes platform packages missed by an earlier run. + if: >- + !cancelled() && needs.compile-binaries.result == 'success' && + ((needs.smoke-test.result == 'success' && needs.npm-no-deno-test.result == 'success') + || needs.release-gate.outputs.resume == 'true') + # GitHub-hosted: npm OIDC trusted publishing, same as `publish`. + runs-on: ubuntu-24.04-arm + timeout-minutes: 10 + + permissions: + contents: read + id-token: write + + steps: + - name: Download ajan npm packages + uses: actions/download-artifact@v8 + with: + name: ajan-npm-packages + path: ajan-npm/ + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + # No `cache: "pnpm"` -- same reason as npm-no-deno-test: this job + # publishes prebuilt directories from an artifact and never checks + # out the repo, so there is no lockfile for the cache to key on. + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" + registry-url: "https://registry.npmjs.org" + + # Auth = npm OIDC trusted publishing (id-token:write + --provenance + the + # setup-node registry-url above), EXACTLY like the main `publish` job. Do + # NOT add `env: NODE_AUTH_TOKEN` — there is no NPM_TOKEN secret in this + # project, and an empty token writes `_authToken=` into .npmrc, making pnpm + # attempt (and fail) token auth (ENEEDAUTH) instead of using OIDC. Each + # @eserstack/ajan-* package must have a trusted publisher configured on npm + # (publisher: GitHub Actions, repo: eser/stack, workflow: build.yml). The + # FIRST publish of a brand-new package name can't use OIDC (npm can't attach + # a trusted publisher to a nonexistent package) — bootstrap it once via + # .github/workflows/publish-ajan.yml, then this job self-sustains. + # + # A real publish error FAILS the job (only versions already on npm are + # skipped) — the previous `|| echo "may already exist"` swallowed ENEEDAUTH + # and published nothing while going green. + - name: Publish ajan platform packages to npm + run: | + set -euo pipefail + published=0 + skipped=0 + untrusted="" + for dir in ajan-npm/ajan-*/; do + [ -f "$dir/package.json" ] || continue + name=$(node -p "require('./$dir/package.json').name") + version=$(node -p "require('./$dir/package.json').version") + if npm view "$name@$version" version >/dev/null 2>&1; then + echo "✓ $name@$version already on npm — skipping" + skipped=$((skipped + 1)) + continue + fi + echo "→ Publishing $name@$version ..." + if (cd "$dir" && pnpm publish --provenance --access public --no-git-checks 2>&1 | tee "$RUNNER_TEMP/publish.log"); then + published=$((published + 1)) + continue + fi + # npm answers 404 on the PUT when the package has no trusted + # publisher for this repo/workflow. That is a one-time npmjs.com + # setting, not a build problem, so keep going and list every such + # package at the end instead of stopping at the first one. Any other + # failure is a real publish error and fails right here. + if grep -q "npm error 404 Not Found - PUT" "$RUNNER_TEMP/publish.log"; then + untrusted="$untrusted $name" + continue + fi + echo "::error::$name@$version failed to publish for a reason other than a missing trusted publisher — see the log above." + exit 1 + done + echo "Done: $published published, $skipped already-present." + if [ -n "$untrusted" ]; then + { + echo "### ajan platform packages missing an npm trusted publisher" + echo "" + echo "npm returned 404 on PUT for:$untrusted" + echo "" + echo "Fix once on npmjs.com for each package: Settings → Trusted publishing → GitHub Actions, repository eser/stack, workflow build.yml. Then resume this tag with workflow_dispatch (stage: post-publish)." + } | tee -a "$GITHUB_STEP_SUMMARY" + echo "::error::No trusted publisher on npm for:$untrusted" + exit 1 + fi + # No root @eserstack/ajan meta-package — the eser CLI lists + # @eserstack/ajan-* as optionalDependencies directly. + # ── Release: upload assets + sign ───────────────────────────── upload-assets: name: Upload & Sign Assets needs: [compile-binaries, release-notes] - runs-on: ubuntu-latest - timeout-minutes: 10 + # Explicit, with !cancelled(): a job without its own condition is skipped + # whenever ANY job upstream in the chain was skipped, and validate + preflight + # are skipped by design on a post-publish resume. Only the direct needs + # decide here. + if: >- + !cancelled() && needs.compile-binaries.result == 'success' && + needs.release-notes.result == 'success' + # GitHub-hosted: cosign keyless signing and `gh release upload` need the + # GitHub-issued id-token and a known image. + runs-on: ubuntu-24.04-arm + # The upload step retries per file, so give the job room for a slow API + # instead of the 10 minutes that killed run 35273933021 mid-upload. + timeout-minutes: 30 permissions: contents: write id-token: write steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} + - name: Download binary bundle uses: actions/download-artifact@v8 with: @@ -262,14 +1244,30 @@ jobs: done - name: Upload to GitHub Release + # One gh invocation per asset with a hard per-attempt timeout and + # retries: a single stalled multi-file upload hung for nine minutes + # with no output and took the whole job down. run: | VERSION=$(cat VERSION) TAG="v$VERSION" - gh release upload "$TAG" --clobber \ - binaries/*.tar.gz \ - binaries/*.zip \ - binaries/*.bundle \ - binaries/SHA256SUMS.txt + status=0 + for asset in binaries/*.tar.gz binaries/*.zip binaries/*.bundle binaries/SHA256SUMS.txt; do + [ -e "$asset" ] || continue + ok=0 + for attempt in 1 2 3; do + if timeout 180 gh release upload "$TAG" --clobber "$asset"; then + ok=1 + break + fi + echo "::warning::upload of $asset failed (attempt $attempt)" + sleep $((attempt * 10)) + done + if [ "$ok" -ne 1 ]; then + echo "::error::giving up on $asset" + status=1 + fi + done + exit "$status" env: GH_TOKEN: ${{ github.token }} @@ -285,22 +1283,37 @@ jobs: update-homebrew: name: Update Homebrew Tap needs: upload-assets - runs-on: ubuntu-latest + # Explicit, with !cancelled(): a job without its own condition is skipped + # whenever ANY job upstream in the chain was skipped, and validate + preflight + # are skipped by design on a post-publish resume. Only the direct needs + # decide here. + if: "!cancelled() && needs.upload-assets.result == 'success'" + # ubicloud: a short Deno script pushing to the tap with an App token. + runs-on: ubicloud-standard-2-arm timeout-minutes: 5 + continue-on-error: true permissions: contents: read steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + # No pnpm install and no generated manifests: the script imports nothing + # from the workspace on purpose (see its header), so Deno alone suffices. - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.ESER_APP_ID }} private-key: ${{ secrets.ESER_APP_PRIVATE_KEY }} @@ -316,22 +1329,37 @@ jobs: update-nix-hashes: name: Update Nix Hashes needs: upload-assets - runs-on: ubuntu-latest + # Explicit, with !cancelled(): a job without its own condition is skipped + # whenever ANY job upstream in the chain was skipped, and validate + preflight + # are skipped by design on a post-publish resume. Only the direct needs + # decide here. + if: "!cancelled() && needs.upload-assets.result == 'success'" + # ubicloud: a short Deno script pushing hashes with an App token. + runs-on: ubicloud-standard-2-arm timeout-minutes: 5 + continue-on-error: true permissions: - contents: read + contents: write steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 + ref: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - name: Setup Deno uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + # No pnpm install and no generated manifests: the script imports nothing + # from the workspace on purpose (see its header), so Deno alone suffices. - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.ESER_APP_ID }} private-key: ${{ secrets.ESER_APP_PRIVATE_KEY }} @@ -341,14 +1369,55 @@ jobs: - name: Update hashes run: deno run --allow-all ./etc/scripts/update-nix-hashes.ts - - name: Commit and push + # Two trees, because the constraints pull opposite ways: the hashes have + # to be computed from the TAG's tree (main's VERSION may already point + # past this tag, and only the tagged release's archives exist to hash), + # but the commit has to land on current main. A tag checkout is a detached + # HEAD, so the old bare `git commit && git push` here could never succeed + # -- and continue-on-error hid that, so nix hashes silently stopped + # updating. + # + # continue-on-error STAYS on this job: a distribution follower must not + # fail a release whose packages are already published. But it also means + # the new failure modes here (main dropped nix/, a rebase conflict, branch + # protection rejecting the push) would be just as silent, so the step + # below ends with an explicit ::warning:: — visible in the run summary, + # not just as a red square someone has to go looking for. + # + # fetch-depth 0: the rebase retry below has to replay onto whatever main + # has become, and a shallow clone may lack the merge base. + - name: Checkout main for the commit + uses: actions/checkout@v7 + with: + ref: main + path: main-tree + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Commit hashes to main run: | + set -euo pipefail + # continue-on-error keeps a failure here from failing the release, so + # announce it loudly instead of letting it pass as a green run. + trap 'echo "::warning::Nix hash update failed — run this job again, or update nix/hashes.json by hand."' ERR + + # Read VERSION from the tag's tree before switching directories -- + # main-tree holds a different, possibly newer, VERSION file. VERSION=$(cat VERSION) + + cp nix/hashes.json main-tree/nix/hashes.json + cd main-tree + git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add nix/hashes.json + # Hashes unchanged (a re-run of the same tag) is success, not failure. git diff --cached --quiet && exit 0 git commit -m "chore(nix): update binary hashes for v$VERSION" - git push - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + # Explicit refspec: the checkout may leave HEAD detached. One retry + # covers a push that landed on main while the release was running. + git push origin HEAD:main || { + git pull --rebase origin main + git push origin HEAD:main + } diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f664dedc1..3716be9ea 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -43,14 +43,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 2 - name: Setup Go if: matrix.language == 'go' - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: - go-version: "1.25" - cache-dependency-path: apps/services/go.sum + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache-dependency-path: go.sum - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 18019b50a..747f6a076 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -13,9 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 - name: Label PR - uses: actions/labeler@v6 + uses: actions/labeler@v7 with: configuration-path: .github/pr-labeler.yml diff --git a/.github/workflows/publish-ajan.yml b/.github/workflows/publish-ajan.yml new file mode 100644 index 000000000..00b803104 --- /dev/null +++ b/.github/workflows/publish-ajan.yml @@ -0,0 +1,235 @@ +# Standalone, manually-triggered publish of the @eserstack/ajan platform +# packages, DECOUPLED from the Integrity Pipeline's `validate` job. +# +# Why this exists: the main release flow gates the ajan platform publish behind +# `validate` (publish-ajan → compile-binaries → release-gate → validate). But +# `validate` runs `pnpm install --frozen-lockfile`, which fails whenever +# pkg/@eserstack/{ajan,cli}/package.json reference platform packages that aren't +# on npm yet — and pnpm can't lock packages that don't exist. That is a +# bootstrap deadlock: the release that would publish the packages can't run +# until they're already published. +# +# This workflow breaks the deadlock: it builds the ajan platform packages on +# demand, without `validate`, using `--no-frozen-lockfile`. It has two modes: +# +# bootstrap (default) — build all platforms and upload the generated tarballs +# as an artifact. Use this for the FIRST EVER publish of a package: npm ( +# unlike PyPI) cannot attach a trusted publisher to a package that does not +# exist yet, so OIDC can't create it (it 404s). Download the artifact and +# `npm publish` each package locally as the eserstack org owner. THEN add a +# GitHub-Actions trusted publisher to each package (repo eser/stack, workflow +# publish-ajan.yml) so future releases can go over OIDC token-lessly. +# +# publish — build all platforms and publish via OIDC (--provenance, no token). +# Use this once every target package exists AND has a trusted publisher +# configured. Versions already on npm are skipped; a real error fails the job. +# +# Either way, after the platform packages are on npm, run +# `pnpm install --lockfile-only` and push so the lockfile gains their specifiers +# and the main pipeline's `validate` passes — the normal release flow then +# self-sustains. +# +# Platforms come from NATIVE_TARGETS (pkg/@eserstack/ajan/targets.ts): +# darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-x64, plus wasm. +# win32-arm64 is intentionally absent — Go has no `-buildmode=c-shared` for +# windows/arm64 (WASM covers that platform). + +name: Publish ajan platform packages + +on: + workflow_dispatch: + inputs: + mode: + description: "bootstrap = build + upload tarballs for manual first publish; publish = build + publish via OIDC (needs trusted publishing already configured)" + type: choice + default: bootstrap + options: + - bootstrap + - publish + +permissions: + contents: read + +jobs: + build-darwin: + name: Build ajan (darwin) + runs-on: macos-latest + timeout-minutes: 10 + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" + cache: "pnpm" + + # build.ts imports @eserstack/standards (→ @std/path); under + # nodeModulesDir:manual deno needs node_modules populated to resolve it. + - name: Install dependencies (no frozen — platform pkgs not yet published) + run: pnpm install --no-frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Build aarch64-darwin (native) + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=aarch64-darwin + + - name: Build x86_64-darwin (cross-compile via clang -arch x86_64) + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-darwin + + - name: Upload darwin dist + uses: actions/upload-artifact@v7 + with: + name: ajan-dist-darwin + path: | + pkg/@eserstack/ajan/dist/aarch64-darwin/ + pkg/@eserstack/ajan/dist/x86_64-darwin/ + + build-and-publish: + name: Build (linux/windows/wasm) & publish + needs: build-darwin + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + + permissions: + contents: read + id-token: write # npm provenance + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.9.5 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: "latest" + registry-url: "https://registry.npmjs.org" + cache: "pnpm" + + # --no-frozen-lockfile is deliberate: the platform packages this workflow + # publishes aren't on npm yet, so a frozen install would fail on their + # unresolved optionalDependencies — the very deadlock this workflow breaks. + - name: Install dependencies (no frozen — platform pkgs not yet published) + run: pnpm install --no-frozen-lockfile + + - name: Setup Go + uses: actions/setup-go@v7 + with: + # go.mod, not "stable": a new Go minor lands on the runner before the + # pinned golangci-lint can parse it, and Validate then dies inside + # staticcheck on the standard library rather than on our code. + go-version-file: go.mod + cache: true + cache-dependency-path: go.sum + + - name: Install cross-compilers + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq \ + gcc-x86-64-linux-gnu \ + gcc-mingw-w64-x86-64 + + - name: Download darwin dist + uses: actions/download-artifact@v8 + with: + name: ajan-dist-darwin + path: pkg/@eserstack/ajan/dist/ + + - name: Build Go shared libraries (linux + windows) + run: | + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=aarch64-linux + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-linux + deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --target=x86_64-windows + + - name: Build Go WASM modules + run: deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --wasm + + - name: Stage platform libraries into their packages + run: deno run --allow-all pkg/@eserstack/ajan/npm/generate-packages.ts + + # ── bootstrap mode: upload the generated tarballs so they can be published + # manually (npm login as the eserstack org owner). Needed for the FIRST + # publish of any package, because npm can't pre-configure a trusted + # publisher for a not-yet-existing package, so OIDC can't create it. + # The artifact's least-common-ancestor is pkg/@eserstack/, so it extracts + # to `ajan-/` dirs at the top level — ready for a publish loop. + - name: Upload generated ajan platform packages (manual first publish) + if: inputs.mode == 'bootstrap' + uses: actions/upload-artifact@v7 + with: + name: ajan-npm-packages + path: | + pkg/@eserstack/ajan-*/package.json + pkg/@eserstack/ajan-*/README.md + pkg/@eserstack/ajan-*/libeser_ajan.* + pkg/@eserstack/ajan-*/*.wasm + if-no-files-found: error + + # ── publish mode: publish each generated package whose exact version isn't + # on npm yet. A real publish error FAILS the job (only already-published + # versions are skipped) so silent publish failures can't hide again. + # + # Auth: this mirrors build.yml's `publish` job exactly — npm OIDC trusted + # publishing (id-token:write + --provenance + setup-node registry-url), + # NOT a token. There is no NPM_TOKEN secret in this project. Do NOT add + # `env: NODE_AUTH_TOKEN` here: an empty token writes `_authToken=` into the + # .npmrc, which makes pnpm attempt (and fail) token auth — ENEEDAUTH — + # instead of falling back to OIDC. Requires each target package to already + # exist AND have a trusted publisher configured (use bootstrap mode first). + - name: Publish ajan platform packages to npm (OIDC) + if: inputs.mode == 'publish' + run: | + set -euo pipefail + published=0 + skipped=0 + for dir in pkg/@eserstack/ajan-*/; do + [ -f "$dir/package.json" ] || continue + name=$(node -p "require('./$dir/package.json').name") + version=$(node -p "require('./$dir/package.json').version") + if npm view "$name@$version" version >/dev/null 2>&1; then + echo "✓ $name@$version already on npm — skipping" + skipped=$((skipped + 1)) + continue + fi + echo "→ Publishing $name@$version ..." + (cd "$dir" && pnpm publish --provenance --access public --no-git-checks) + published=$((published + 1)) + done + echo "Done: $published published, $skipped already-present." diff --git a/.github/workflows/sync-issue-labels.yml b/.github/workflows/sync-issue-labels.yml index a3c81fb92..31f1cf0f8 100644 --- a/.github/workflows/sync-issue-labels.yml +++ b/.github/workflows/sync-issue-labels.yml @@ -17,7 +17,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 - name: Sync Issue Labels uses: EndBug/label-sync@v2 diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml index 782821352..0b9a465e2 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -13,12 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 + with: + fetch-depth: 1 - name: Setup Deno uses: denoland/setup-deno@v2 with: - deno-version: v2.x + deno-version: v2.9.5 - name: Update contributor list run: deno task cli codebase gh contributors --commit diff --git a/.gitignore b/.gitignore index 264e5fd24..e4f215d42 100644 --- a/.gitignore +++ b/.gitignore @@ -23,17 +23,50 @@ Thumbs.db # coverage files /etc/coverage/* /etc/coverage.lcov +/coverage.out # package managers -node_modules/* +node_modules/ # temporary files /etc/temp/* !/etc/temp/.gitkeep +# Go builds +/bin/ + # npm builds -/pkg/@eser/cli/dist/* +/pkg/@eserstack/cli/dist/* +/pkg/@eserstack/laroux-server/dist/* +/pkg/@eserstack/noskills/dist/* # Nix result result-* + +# Per-package JSR publish manifests are generated from package.json + VERSION +# by etc/scripts/gen-jsr-manifests.ts. Hand-editing them reintroduces the +# exports-map duplication (and the drift) they were removed to prevent. +/pkg/@eserstack/*/deno.json + +# `deno compile` writes a bundle next to the CWD while it works and removes it +# on success. A failed or interrupted compile leaves it behind, where the +# repo validators -- which now scan untracked files -- would flag it as an +# unlicensed, non-kebab-case source file. +.deno_compile_bundle_*.mjs + +# `go build ./cmd/` without -o drops the binary in the repo root. +/noskills-server + +# Generated API reference: `deno task cli docs` rebuilds it wholesale from the +# JSDoc in pkg/@eserstack/*/mod.ts. Committing 3,700+ files of output meant it +# was perpetually stale (last refreshed 2026-06-07, months behind the source it +# documents), every regeneration produced an unreviewable diff, and the +# formatting validators had to be hand-satisfied each time. Hand-written docs +# under docs/adr/ and docs/schema/ stay tracked. +docs/api/ + +# Platform library packages are workspace members whose only payload is a build +# output written by pkg/@eserstack/ajan/scripts/build.ts; the manifests are committed. +/pkg/@eserstack/ajan-*/libeser_ajan.* +/pkg/@eserstack/ajan-wasm/*.wasm diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 000000000..b0fe87b36 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,44 @@ +version: "2" + +run: + go: "1.26" + timeout: 5m + +linters: + enable: + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - gocritic + - misspell + - unconvert + - unparam + - nakedret + - prealloc + - errorlint + - gosec + + settings: + gocritic: + # Flags parameters of 80+ bytes passed by value; see go-practices: + # Value vs Pointer Parameters. 80 is gocritic's default threshold. + enabled-checks: + - hugeParam + gosec: + excludes: + - G115 + - G101 # Example/test credentials — not real secrets + - G204 # exec.Command("git", ...) is intentional throughout; production sites have //nolint:gosec + - G301 # 0o755 is appropriate for project/test dirs; production sites have //nolint:gosec + - G306 # 0o644 is appropriate for text files; production sites have //nolint:gosec + +issues: + max-issues-per-linter: 50 + max-same-issues: 10 + +formatters: + enable: + - gofmt + - goimports diff --git a/.manifest.yml b/.manifest.yml deleted file mode 100644 index d8e662fb8..000000000 --- a/.manifest.yml +++ /dev/null @@ -1,186 +0,0 @@ -stack: - - javascript - - golang - -workflows: - - id: default - on: [precommit, prepush] - steps: - # Internal tools - - validate-eof - - validate-trailing-whitespace - - validate-bom - - validate-line-endings - - validate-large-files: - maxKb: 1024 - - validate-case-conflict - - validate-merge-conflict - - validate-json: - exclude: - - "etc/templates/vite-app/tsconfig.app.json" - - "etc/templates/cf-workers-app/tsconfig.json" - - "tsconfig.json" - - validate-toml - - validate-yaml: - allowMultipleDocuments: true - - validate-symlinks - - validate-shebangs - - validate-secrets: - allowMissingCredentials: true - exclude: - - ".claude/skills/" - - validate-filenames: - rules: - - directory: "apps/services/" - convention: "snake_case" - exclude: - - "Dockerfile" - - "Makefile" - - "README.md" - - directory: "*" - convention: "kebab-case" - exclude: - - ".claude/" - - ".github/" - - ".git/COMMIT_EDITMSG" - - "AGENTS.md" - - "CHANGELOG.md" - - "CLAUDE.md" - - "Makefile" - - "VERSION" - - "apps/services/" - - "docs/" - - "etc/coverage/" - - "etc/temp/" - - "etc/templates/*/Makefile" - - "etc/templates/*/README.md" - - "Dockerfile" - - "LICENSE" - - "README.md" - - "TODOS.md" - - "pkg/*/README.md" - - "*_test.ts" - - "*_test.go" - - validate-submodules - - validate-licenses - - # Deno checks - - shell: - name: deno-formatter - command: deno fmt --check - fixCommand: deno fmt - - shell: - name: deno-linter - command: deno lint - fixCommand: deno lint --fix - - shell: - name: deno-type-check - command: deno check ./pkg/@eser/*/mod.ts ./pkg/@cool/*/mod.ts - timeout: 120 - - shell: - name: deno-tests - command: deno test --allow-sys --allow-net --allow-env --allow-read --allow-write --allow-run --allow-ffi --parallel --trace-leaks --coverage=./etc/coverage/ - timeout: 300 - - shell: - name: deno-publish-check - command: deno publish --dry-run --allow-dirty - timeout: 120 - - # Go checks (apps/services) - - shell: - name: go-check - command: "go vet ./... && go build ./..." - workingDirectory: ./apps/services/ - timeout: 120 - - shell: - name: go-lint - command: "go tool golangci-lint run ./..." - fixCommand: "gofmt -w . && go tool golangci-lint run --fix ./..." - workingDirectory: ./apps/services/ - timeout: 120 - - shell: - name: go-tests - command: "go test -race ./..." - workingDirectory: ./apps/services/ - timeout: 300 - - - id: commit-validation - on: [commitmsg] - steps: - - validate-commit-msg: - forceScope: true - strict: true - types: - - ci - - chore - - docs - - feat - - fix - - perf - - refactor - - revert - - test - -scripts: - # Validation - ok: eser workflows run -e precommit - fix: eser workflows run -e precommit --fix - - # Setup - init: deno install && eser codebase install - - # Testing - test: - command: deno test --allow-sys --allow-net --allow-env --allow-read --allow-write --allow-run --allow-ffi --parallel --trace-leaks --coverage=./etc/coverage/ - description: Run tests with coverage - test-watch: - command: deno test --allow-sys --allow-net --allow-env --allow-read --allow-write --allow-run --allow-ffi --parallel --watch - description: Run tests in watch mode - - # Code quality - lint: deno lint - fmt: deno fmt - check: deno check ./pkg/@eser/*/mod.ts ./pkg/@cool/*/mod.ts - - # Build - build: deno run --allow-all ./pkg/@eser/cli/scripts/npm-build.ts - compile: deno run --allow-all ./pkg/@eser/cli/scripts/compile.ts - - # Documentation - docs: "rm -rf ./docs/ && deno doc --name='eserstack' --output=./docs/ --html ./pkg/@eser/*/mod.ts ./pkg/@cool/*/mod.ts" - docs-lint: deno doc --lint ./pkg/@eser/*/mod.ts ./pkg/@cool/*/mod.ts - - # Docker - container-build: docker build -t eservm . - container-run: docker run --interactive --tty --rm eservm - - # Maintenance - clean: "rm -rf ./etc/coverage/* ./etc/coverage.lcov ./deno.lock && deno check ./pkg/@eser/*/mod.ts ./pkg/@cool/*/mod.ts" - repl: deno repl --allow-all --unstable-bundle --eval-file=./repl-init.ts - - # Coverage - coverage: deno coverage ./etc/coverage/ - coverage-gen: deno coverage ./etc/coverage --lcov --output=./etc/coverage.lcov - - # Release - version-bump: eser codebase versions - tag: eser codebase gh release-tag - release-notes: eser codebase gh release-notes - changelog: eser codebase changelog-gen - - # Go shortcuts - go-ok: - command: make ok - workingDirectory: ./apps/services/ - go-test: - command: make test-coverage - workingDirectory: ./apps/services/ - go-lint: - command: make lint - workingDirectory: ./apps/services/ - go-fmt: - command: make fix - workingDirectory: ./apps/services/ - go-build: - command: make build - workingDirectory: ./apps/services/ diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index da39e4ffa..000000000 --- a/.mcp.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mcpServers": {} -} diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 000000000..c8d999360 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,29 @@ +# mise (formerly rtx) configuration for local development +# For Nix users: flake.nix already provides deno, go, node, git via devShell. +# This file is for contributors who prefer mise over Nix. + +[tools] +node = "26" +bun = "1.3" +deno = "2.9.5" +go = "1.25" + +[env] +# Optional: ensure Go shared library is found during development +# ESER_AJAN_LIB_PATH is auto-resolved by the FFI module + +[tasks.build-go] +run = "deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts" +description = "Build Go shared library for current platform" + +[tasks.build-go-all] +run = "deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --all" +description = "Build Go shared library for all platforms" + +[tasks.build-wasm] +run = "deno run --allow-all pkg/@eserstack/ajan/scripts/build.ts --wasm" +description = "Build Go WASM modules" + +[tasks.test-ffi] +run = "deno task cli go version" +description = "Verify FFI bridge works" diff --git a/.vscode/settings.json b/.vscode/settings.json index d04200da0..01e739162 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,23 +14,6 @@ "deno.enable": true, "deno.lint": true, "deno.suggest.imports.autoDiscover": true, - "javascript.suggest.autoImports": true, - "javascript.suggest.completeFunctionCalls": true, - "javascript.suggest.completeJSDocs": true, - "javascript.suggest.enabled": true, - "javascript.suggest.includeAutomaticOptionalChainCompletions": true, - "javascript.suggest.includeCompletionsForImportStatements": true, - "javascript.suggest.jsdoc.generateReturns": true, - "javascript.suggest.names": true, - "javascript.suggest.paths": true, - "typescript.suggest.autoImports": true, - "typescript.suggest.completeFunctionCalls": true, - "typescript.suggest.completeJSDocs": true, - "typescript.suggest.enabled": true, - "typescript.suggest.includeAutomaticOptionalChainCompletions": true, - "typescript.suggest.includeCompletionsForImportStatements": true, - "typescript.suggest.jsdoc.generateReturns": true, - "typescript.suggest.paths": true, "editor.defaultFormatter": "denoland.vscode-deno", "yaml.schemas": { "https://json.schemastore.org/github-workflow.json": "./.github/workflows/*.yml" diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index 681311eb9..000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..aae05e7ef --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,86 @@ +# eserstack Development Guidelines + +All development rules live in `.agents/skills/`. Load relevant skills before +starting any task. `.claude` is a symlink to `.agents`, so Claude Code reads the +same skills, settings and hooks; edit files under `.agents/` only. + +## Skill Map + +| When you need to... | Load skill | +| ----------------------------------------- | --------------------------- | +| Discover which skills apply | `eser-rules-manager` | +| Releases, publishing, CI/CD, deploys | `release-and-ci` | +| Design structure, test strategy, ADRs | `architecture-guidelines` | +| Write or review code quality | `coding-practices` | +| Write or edit READMEs and docs | `documentation-conventions` | +| Apply FP patterns, composition, async | `design-principles` | +| Write JS/TS, modules, React components | `javascript-practices` | +| Write Go services | `go-practices` | +| Handle secrets, auth, validation | `security-practices` | +| Clarify, roles, approvals, tasks, commits | `workflow-practices` | +| Interview a plan until nothing is open | `enrichment` | +| Hand the session over (`/handoff`) | `handoff` | +| Tooling, dependencies, naming, packages | `tooling-standards` | + +## Repository + +A Deno/TypeScript and Go monorepo. + +- `pkg/@eserstack//`: 48 TypeScript packages (42 libraries and six + `ajan-*` platform packages), all on the shared version in `VERSION`. Every + package goes to JSR; `eser`, `noskills`, `laroux` and the `ajan-*` packages + also go to npm. +- Per-package `deno.json` is generated by `etc/scripts/gen-jsr-manifests.ts` + from `package.json`, `VERSION` and `deno.extra.json`, and is gitignored. +- Go: the root module `github.com/eser/stack` (`go.mod`, `Makefile`, + `.golangci.yaml` at the root; framework in `pkg/ajan/`, the server in + `cmd/noskills-server/`) and the FFI bridge `pkg/@eserstack/ajan/` + (`github.com/eser/ajan`), a second module with its own `go.mod`. +- Snake_case file names apply under `cmd/`, `pkg/ajan/` and + `pkg/@eserstack/ajan/`; kebab-case everywhere else. +- `.eser/manifest.yml` holds the precommit validators and every + `deno task cli +Security audit finding (run stack-run-1, ref f7394953). Severity: medium (likelihood low, impact high). Fingerprint: laroux-server/adapters/react/inline-rsc-emitter/unescaped-json-in-executing-script. Full record, bounded reproduction and proposed code are in security-report.md. + +chunkToInlineScript in pkg/@eserstack/laroux-server/adapters/react/inline-rsc-emitter.ts wraps JSON.stringify(chunk) directly in . JSON.stringify leaves < and / untouched, so any RSC chunk whose value contains {s} tree emits both copies of s raw inside the executing script), so a page that renders a request- or store-derived string through createInlineRSCStream emits attacker markup. The helpers are part of the package's public ./adapters/react export and the shipped client (laroux-react/client.ts:1581) installs the matching __RSC_CHUNK__ consumer, but no HTTP handler inside laroux-server calls them, so the path is reachable only for consumers of that API. + +Root cause: inline-rsc-emitter.ts builds an executable script body from JSON without the script-context escaping (< to \u003c, U+2028/U+2029) that JSON-in-script embedding requires; the other embedding sites in the package (ssr-renderer.ts:750, runtime/server.ts:105) at least replace self.__RSC_CHUNK__(${chunkJson})` with chunkJson = JSON.stringify(chunk) (line 43) and no escaping; a value containing terminates the element and the rest is parsed as markup. + +Conditions: +- system_configuration: The consuming application must use the exported createInlineRSCStream / createInlineTransformStream / chunkToInlineScript API to stream RSC chunks into its HTML; laroux-server's own runtime handlers do not call it. +- data_state: A server component reached through that stream must render a string (text child or string prop) containing self.__RSC_CHUNK__({"type":"J","id":5,"value":""}) with two "}). TRANSFORM: identical output from createInlineTransformStream. FULL: createInlineRSCStream on a div with the payload as title prop and child emitted ","children":""}}) followed by the element chunk, two raw & with \u003c \u003e \u0026 and U+2028/U+2029 with their escapes); the result is still a valid JS object literal, so the client's __RSC_CHUNK__ handler is unchanged. Escaping < also neutralizes + +## Acceptance Criteria + +- [x] #1 The invariant holds in code: Every RSC chunk embedded in HTML is encoded so that no byte sequence in a chunk value can end the script element or change how the page parses; the emitter is safe for arbitrary string values by construction. +- [x] #2 A regression test reproduces the audit input from security-report.md and fails on the current code, then passes after the fix +- [x] #3 The change stays within the files the fix needs (expected: pkg/@eserstack/laroux-server/adapters/react/inline-rsc-emitter.ts); any wider change is noted in the implementation notes +- [x] #4 deno task cli ok passes + + +## Implementation Notes + + +New adapters/react/script-json.ts exports escapeJsonForScript, which turns < > & into \u003c \u003e \u0026 and U+2028/U+2029 into escapes. The result is still valid JSON and a valid JS expression, so the client handlers are unchanged. chunkToInlineScript (the reported sink) now escapes every chunk, and the four other JSON-in-script sites that only replaced diff --git a/backlog/tasks/task-13 - Stop-sending-server-error-stacks-to-clients-in-RSC-error-chunks.md b/backlog/tasks/task-13 - Stop-sending-server-error-stacks-to-clients-in-RSC-error-chunks.md new file mode 100644 index 000000000..d37a9509e --- /dev/null +++ b/backlog/tasks/task-13 - Stop-sending-server-error-stacks-to-clients-in-RSC-error-chunks.md @@ -0,0 +1,65 @@ +--- +id: TASK-13 +title: Stop sending server error stacks to clients in RSC error chunks +status: Done +assignee: [] +created_date: '2026-09-25 00:26' +updated_date: '2026-09-25 01:12' +labels: + - security + - laroux-server +dependencies: [] +references: + - pkg/@eserstack/laroux-server/runtime/server.ts + - pkg/@eserstack/laroux-server/adapters/react/rsc-handler.ts + - pkg/@eserstack/laroux-server/adapters/react/rsc-flight-renderer.ts + - pkg/@eserstack/laroux-server/adapters/react/ssr-renderer.ts + - security-report.md +priority: medium +ordinal: 13000 +--- + +## Description + + +Security audit finding (run stack-run-1, ref f7394953). Severity: medium (likelihood medium, impact medium). Fingerprint: laroux-server/react/error-chunk-stack-to-client. Full record, bounded reproduction and proposed code are in security-report.md. + +When any server component (function, async, or forwardRef) throws or rejects during render, the laroux React adapter emits an RSC 'E' chunk whose value is {message: error.message, stack: error.stack}. The chunk is streamed verbatim to the requester of GET /rsc?pathname=... (registered unconditionally in every mode, with Access-Control-Allow-Origin: *) and, under the default ssr config (mode 'always', streamMode 'streaming-optimal'), embedded in the page's - - - - -
-
-
-
N
-
- appRuntime - -
No documentation available
-
v
-
- appRuntime.createAppRuntimeState - -
No documentation available
-
T
-
- appRuntime.Module - -
No documentation available
-
N
-
- bundler - -
No documentation available
-
c
-
- bundler.AotSnapshot - -
No documentation available
-
T
-
- bundler.AotSnapshotState - -
No documentation available
-
I
-
- bundler.Builder - -
No documentation available
-
T
-
- bundler.BuildSnapshot - -
No documentation available
-
T
-
- bundler.BuildSnapshotSerialized - -
No documentation available
-
v
-
- bundler.createAotSnapshotState - -
No documentation available
-
v
-
- bundler.createEsbuildBuilderState - -
No documentation available
-
v
-
- bundler.createEsbuildSnapshotState - -
No documentation available
-
c
-
- bundler.EsbuildBuilder - -
No documentation available
-
T
-
- bundler.EsbuildBuilderState - -
No documentation available
-
c
-
- bundler.EsbuildSnapshot - -
No documentation available
-
T
-
- bundler.EsbuildSnapshotState - -
No documentation available
-
v
-
- bundler.loadAotSnapshot - -
No documentation available
-
N
-
- collector - -
No documentation available
-
v
-
- collector.buildManifest - -
No documentation available
-
v
-
- collector.buildManifestFile - -
No documentation available
-
v
-
- collector.collectExports - -
No documentation available
-
T
-
- collector.ExportItem - -
No documentation available
-
v
-
- collector.specifierToIdentifier - -
No documentation available
-
f
-
- collector.walkFiles - -
No documentation available
-
v
-
- collector.writeManifestToString - -
No documentation available
-
N
-
- config - -
No documentation available
-
c
-
- config.Config - -
No documentation available
-
T
-
- config.ConfigItem - -
No documentation available
-
T
- -
T
-
- config.ConfigState - -
No documentation available
-
v
-
- config.createConfigState - -
No documentation available
-
v
-
- config.createDotenvSourceState - -
No documentation available
-
T
-
- config.DotenvSourceState - -
No documentation available
-
T
-
- config.Provider - -
No documentation available
-
T
-
- config.RequestContext - -
No documentation available
-
T
-
- config.Source - -
No documentation available
-
T
-
- config.SourceState - -
No documentation available
-
N
-
- di - -
No documentation available
-
T
-
- di.ClassService - -
No documentation available
-
v
-
- di.di - -
No documentation available
-
T
-
- di.Factory - -
No documentation available
-
T
-
- di.FunctionService - -
No documentation available
-
v
-
- di.inject - -
No documentation available
-
v
-
- di.injectable - -
No documentation available
-
T
-
- di.PromisableBuilder - -
No documentation available
-
c
- -
v
-
- di.registry - -
No documentation available
-
c
- -
T
-
- di.ServiceDescriptor - -
No documentation available
-
T
-
- di.ServiceKey - -
No documentation available
-
T
-
- di.ServiceRegistry - -
No documentation available
-
I
- -
T
-
- di.ServiceResolution - -
No documentation available
-
v
-
- di.services - -
No documentation available
-
T
-
- di.ServiceScope - -
No documentation available
-
I
-
- di.ServiceScopeQueryable - -
No documentation available
-
T
-
- di.ServiceType - -
No documentation available
-
T
-
- di.ServiceTypeKey - -
No documentation available
-
v
-
- di.ServiceTypes - -
No documentation available
-
T
-
- di.ServiceValue - -
No documentation available
-
T
-
- di.Value - -
No documentation available
-
N
-
- events - -
No documentation available
-
v
-
- events.createDispatcherState - -
No documentation available
-
v
-
- events.createRegistryState - -
No documentation available
-
c
-
- events.Dispatcher - -
No documentation available
-
v
-
- events.dispatcher - -
No documentation available
-
T
-
- events.DispatcherState - -
No documentation available
-
T
-
- events.EventDispatcher - -
No documentation available
-
T
-
- events.EventDispatcherState - -
No documentation available
-
T
-
- events.EventRegistry - -
No documentation available
-
T
-
- events.EventRegistryState - -
No documentation available
-
I
-
- events.EventRegistryWritable - -
No documentation available
-
v
-
- events.events - -
No documentation available
-
T
-
- events.Factory - -
No documentation available
-
c
-
- events.Registry - -
No documentation available
-
v
-
- events.registry - -
No documentation available
-
T
-
- events.RegistryState - -
No documentation available
-
N
-
- fp - -
No documentation available
-
v
-
- fp.appendToArray - -
No documentation available
-
v
-
- fp.appendToObject - -
No documentation available
-
v
-
- fp.associateArray - -
No documentation available
-
v
-
- fp.associateObject - -
No documentation available
-
T
-
- fp.Callback - -
No documentation available
-
v
-
- fp.compose - -
No documentation available
-
v
-
- fp.curry - -
No documentation available
-
v
-
- fp.curryRight - -
No documentation available
-
v
-
- fp.decorate - -
No documentation available
-
T
-
- fp.Decorated - -
No documentation available
-
v
-
- fp.deepCopy - -
No documentation available
-
v
-
- fp.deepCopy2 - -
No documentation available
-
v
-
- fp.deepMerge - -
No documentation available
-
v
-
- fp.dispatcher - -
No documentation available
-
v
-
- fp.distinctArray - -
No documentation available
-
v
-
- fp.distinctObject - -
No documentation available
-
v
-
- fp.dropFromArray - -
No documentation available
-
v
-
- fp.dropFromObject - -
No documentation available
-
v
-
- fp.filterArray - -
No documentation available
-
v
-
- fp.filterObject - -
No documentation available
-
v
-
- fp.iterate - -
No documentation available
-
T
-
- fp.LoggerType - -
No documentation available
-
T
-
- fp.LogType - -
No documentation available
-
v
-
- fp.mapArray - -
No documentation available
-
v
-
- fp.mapObject - -
No documentation available
-
v
-
- fp.match - -
No documentation available
-
v
-
- fp.mergeArrays - -
No documentation available
-
v
-
- fp.mergeObjects - -
No documentation available
-
v
-
- fp.mutate - -
No documentation available
-
T
-
- fp.MutatorType - -
No documentation available
-
T
-
- fp.NextType - -
No documentation available
-
T
-
- fp.Pattern - -
No documentation available
-
v
-
- fp.pickFromArray - -
No documentation available
-
T
-
- fp.PickFromArrayResult - -
No documentation available
-
v
-
- fp.pickFromObject - -
No documentation available
-
T
-
- fp.PickFromObjectResult - -
No documentation available
-
v
-
- fp.pipe - -
No documentation available
-
v
-
- fp.prependToArray - -
No documentation available
-
v
-
- fp.prependToObject - -
No documentation available
-
v
-
- fp.removeFirstMatchFromArray - -
No documentation available
-
v
-
- fp.removeFirstMatchFromObject - -
No documentation available
-
v
-
- fp.removeIndexFromArray - -
No documentation available
-
v
-
- fp.removeKeyFromObject - -
No documentation available
-
v
-
- fp.removeValueFromArray - -
No documentation available
-
v
-
- fp.removeValueFromObject - -
No documentation available
-
v
-
- fp.reverseArray - -
No documentation available
-
v
-
- fp.reverseObject - -
No documentation available
-
v
-
- fp.splitArray - -
No documentation available
-
T
-
- fp.SplitArrayResult - -
No documentation available
-
v
-
- fp.splitObject - -

The splitObject function splits an object into two parts based on a given number, with the first -part containing the specified number of items and the second part containing the remaining items.

-
-
T
-
- fp.SplitObjectResult - -
No documentation available
-
v
-
- fp.takeFromArray - -
No documentation available
-
v
-
- fp.takeFromObject - -
No documentation available
-
v
-
- fp.wth - -
No documentation available
-
v
-
- fp.wthout - -
No documentation available
-
N
-
- functions - -
No documentation available
-
T
-
- functions.AcceptableResult - -
No documentation available
-
T
-
- functions.Context - -
No documentation available
-
T
-
- functions.ExtraData - -
No documentation available
-
v
-
- functions.Fail - -
No documentation available
-
T
-
- functions.Fn - -
No documentation available
-
v
-
- functions.fn - -
No documentation available
-
T
-
- functions.FnResult - -
No documentation available
-
v
-
- functions.Ok - -
No documentation available
-
T
-
- functions.Pipeline - -
No documentation available
-
T
-
- functions.Result - -
No documentation available
-
T
-
- functions.State - -
No documentation available
-
N
-
- logging - -
No documentation available
-
v
-
- logging.createLoggerState - -
No documentation available
-
v
-
- logging.current - -
No documentation available
-
v
-
- logging.DEFAULT_LEVEL - -
No documentation available
-
T
-
- logging.FormatterFn - -
No documentation available
-
v
-
- logging.jsonFormatter - -
No documentation available
-
c
- -
T
- -
N
-
- parsing - -
No documentation available
-
T
-
- parsing.ASTNode - -
No documentation available
-
v
-
- parsing.createTokenizerState - -
No documentation available
-
T
-
- parsing.Parser - -
No documentation available
-
T
-
- parsing.PatternFunction - -
No documentation available
-
T
-
- parsing.PatternFunctionResult - -
No documentation available
-
v
-
- parsing.sequence - -
No documentation available
-
v
-
- parsing.simpleTokens - -
No documentation available
-
T
-
- parsing.Token - -
No documentation available
-
v
-
- parsing.token - -
No documentation available
-
T
-
- parsing.TokenDefinitions - -
No documentation available
-
T
-
- parsing.TokenizerState - -
No documentation available
-
N
-
- standards - -
No documentation available
-
-
-
-
-
-
-
- - diff --git a/docs/comrak.css b/docs/comrak.css deleted file mode 100644 index 81e5c8d96..000000000 --- a/docs/comrak.css +++ /dev/null @@ -1 +0,0 @@ -.link{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.link:is(.dark *),.link:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.link:hover:is(.dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}:is(.markdown_summary,.markdown) a:not(.no_color){--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}:is(.markdown_summary,.markdown) a:not(.no_color):is(.dark *),:is(.markdown_summary,.markdown) a:not(.no_color):hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}:is(.markdown_summary,.markdown) a:not(.no_color):hover:is(.dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.markdown_summary{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));display:inline}.markdown_summary:is(.dark *){--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity))}.markdown_summary p{-webkit-line-clamp:4;-webkit-box-orient:vertical;max-width:100%;line-height:1.5;display:inline-block;overflow:hidden}.markdown_summary :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.25rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}.markdown_summary :not(pre)>code:is(.dark *){--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))}.copyButton svg.check{display:none}.copyButton.copied svg.copy{display:none}.copyButton.copied svg.check{display:block}.markdown{flex-shrink:1;min-width:0;max-width:75ch}.markdown>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}@media (min-width:640px){.markdown{max-width:640px}}@media (min-width:768px){.markdown{max-width:768px}}@media (min-width:1024px){.markdown{max-width:75ch}}.markdown summary h1,.markdown summary h2,.markdown summary h3,.markdown summary h4,.markdown summary h5,.markdown summary h6{display:inline-block}.markdown h1{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.25rem;line-height:1.75rem}.markdown h1:is(.dark *){--tw-border-opacity:1;border-color:rgb(68 64 60/var(--tw-border-opacity))}@media (min-width:768px){.markdown h1{font-size:1.5rem;line-height:2rem}}@media (min-width:1024px){.markdown h1{font-size:1.875rem;line-height:2.25rem}}.markdown h2{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.125rem;line-height:1.75rem}.markdown h2:is(.dark *){--tw-border-opacity:1;border-color:rgb(68 64 60/var(--tw-border-opacity))}@media (min-width:768px){.markdown h2{font-size:1.25rem;line-height:1.75rem}}@media (min-width:1024px){.markdown h2{font-size:1.5rem;line-height:2rem}}.markdown h3{font-weight:700}@media (min-width:768px){.markdown h3{font-size:1.125rem;font-weight:400;line-height:1.75rem}}@media (min-width:1024px){.markdown h3{font-size:1.25rem;font-weight:400;line-height:1.75rem}}.markdown h4{font-weight:600}@media (min-width:768px){.markdown h4{font-weight:700}}@media (min-width:1024px){.markdown h4{font-size:1.125rem;font-weight:400;line-height:1.75rem}}.markdown h5{font-style:italic}@media (min-width:768px){.markdown h5{font-weight:600}}@media (min-width:1024px){.markdown h5{font-weight:700}}@media (min-width:768px){.markdown h6{font-style:italic}}@media (min-width:1024px){.markdown h6{font-weight:600}}.markdown hr{--tw-border-opacity:1;border-color:rgb(120 113 108/var(--tw-border-opacity));margin:.5rem}.markdown hr:is(.dark *){--tw-border-opacity:1;border-color:rgb(168 162 158/var(--tw-border-opacity))}.markdown ol,.markdown ul{margin-left:1rem;list-style-position:outside}.markdown ol{list-style-type:decimal}.markdown ul{list-style-type:disc}.markdown :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.375rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}.markdown :not(pre)>code:is(.dark *){--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))}:is(.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6)>code{font-size:inherit!important}.markdown pre{--tw-border-opacity:1;border-top-width:1.5px;border-bottom-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity));border-radius:0;margin-left:-1rem;margin-right:-1rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}.markdown pre:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))}@media (min-width:768px){.markdown pre{border-width:1.5px;border-radius:.375rem;margin-left:0;margin-right:0}}.markdown pre>code:first-child{padding:1rem 1.5rem;display:block;overflow-x:auto}.markdown p{margin:.25rem 0}.markdown table{table-layout:auto;width:max-content;max-width:100%;display:block;overflow:auto}.markdown td{padding:.5rem}.markdown th{text-align:center;padding-top:.375rem;padding-bottom:.375rem;font-weight:700}.markdown th,.markdown td{--tw-border-opacity:1;border-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity))}.markdown th:is(.dark *),.markdown td:is(.dark *){--tw-border-opacity:1;border-color:rgb(68 64 60/var(--tw-border-opacity))}.markdown tr:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.markdown tr:nth-child(2n):is(.dark *){--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))}.markdown img{display:inline-block}.markdown .alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.markdown .alert{border-width:2px;border-radius:.5rem;padding:1rem 1.5rem}.markdown .alert div:first-child{align-items:center;gap:.375rem;font-weight:500;display:flex}.markdown .alert div:first-child svg{width:1.25rem;height:1.25rem}.markdown .alert-note{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity));background-color:#2563eb0d}.markdown .alert-note:is(.dark *){background-color:#2563eb0d}.markdown .alert-note div:first-child,.markdown .alert-note div:first-child:is(.dark *){stroke:#2563eb;--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.markdown .alert-tip{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity));background-color:#16a34a0d}.markdown .alert-tip:is(.dark *){background-color:#16a34a0d}.markdown .alert-tip div:first-child,.markdown .alert-tip div:first-child:is(.dark *){stroke:#16a34a;--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.markdown .alert-important{--tw-border-opacity:1;border-color:rgb(147 51 234/var(--tw-border-opacity));background-color:#9333ea0d}.markdown .alert-important:is(.dark *){background-color:#9333ea0d}.markdown .alert-important div:first-child,.markdown .alert-important div:first-child:is(.dark *){stroke:#9333ea;--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity))}.markdown .alert-warning{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity));background-color:#ca8a040d}.markdown .alert-warning:is(.dark *){background-color:#ca8a040d}.markdown .alert-warning div:first-child,.markdown .alert-warning div:first-child:is(.dark *){stroke:#ca8a04;--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity))}.markdown .alert-caution{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity));background-color:#dc26260d}.markdown .alert-caution:is(.dark *){background-color:#dc26260d}.markdown .alert-caution div:first-child,.markdown .alert-caution div:first-child:is(.dark *){stroke:#dc2626;--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.markdown code{width:100%}.markdown .highlight{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity));position:relative}.markdown .highlight:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.markdown .highlight .lineNumbers{--tw-border-opacity:1;border-right-width:2px;border-color:rgb(209 213 219/var(--tw-border-opacity));text-align:right;flex:none;padding-right:.25rem}.markdown .highlight .lineNumbers:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))} diff --git a/docs/darkmode_toggle.js b/docs/darkmode_toggle.js deleted file mode 100644 index e5939fc4c..000000000 --- a/docs/darkmode_toggle.js +++ /dev/null @@ -1,49 +0,0 @@ -function setTheme(theme, themeToggle) { - if (theme === "dark") { - document.documentElement.classList.add("dark"); - localStorage.setItem("theme", "dark"); - } else { - document.documentElement.classList.remove("dark"); - localStorage.setItem("theme", "light"); - } - - const darkIcon = themeToggle.children[0]; - const lightIcon = themeToggle.children[1]; - - if (theme === "dark") { - darkIcon.classList.add("hidden"); - lightIcon.classList.remove("hidden"); - } else { - darkIcon.classList.remove("hidden"); - lightIcon.classList.add("hidden"); - } -} - -window.addEventListener("load", () => { - const themeToggle = document.getElementById("theme-toggle"); - themeToggle.removeAttribute("style"); - - const storedTheme = localStorage.getItem("theme"); - const systemPrefersDark = - window.matchMedia("(prefers-color-scheme: dark)").matches; - - if (storedTheme) { - setTheme(storedTheme, themeToggle); - } else { - setTheme(systemPrefersDark ? "dark" : "light", themeToggle); - } - - if (themeToggle) { - themeToggle.addEventListener("click", () => { - const isDark = document.documentElement.classList.contains("dark"); - setTheme(isDark ? "light" : "dark", themeToggle); - }); - } -}); - -// prevent flash -const theme = localStorage.getItem("theme") || - (window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"); -document.documentElement.classList.add(theme); diff --git a/docs/fuse.js b/docs/fuse.js deleted file mode 100644 index 69737ed37..000000000 --- a/docs/fuse.js +++ /dev/null @@ -1,11 +0,0 @@ -// deno-fmt-ignore-file -// deno-lint-ignore-file -/** - * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) - * - * Copyright (c) 2023 Kiro Risk (http://kiro.me) - * All Rights Reserved. Apache Software License 2.0 - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?$.getFn:n,o=t.fieldNormWeight,c=void 0===o?$.fieldNormWeight:o;r(this,e),this.norm=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(F).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,m(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();m(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?$.getFn:r,o=n.fieldNormWeight,c=void 0===o?$.fieldNormWeight:o,a=new R({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(A)),a.setSources(t),a.create(),a}function N(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?$.distance:s,h=t.ignoreLocation,l=void 0===h?$.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}var W=32;function T(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?$.location:i,c=r.distance,a=void 0===c?$.distance:c,s=r.threshold,u=void 0===s?$.threshold:s,h=r.findAllMatches,l=void 0===h?$.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?$.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?$.includeMatches:v,y=r.ignoreLocation,p=void 0===y?$.ignoreLocation:y;if(t.length>W)throw new Error("Pattern length exceeds max of ".concat(W,"."));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,w=b,S=d>1||g,L=S?Array(M):[];(m=e.indexOf(t,w))>-1;){var _=N(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(_,x),w=m+k,S)for(var O=0;O=P;D-=1){var K=D-1,q=n[e.charAt(K)];if(S&&(L[K]=+!!q),z[D]=(z[D+1]<<1|1)&q,E&&(z[D]|=(j[D+1]|j[D])<<1|1|j[D+1]),z[D]&C&&(A=N(t,{errors:E,currentLocation:K,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=A,(w=K)<=b)break;P=Math.max(1,2*b-w)}}if(N(t,{errors:E+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;j=z}var B={isMatch:w>=0,score:Math.max(.001,A)};if(S){var J=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:$.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}(L,d);J.length?g&&(B.indices=J):B.isMatch=!1}return B}function z(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?$.location:o,a=i.threshold,s=void 0===a?$.threshold:a,u=i.distance,h=void 0===u?$.distance:u,l=i.includeMatches,f=void 0===l?$.includeMatches:l,d=i.findAllMatches,v=void 0===d?$.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?$.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?$.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?$.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:z(e),startIndex:t})},x=this.pattern.length;if(x>W){for(var w=0,S=x%W,L=x-S;w1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?$.location:c,s=o.threshold,u=void 0===s?$.threshold:s,h=o.distance,l=void 0===h?$.distance:h,f=o.includeMatches,d=void 0===f?$.includeMatches:f,v=o.findAllMatches,g=void 0===v?$.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?$.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?$.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?$.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new D(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(K),X=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(K),Y=[B,X,U,V,H,G,J,Q],Z=Y.length,ee=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,te=new Set([Q.type,X.type]),ne=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?$.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?$.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?$.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?$.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?$.findAllMatches:f,v=n.location,g=void 0===v?$.location:v,y=n.threshold,p=void 0===y?$.threshold:y,m=n.distance,k=void 0===m?$.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(ee).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n;return ue(e)||(e=he(e)),function e(n){var i=Object.keys(n),o=function(e){return!!e[ae]}(n);if(!o&&i.length>1&&!ue(n))return e(he(n));if(function(e){return!g(e)&&b(e)&&!ue(e)}(n)){var c=o?n[ae]:i[0],a=o?n[se]:n[c];if(!m(a))throw new Error(function(e){return"Invalid value for key ".concat(e)}(c));var s={keyId:C(c),pattern:a};return r&&(s.searcher=ie(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];g(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u}(e)}function fe(e,t){var n=e.matches;t.matches=[],x(n)&&n.forEach((function(e){if(x(e.indices)&&e.indices.length){var n={indices:e.indices,value:e.value};e.key&&(n.key=e.key.src),e.idx>-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function de(e,t){t.score=e.score}var ve=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},$),i),this.options.useExtendedSearch,this._keyStore=new j(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof R))throw new Error("Incorrect 'index' type");this._myIndex=t||P(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){x(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{}).limit,n=void 0===t?-1:t,r=this.options,i=r.includeMatches,o=r.includeScore,c=r.shouldSort,a=r.sortFn,s=r.ignoreFieldNorm,u=m(e)?m(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return function(e,t){var n=t.ignoreFieldNorm,r=void 0===n?$.ignoreFieldNorm:n;e.forEach((function(e){var t=1;e.matches.forEach((function(e){var n=e.key,i=e.norm,o=e.score,c=n?n.weight:null;t*=Math.pow(0===o&&c?Number.EPSILON:o,(c||1)*(r?1:i))})),e.score=t}))}(u,{ignoreFieldNorm:s}),c&&u.sort(a),k(n)&&n>-1&&(u=u.slice(0,n)),function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?$.includeMatches:r,o=n.includeScore,c=void 0===o?$.includeScore:o,a=[];return i&&a.push(fe),c&&a.push(de),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}(u,this._docs,{includeMatches:i,includeScore:o})}},{key:"_searchStringList",value:function(e){var t=ie(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(x(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=le(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?$.getFn:n,i=t.fieldNormWeight,o=void 0===i?$.fieldNormWeight:i,c=e.keys,a=e.records,s=new R({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ve.config=$,function(){re.push.apply(re,arguments)}(ne),ve},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index e9b1cccfc..000000000 --- a/docs/index.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - eserstack documentation - - - - - - - - - - - - - diff --git a/docs/page.css b/docs/page.css deleted file mode 100644 index 5f4440fe7..000000000 --- a/docs/page.css +++ /dev/null @@ -1 +0,0 @@ -.mx-1{margin-left:.25rem;margin-right:.25rem}.block{display:block}.flex{display:flex}.hidden{display:none}.h-full{height:100%}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-4{gap:1rem}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.bg-transparent{background-color:#0000}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.leading-none{line-height:1}.blur{--tw-blur:blur(8px);filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}body{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}body:is(.dark *){--tw-bg-opacity:1;background-color:rgb(18 20 23/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.ddoc{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));align-items:flex-start;gap:1.5rem;padding:1rem;display:flex}.ddoc:is(.dark *){--tw-bg-opacity:1;background-color:rgb(18 20 23/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}@media (min-width:1024px){.ddoc:has(#categoryPanel){padding:.5rem}}@media (min-width:1024px){.ddoc:has(#categoryPanel)>div:not(#categoryPanel){padding-top:1rem}.ddoc:has(#categoryPanel) #topnav{margin-left:-1.5rem!important;padding-left:1.5rem!important}}.ddoc:not(:has(#categoryPanel)),.ddoc:has(#categoryPanel)>div:not(#categoryPanel){padding-top:.25rem;padding-left:1.5rem;padding-right:1.5rem}.ddoc>div:not(#categoryPanel){flex-direction:column;flex-grow:1;display:flex}#categoryPanel{box-sizing:border-box;flex-shrink:0;width:250px;height:100vh;margin-top:0;padding-top:0;position:sticky;top:0}@media not all and (min-width:1024px){#categoryPanel{display:none}}#categoryPanel>ul{max-height:100%;overflow-y:auto}#content{margin-top:1rem}#content>main{flex-direction:column;flex-grow:1;grid-column:1/-1;gap:.75rem;min-width:0;padding-bottom:0;display:flex}@media (min-width:768px){#content>main{padding-bottom:2rem}}@media (min-width:1024px){#content>main{padding-bottom:3rem}#content:has(.toc)>main{grid-column:span 3/span 3;grid-row-start:1}}#topnav{margin-left:-1rem;padding-left:1rem}#content,#topnav>div{flex-direction:row;justify-content:space-between;gap:2rem;display:flex}@media (min-width:1024px){#content,#topnav>div{gap:3rem}}.toc,#searchbar{flex-shrink:0;min-width:250px;max-width:300px}.toc{box-sizing:border-box;row-gap:1rem;height:fit-content;max-height:100vh;margin-top:-3.5rem;padding-top:3.5rem;position:sticky;top:0}@media not all and (min-width:1024px){.toc{grid-row-start:1}}@media not all and (min-width:640px){.toc{display:none}}@media (min-width:640px){.toc{flex-direction:column;display:flex}}@media (min-width:1024px){.toc{grid-column:span 1/-1}}.toc>div{max-height:100%}@media (min-width:1024px){.toc>div{overflow-y:auto}}.toc>div>:last-child{padding-bottom:1rem}#topnav{z-index:50;--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));background-color:#ffffff1a;height:3.5rem;padding-top:.75rem;padding-bottom:.75rem;position:sticky;top:0}#topnav:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity));color:#ffffffe6;background-color:#121417e6}#theme-toggle{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity));border-radius:.25rem;padding:.25rem}#theme-toggle:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}#theme-toggle:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}#theme-toggle:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}#theme-toggle *{width:1.25rem;height:1.25rem}.hover\:bg-stone-100:hover{--tw-bg-opacity:1;background-color:rgb(245 245 244/var(--tw-bg-opacity))}.dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:hover\:bg-stone-800:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))} diff --git a/docs/prism.css b/docs/prism.css deleted file mode 100644 index aa565f6f7..000000000 --- a/docs/prism.css +++ /dev/null @@ -1,6 +0,0 @@ -/* PrismJS 1.30.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+json+markdown+regex+rust+typescript -and https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+json+markdown+regex+rust+typescript -modified to remove extra padding, and add .dark prefix for darkmodes */ -code[class*=language-],pre[class*=language-]{color:#000;text-shadow:0 1px #fff;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;tab-size:4;hyphens:none;background:0 0;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;line-height:1.5}code[class*=language-] ::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}pre[class*=language-] ::-moz-selection{text-shadow:none;background:#b3d4fc}pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{white-space:normal;border-radius:.3em;padding:.1em}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:#ffffff80}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} -.dark code[class*=language-],.dark pre[class*=language-]{color:#f8f8f2;text-shadow:0 1px #0000004d;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;tab-size:4;hyphens:none;background:0 0;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;line-height:1.5}.dark pre[class*=language-]{overflow:auto}.dark :not(pre)>code[class*=language-],.dark pre[class*=language-]{background:#272822}.dark :not(pre)>code[class*=language-]{white-space:normal;border-radius:.3em;padding:.1em}.dark .token.cdata,.dark .token.comment,.dark .token.doctype,.dark .token.prolog{color:#8292a2}.dark .token.punctuation{color:#f8f8f2}.dark .token.namespace{opacity:.7}.dark .token.constant,.dark .token.deleted,.dark .token.property,.dark .token.symbol,.dark .token.tag{color:#f92672}.dark .token.boolean,.dark .token.number{color:#ae81ff}.dark .token.attr-name,.dark .token.builtin,.dark .token.char,.dark .token.inserted,.dark .token.selector,.dark .token.string{color:#a6e22e}.dark .language-css .token.string,.dark .style .token.string,.dark .token.entity,.dark .token.operator,.dark .token.url,.dark .token.variable{color:#f8f8f2}.dark .token.atrule,.dark .token.attr-value,.dark .token.class-name,.dark .token.function{color:#e6db74}.dark .token.keyword{color:#66d9ef}.dark .token.important,.dark .token.regex{color:#fd971f}.dark .token.bold,.dark .token.important{font-weight:700}.dark .token.italic{font-style:italic}.dark .token.entity{cursor:help} diff --git a/docs/prism.js b/docs/prism.js deleted file mode 100644 index 23bf91958..000000000 --- a/docs/prism.js +++ /dev/null @@ -1,15 +0,0 @@ -// MIT LICENSE -// Copyright (c) 2012 Lea Verou -/* PrismJS 1.29.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+json+markdown+regex+rust+typescript */ -var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); -Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; -!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; -Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; -!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i/g,(function(){return"(?:\\\\.|[^\\\\\n\r]|(?:\n|\r\n?)(?![\r\n]))"})),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var t="(?:\\\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\\\|\r\n`])+",a="\\|?__(?:\\|__)+\\|?(?:(?:\n|\r\n?)|(?![^]))".replace(/__/g,(function(){return t})),i="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\n|\r\n?)";n.languages.markdown=n.languages.extend("markup",{}),n.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:n.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+i+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+i+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(t),inside:n.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+i+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(t),alias:"important",inside:n.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:e("\\b__(?:(?!_)|_(?:(?!_))+_)+__\\b|\\*\\*(?:(?!\\*)|\\*(?:(?!\\*))+\\*)+\\*\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:e("\\b_(?:(?!_)|__(?:(?!_))+__)+_\\b|\\*(?:(?!\\*)|\\*\\*(?:(?!\\*))+\\*\\*)+\\*"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:e("(~~?)(?:(?!~))+\\2"),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:e('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)|[ \t]?\\[(?:(?!\\]))+\\])'),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(e){["url","bold","italic","strike","code-snippet"].forEach((function(t){e!==t&&(n.languages.markdown[e].inside.content.inside[t]=n.languages.markdown[t])}))})),n.hooks.add("after-tokenize",(function(n){"markdown"!==n.language&&"md"!==n.language||function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t",quot:'"'},l=String.fromCodePoint||String.fromCharCode;n.languages.md=n.languages.markdown}(Prism); -!function(a){var e={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},n=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/,t="(?:[^\\\\-]|"+n.source+")",s=RegExp(t+"-"+t),i={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"};a.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:s,inside:{escape:n,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":e,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:n}},"special-escape":e,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":i}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:n,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|)*\\*/",t=0;t<2;t++)a=a.replace(//g,(function(){return a}));a=a.replace(//g,(function(){return"[^\\s\\S]"})),e.languages.rust={comment:[{pattern:RegExp("(^|[^\\\\])"+a),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(Prism); -!function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var s=e.languages.extend("typescript",{});delete s["class-name"],e.languages.typescript["class-name"].inside=s,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:s}}}}),e.languages.ts=e.languages.typescript}(Prism); diff --git a/docs/reset.css b/docs/reset.css deleted file mode 100644 index 2f99376a2..000000000 --- a/docs/reset.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{color:inherit;border-top-width:1px;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } diff --git a/docs/schema/manifest.json b/docs/schema/manifest.json new file mode 100644 index 000000000..57d9681b0 --- /dev/null +++ b/docs/schema/manifest.json @@ -0,0 +1,114 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/eser/stack/main/docs/schema/manifest.json", + "title": "noskills manifest (.eser/manifest.yml)", + "description": "Schema for the .eser/manifest.yml project configuration file used by noskills.", + "type": "object", + "properties": { + "concerns": { + "type": "array", + "description": "List of concern IDs from .eser/concerns/ to apply to all specs.", + "items": { "type": "string" }, + "default": [] + }, + "tools": { + "type": "array", + "description": "Coding assistant tools available in this project.", + "items": { + "type": "string", + "enum": [ + "claude-code", + "cursor", + "kiro", + "copilot", + "windsurf", + "opencode", + "codex", + "copilot-cli" + ] + }, + "default": [] + }, + "providers": { + "type": "array", + "description": "AI provider identifiers (e.g. \"anthropic\", \"openai\").", + "items": { "type": "string" }, + "default": [] + }, + "project": { + "type": "object", + "description": "Detected tech-stack traits for this project.", + "properties": { + "languages": { + "type": "array", + "description": "Programming languages used.", + "items": { "type": "string" }, + "default": [] + }, + "frameworks": { + "type": "array", + "description": "Frameworks and runtimes used.", + "items": { "type": "string" }, + "default": [] + }, + "ci": { + "type": "array", + "description": "CI/CD systems in use (e.g. \"github-actions\").", + "items": { "type": "string" }, + "default": [] + }, + "testRunner": { + "type": ["string", "null"], + "description": "Primary test runner (e.g. \"deno\", \"jest\", \"go\").", + "default": null + } + }, + "additionalProperties": false + }, + "maxIterationsBeforeRestart": { + "type": "integer", + "description": "Maximum Claude iterations in one turn before prompting a context reset. 0 means unlimited.", + "minimum": 0, + "default": 10 + }, + "verifyCommand": { + "type": ["string", "null"], + "description": "Shell command run after each execution turn to verify correctness (e.g. \"deno task ok\").", + "default": null + }, + "allowGit": { + "type": "boolean", + "description": "Whether Claude is permitted to run destructive git write commands (commit, push, checkout --). Defaults to false for safety.", + "default": false + }, + "command": { + "type": "string", + "description": "Custom shell command used to launch the coding assistant (overrides the tool default). Leave empty to use the tool default.", + "default": "" + }, + "user": { + "type": ["object", "null"], + "description": "Per-user identity, used in spec metadata (contributors, approvals).", + "properties": { + "name": { "type": "string", "description": "Display name." }, + "email": { "type": "string", "format": "email", "description": "Email address." } + }, + "required": ["name", "email"], + "additionalProperties": false, + "default": null + }, + "defaultReviewPosture": { + "type": ["string", "null"], + "description": "Controls how the spec refinement phase expands or contracts scope.", + "enum": [ + "selective-expansion", + "hold-scope", + "scope-expansion", + "scope-reduction", + null + ], + "default": null + } + }, + "additionalProperties": false +} diff --git a/docs/script.js b/docs/script.js deleted file mode 100644 index 16570fb76..000000000 --- a/docs/script.js +++ /dev/null @@ -1,35 +0,0 @@ -function findParent(el, find) { - do { - if (find(el)) { - return el; - } - } while (el = el.parentElement); -} - -document.addEventListener("click", (e) => { - const target = findParent( - e.target, - (el) => el instanceof HTMLButtonElement && el.dataset["copy"], - ); - if (target) { - navigator?.clipboard?.writeText(target.dataset["copy"]); - target.classList.add("copied"); - setTimeout(() => target.classList.remove("copied"), 1000); - } -}); - -window.addEventListener("load", () => { - const usageSelector = document.getElementById("usageSelector"); - - document.addEventListener("mouseup", (e) => { - if ( - findParent( - e.target, - (el) => - el.parentElement === usageSelector && el instanceof HTMLDivElement, - ) - ) { - usageSelector.open = false; - } - }); -}); diff --git a/docs/search.js b/docs/search.js deleted file mode 100644 index 255f138d7..000000000 --- a/docs/search.js +++ /dev/null @@ -1,141 +0,0 @@ -const Fuse = window.Fuse; - -const searchInput = document.querySelector("#searchbar"); -const contentDiv = document.querySelector("#content"); -const searchResultsDiv = document.querySelector("#searchResults"); -const currentFile = - document.querySelector("meta[name='doc-current-file']").attributes - .getNamedItem("content").value; -const pathToRoot = "../".repeat( - currentFile ? (currentFile.split("/").length + 1) : 0, -); -searchInput.removeAttribute("style"); - -const SEARCH_INDEX = window.DENO_DOC_SEARCH_INDEX; - -const fuse = new Fuse(SEARCH_INDEX.nodes, { - keys: [{ - name: "name", - weight: 2, - }], - isCaseSensitive: false, - minMatchCharLength: 2, - threshold: 0.4, -}); - -const loadedUrl = new URL(window.location.href); -const val = loadedUrl.searchParams.get("q"); -if (val) { - searchInput.value = val; - doSearch(val); -} - -window.addEventListener("load", function () { - document.addEventListener("keydown", function (event) { - if (event.key.toLowerCase() === "s") { - if (event.target !== searchInput) { - searchInput.focus(); - event.preventDefault(); - } - } - }); - - const emptyPlaceholder = "Click or press 'S' to search..."; - searchInput.placeholder = emptyPlaceholder; - - searchInput.addEventListener("focus", function () { - searchInput.placeholder = "Type your query here..."; - }); - - searchInput.addEventListener("blur", function () { - searchInput.placeholder = emptyPlaceholder; - }); -}); - -function debounce(func, delay) { - let timerId; - - return function () { - const context = this; - const args = arguments; - - clearTimeout(timerId); - - timerId = setTimeout(function () { - func.apply(context, args); - }, delay); - }; -} - -const debouncedSearch = debounce(doSearch, 250); - -searchInput.addEventListener("input", (e) => { - const val = e.target.value; - debouncedSearch(val); -}); - -function doSearch(val) { - if (!val) { - updateCurrentLocation(val); - showPage(); - } else { - const results = searchInIndex(val); - // console.log("results", results); - updateCurrentLocation(val); - renderResults(results); - showSearchResults(); - } -} - -function updateCurrentLocation(val) { - const url = new URL(window.location.href); - if (val) { - url.searchParams.set("q", val); - } else { - url.searchParams.delete("q"); - } - window.history.replaceState({}, "", url.href); -} - -function showPage() { - contentDiv.style.display = "flex"; - searchResultsDiv.style.display = "none"; -} - -function showSearchResults() { - contentDiv.style.display = "none"; - searchResultsDiv.style.display = "block"; -} - -function renderResults(results) { - if (results.length === 0) { - searchResultsDiv.innerHTML = `No result`; - return; - } - - let html = `
    `; - - for (const result of results) { - const kind = result.kind.map((kind) => { - return `
    ${kind.char}
    `; - }).join(""); - - html += `
  • - -
    -
    - ${kind} -
    - ${result.name} -
    -
    -
  • `; - } - - html += `
`; - searchResultsDiv.innerHTML = html; -} - -function searchInIndex(val) { - return fuse.search(val).map((result) => result.item); -} diff --git a/docs/search_index.js b/docs/search_index.js deleted file mode 100644 index a2181f0b3..000000000 --- a/docs/search_index.js +++ /dev/null @@ -1,3 +0,0 @@ -(function () { - window.DENO_DOC_SEARCH_INDEX = {"kind":"search","nodes":[{"id":"namespace_appRuntime","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"appRuntime","file":".","doc":"","url":"././~/appRuntime.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"appRuntime.AppRuntime","file":".","doc":"","url":"././~/appRuntime.AppRuntime.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"appRuntime.AppRuntime.prototype.addModule","file":".","doc":"","url":"././~/appRuntime.AppRuntime.prototype.addModule.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"appRuntime.AppRuntime.prototype.addChannel","file":".","doc":"","url":"././~/appRuntime.AppRuntime.prototype.addChannel.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"appRuntime.AppRuntime.prototype.setAsDefault","file":".","doc":"","url":"././~/appRuntime.AppRuntime.prototype.setAsDefault.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"appRuntime.AppRuntime.prototype.awaitAll","file":".","doc":"","url":"././~/appRuntime.AppRuntime.prototype.awaitAll.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntime.default","file":".","doc":"","url":"././~/appRuntime.AppRuntime.default.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntime","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntime.prototype.state","file":".","doc":"","url":"././~/appRuntime.AppRuntime.prototype.state.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"appRuntime.AppRuntimeState","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.runMode","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.runMode.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.events","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.events.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.di","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.di.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.channels","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.channels.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.modules","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.modules.html","deprecated":false},{"id":"namespace_appRuntime_AppRuntimeState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.AppRuntimeState.awaits","file":".","doc":"","url":"././~/appRuntime.AppRuntimeState.awaits.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"appRuntime.Module","file":".","doc":"","url":"././~/appRuntime.Module.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.Module.name","file":".","doc":"","url":"././~/appRuntime.Module.name.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.Module.manifest","file":".","doc":"","url":"././~/appRuntime.Module.manifest.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.Module.uses","file":".","doc":"","url":"././~/appRuntime.Module.uses.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.Module.provides","file":".","doc":"","url":"././~/appRuntime.Module.provides.html","deprecated":false},{"id":"namespace_appRuntime_Module","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"appRuntime.Module.entrypoint","file":".","doc":"","url":"././~/appRuntime.Module.entrypoint.html","deprecated":false},{"id":"namespace_appRuntime_createAppRuntimeState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"appRuntime.createAppRuntimeState","file":".","doc":"","url":"././~/appRuntime.createAppRuntimeState.html","deprecated":false},{"id":"namespace_bundler","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"bundler","file":".","doc":"","url":"././~/bundler.html","deprecated":false},{"id":"namespace_bundler_AotSnapshot","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"bundler.AotSnapshot","file":".","doc":"","url":"././~/bundler.AotSnapshot.html","deprecated":false},{"id":"namespace_bundler_AotSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.AotSnapshot.prototype.paths","file":".","doc":"","url":"././~/bundler.AotSnapshot.prototype.paths.html","deprecated":false},{"id":"namespace_bundler_AotSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.AotSnapshot.prototype.read","file":".","doc":"","url":"././~/bundler.AotSnapshot.prototype.read.html","deprecated":false},{"id":"namespace_bundler_AotSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.AotSnapshot.prototype.dependencies","file":".","doc":"","url":"././~/bundler.AotSnapshot.prototype.dependencies.html","deprecated":false},{"id":"namespace_bundler_AotSnapshot","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.AotSnapshot.prototype.state","file":".","doc":"","url":"././~/bundler.AotSnapshot.prototype.state.html","deprecated":false},{"id":"namespace_bundler_AotSnapshotState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.AotSnapshotState","file":".","doc":"","url":"././~/bundler.AotSnapshotState.html","deprecated":false},{"id":"namespace_bundler_AotSnapshotState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.AotSnapshotState.files","file":".","doc":"","url":"././~/bundler.AotSnapshotState.files.html","deprecated":false},{"id":"namespace_bundler_AotSnapshotState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.AotSnapshotState.dependencyMapping","file":".","doc":"","url":"././~/bundler.AotSnapshotState.dependencyMapping.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshot","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.BuildSnapshot","file":".","doc":"","url":"././~/bundler.BuildSnapshot.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.BuildSnapshot.read","file":".","doc":"For a given file, return it's contents.","url":"././~/bundler.BuildSnapshot.read.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.BuildSnapshot.dependencies","file":".","doc":"For a given entrypoint, return it's list of dependencies.Returns an empty array if the entrypoint does not exist.","url":"././~/bundler.BuildSnapshot.dependencies.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshot","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.BuildSnapshot.paths","file":".","doc":"The list of files contained in this snapshot, not prefixed by a slash.","url":"././~/bundler.BuildSnapshot.paths.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshotSerialized","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.BuildSnapshotSerialized","file":".","doc":"","url":"././~/bundler.BuildSnapshotSerialized.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshotSerialized","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.BuildSnapshotSerialized.build_id","file":".","doc":"","url":"././~/bundler.BuildSnapshotSerialized.build_id.html","deprecated":false},{"id":"namespace_bundler_BuildSnapshotSerialized","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.BuildSnapshotSerialized.files","file":".","doc":"","url":"././~/bundler.BuildSnapshotSerialized.files.html","deprecated":false},{"id":"namespace_bundler_Builder","kind":[{"char":"I","kind":"Interface","title":"Interface"}],"name":"bundler.Builder","file":".","doc":"","url":"././~/bundler.Builder.html","deprecated":false},{"id":"namespace_bundler_Builder","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.Builder.build","file":".","doc":"","url":"././~/bundler.Builder.build.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilder","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"bundler.EsbuildBuilder","file":".","doc":"","url":"././~/bundler.EsbuildBuilder.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilder","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.EsbuildBuilder.prototype.build","file":".","doc":"","url":"././~/bundler.EsbuildBuilder.prototype.build.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilder","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilder.prototype.state","file":".","doc":"","url":"././~/bundler.EsbuildBuilder.prototype.state.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.EsbuildBuilderOptions","file":".","doc":"","url":"././~/bundler.EsbuildBuilderOptions.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.buildID","file":".","doc":"The build ID.","url":"././~/bundler.EsbuildBuilderOptions.buildID.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.entrypoints","file":".","doc":"The entrypoints, mapped from name to URL.","url":"././~/bundler.EsbuildBuilderOptions.entrypoints.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.dev","file":".","doc":"Whether or not this is a dev build.","url":"././~/bundler.EsbuildBuilderOptions.dev.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.configPath","file":".","doc":"The path to the deno.json / deno.jsonc config file.","url":"././~/bundler.EsbuildBuilderOptions.configPath.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.jsx","file":".","doc":"The JSX configuration.","url":"././~/bundler.EsbuildBuilderOptions.jsx.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.jsxImportSource","file":".","doc":"","url":"././~/bundler.EsbuildBuilderOptions.jsxImportSource.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.target","file":".","doc":"","url":"././~/bundler.EsbuildBuilderOptions.target.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.absoluteWorkingDir","file":".","doc":"","url":"././~/bundler.EsbuildBuilderOptions.absoluteWorkingDir.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderOptions.basePath","file":".","doc":"","url":"././~/bundler.EsbuildBuilderOptions.basePath.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.EsbuildBuilderState","file":".","doc":"","url":"././~/bundler.EsbuildBuilderState.html","deprecated":false},{"id":"namespace_bundler_EsbuildBuilderState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildBuilderState.options","file":".","doc":"","url":"././~/bundler.EsbuildBuilderState.options.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshot","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"bundler.EsbuildSnapshot","file":".","doc":"","url":"././~/bundler.EsbuildSnapshot.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.EsbuildSnapshot.prototype.paths","file":".","doc":"","url":"././~/bundler.EsbuildSnapshot.prototype.paths.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.EsbuildSnapshot.prototype.read","file":".","doc":"","url":"././~/bundler.EsbuildSnapshot.prototype.read.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshot","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"bundler.EsbuildSnapshot.prototype.dependencies","file":".","doc":"","url":"././~/bundler.EsbuildSnapshot.prototype.dependencies.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshot","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildSnapshot.prototype.state","file":".","doc":"","url":"././~/bundler.EsbuildSnapshot.prototype.state.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshotState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"bundler.EsbuildSnapshotState","file":".","doc":"","url":"././~/bundler.EsbuildSnapshotState.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshotState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildSnapshotState.files","file":".","doc":"","url":"././~/bundler.EsbuildSnapshotState.files.html","deprecated":false},{"id":"namespace_bundler_EsbuildSnapshotState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"bundler.EsbuildSnapshotState.dependencyMapping","file":".","doc":"","url":"././~/bundler.EsbuildSnapshotState.dependencyMapping.html","deprecated":false},{"id":"namespace_bundler_createAotSnapshotState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"bundler.createAotSnapshotState","file":".","doc":"","url":"././~/bundler.createAotSnapshotState.html","deprecated":false},{"id":"namespace_bundler_createEsbuildBuilderState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"bundler.createEsbuildBuilderState","file":".","doc":"","url":"././~/bundler.createEsbuildBuilderState.html","deprecated":false},{"id":"namespace_bundler_createEsbuildSnapshotState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"bundler.createEsbuildSnapshotState","file":".","doc":"","url":"././~/bundler.createEsbuildSnapshotState.html","deprecated":false},{"id":"namespace_bundler_loadAotSnapshot","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"bundler.loadAotSnapshot","file":".","doc":"","url":"././~/bundler.loadAotSnapshot.html","deprecated":false},{"id":"namespace_collector","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"collector","file":".","doc":"","url":"././~/collector.html","deprecated":false},{"id":"namespace_collector_CollectExportsOptions","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"collector.CollectExportsOptions","file":".","doc":"","url":"././~/collector.CollectExportsOptions.html","deprecated":false},{"id":"namespace_collector_CollectExportsOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"collector.CollectExportsOptions.baseDir","file":".","doc":"","url":"././~/collector.CollectExportsOptions.baseDir.html","deprecated":false},{"id":"namespace_collector_CollectExportsOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"collector.CollectExportsOptions.globFilter","file":".","doc":"","url":"././~/collector.CollectExportsOptions.globFilter.html","deprecated":false},{"id":"namespace_collector_CollectExportsOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"collector.CollectExportsOptions.exportFilter","file":".","doc":"","url":"././~/collector.CollectExportsOptions.exportFilter.html","deprecated":false},{"id":"namespace_collector_CollectExportsOptions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"collector.CollectExportsOptions.ignoreFilePattern","file":".","doc":"","url":"././~/collector.CollectExportsOptions.ignoreFilePattern.html","deprecated":false},{"id":"namespace_collector_ExportItem","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"collector.ExportItem","file":".","doc":"","url":"././~/collector.ExportItem.html","deprecated":false},{"id":"namespace_collector_buildManifest","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"collector.buildManifest","file":".","doc":"","url":"././~/collector.buildManifest.html","deprecated":false},{"id":"namespace_collector_buildManifestFile","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"collector.buildManifestFile","file":".","doc":"","url":"././~/collector.buildManifestFile.html","deprecated":false},{"id":"namespace_collector_collectExports","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"collector.collectExports","file":".","doc":"","url":"././~/collector.collectExports.html","deprecated":false},{"id":"namespace_collector_specifierToIdentifier","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"collector.specifierToIdentifier","file":".","doc":"","url":"././~/collector.specifierToIdentifier.html","deprecated":false},{"id":"namespace_collector_walkFiles","kind":[{"char":"f","kind":"Function","title":"Function"}],"name":"collector.walkFiles","file":".","doc":"","url":"././~/collector.walkFiles.html","deprecated":false},{"id":"namespace_collector_writeManifestToString","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"collector.writeManifestToString","file":".","doc":"","url":"././~/collector.writeManifestToString.html","deprecated":false},{"id":"namespace_config","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"config","file":".","doc":"","url":"././~/config.html","deprecated":false},{"id":"namespace_config_Config","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"config.Config","file":".","doc":"","url":"././~/config.Config.html","deprecated":false},{"id":"namespace_config_Config","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.Config.prototype.setKeyMeta","file":".","doc":"","url":"././~/config.Config.prototype.setKeyMeta.html","deprecated":false},{"id":"namespace_config_Config","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.Config.prototype.state","file":".","doc":"","url":"././~/config.Config.prototype.state.html","deprecated":false},{"id":"namespace_config_ConfigItem","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.ConfigItem","file":".","doc":"","url":"././~/config.ConfigItem.html","deprecated":false},{"id":"namespace_config_ConfigItem","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItem.key","file":".","doc":"","url":"././~/config.ConfigItem.key.html","deprecated":false},{"id":"namespace_config_ConfigItem","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItem.value","file":".","doc":"","url":"././~/config.ConfigItem.value.html","deprecated":false},{"id":"namespace_config_ConfigItem","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItem.meta","file":".","doc":"","url":"././~/config.ConfigItem.meta.html","deprecated":false},{"id":"namespace_config_ConfigItemMeta","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.ConfigItemMeta","file":".","doc":"","url":"././~/config.ConfigItemMeta.html","deprecated":false},{"id":"namespace_config_ConfigItemMeta","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItemMeta.description","file":".","doc":"","url":"././~/config.ConfigItemMeta.description.html","deprecated":false},{"id":"namespace_config_ConfigItemMeta","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItemMeta.type","file":".","doc":"","url":"././~/config.ConfigItemMeta.type.html","deprecated":false},{"id":"namespace_config_ConfigItemMeta","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItemMeta.ttl","file":".","doc":"","url":"././~/config.ConfigItemMeta.ttl.html","deprecated":false},{"id":"namespace_config_ConfigItemMeta","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigItemMeta.disallowSource","file":".","doc":"","url":"././~/config.ConfigItemMeta.disallowSource.html","deprecated":false},{"id":"namespace_config_ConfigState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.ConfigState","file":".","doc":"","url":"././~/config.ConfigState.html","deprecated":false},{"id":"namespace_config_ConfigState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.ConfigState.meta","file":".","doc":"","url":"././~/config.ConfigState.meta.html","deprecated":false},{"id":"namespace_config_ContainerReadable","kind":[{"char":"I","kind":"Interface","title":"Interface"}],"name":"config.ContainerReadable","file":".","doc":"","url":"././~/config.ContainerReadable.html","deprecated":false},{"id":"namespace_config_ContainerReadable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.ContainerReadable.getBooleanValue","file":".","doc":"","url":"././~/config.ContainerReadable.getBooleanValue.html","deprecated":false},{"id":"namespace_config_ContainerReadable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.ContainerReadable.getNumberValue","file":".","doc":"","url":"././~/config.ContainerReadable.getNumberValue.html","deprecated":false},{"id":"namespace_config_ContainerReadable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.ContainerReadable.getObjectValue","file":".","doc":"","url":"././~/config.ContainerReadable.getObjectValue.html","deprecated":false},{"id":"namespace_config_ContainerReadable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.ContainerReadable.getStringValue","file":".","doc":"","url":"././~/config.ContainerReadable.getStringValue.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"config.DotenvSource","file":".","doc":"","url":"././~/config.DotenvSource.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.DotenvSource.prototype.getBooleanValue","file":".","doc":"","url":"././~/config.DotenvSource.prototype.getBooleanValue.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.DotenvSource.prototype.getNumberValue","file":".","doc":"","url":"././~/config.DotenvSource.prototype.getNumberValue.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.DotenvSource.prototype.getObjectValue","file":".","doc":"","url":"././~/config.DotenvSource.prototype.getObjectValue.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"config.DotenvSource.prototype.getStringValue","file":".","doc":"","url":"././~/config.DotenvSource.prototype.getStringValue.html","deprecated":false},{"id":"namespace_config_DotenvSource","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.DotenvSource.prototype.state","file":".","doc":"","url":"././~/config.DotenvSource.prototype.state.html","deprecated":false},{"id":"namespace_config_DotenvSourceState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.DotenvSourceState","file":".","doc":"","url":"././~/config.DotenvSourceState.html","deprecated":false},{"id":"namespace_config_Provider","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.Provider","file":".","doc":"","url":"././~/config.Provider.html","deprecated":false},{"id":"namespace_config_RequestContext","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.RequestContext","file":".","doc":"","url":"././~/config.RequestContext.html","deprecated":false},{"id":"namespace_config_Source","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.Source","file":".","doc":"","url":"././~/config.Source.html","deprecated":false},{"id":"namespace_config_Source","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.Source.state","file":".","doc":"","url":"././~/config.Source.state.html","deprecated":false},{"id":"namespace_config_SourceState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"config.SourceState","file":".","doc":"","url":"././~/config.SourceState.html","deprecated":false},{"id":"namespace_config_SourceState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"config.SourceState.id","file":".","doc":"","url":"././~/config.SourceState.id.html","deprecated":false},{"id":"namespace_config_createConfigState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"config.createConfigState","file":".","doc":"","url":"././~/config.createConfigState.html","deprecated":false},{"id":"namespace_config_createDotenvSourceState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"config.createDotenvSourceState","file":".","doc":"","url":"././~/config.createDotenvSourceState.html","deprecated":false},{"id":"namespace_di","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"di","file":".","doc":"","url":"././~/di.html","deprecated":false},{"id":"namespace_di_ClassService","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ClassService","file":".","doc":"","url":"././~/di.ClassService.html","deprecated":false},{"id":"namespace_di_Factory","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.Factory","file":".","doc":"","url":"././~/di.Factory.html","deprecated":false},{"id":"namespace_di_FunctionService","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.FunctionService","file":".","doc":"","url":"././~/di.FunctionService.html","deprecated":false},{"id":"namespace_di_PromisableBuilder","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.PromisableBuilder","file":".","doc":"","url":"././~/di.PromisableBuilder.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"di.Registry","file":".","doc":"","url":"././~/di.Registry.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Registry.prototype.set","file":".","doc":"","url":"././~/di.Registry.prototype.set.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Registry.prototype.setLazy","file":".","doc":"","url":"././~/di.Registry.prototype.setLazy.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Registry.prototype.setScoped","file":".","doc":"","url":"././~/di.Registry.prototype.setScoped.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Registry.prototype.setTransient","file":".","doc":"","url":"././~/di.Registry.prototype.setTransient.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Registry.prototype.build","file":".","doc":"","url":"././~/di.Registry.prototype.build.html","deprecated":false},{"id":"namespace_di_Registry","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.Registry.prototype.descriptors","file":".","doc":"","url":"././~/di.Registry.prototype.descriptors.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"di.Scope","file":".","doc":"","url":"././~/di.Scope.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Scope.prototype.get","file":".","doc":"","url":"././~/di.Scope.prototype.get.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Scope.prototype.getMany","file":".","doc":"","url":"././~/di.Scope.prototype.getMany.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Scope.prototype.invoke","file":".","doc":"","url":"././~/di.Scope.prototype.invoke.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.Scope.prototype.createScope","file":".","doc":"","url":"././~/di.Scope.prototype.createScope.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.Scope.prototype.registry","file":".","doc":"","url":"././~/di.Scope.prototype.registry.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.Scope.prototype.rootScope","file":".","doc":"","url":"././~/di.Scope.prototype.rootScope.html","deprecated":false},{"id":"namespace_di_Scope","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.Scope.prototype.items","file":".","doc":"","url":"././~/di.Scope.prototype.items.html","deprecated":false},{"id":"namespace_di_ServiceDescriptor","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceDescriptor","file":".","doc":"","url":"././~/di.ServiceDescriptor.html","deprecated":false},{"id":"namespace_di_ServiceKey","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceKey","file":".","doc":"","url":"././~/di.ServiceKey.html","deprecated":false},{"id":"namespace_di_ServiceRegistry","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceRegistry","file":".","doc":"","url":"././~/di.ServiceRegistry.html","deprecated":false},{"id":"namespace_di_ServiceRegistryWritable","kind":[{"char":"I","kind":"Interface","title":"Interface"}],"name":"di.ServiceRegistryWritable","file":".","doc":"","url":"././~/di.ServiceRegistryWritable.html","deprecated":false},{"id":"namespace_di_ServiceRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceRegistryWritable.set","file":".","doc":"","url":"././~/di.ServiceRegistryWritable.set.html","deprecated":false},{"id":"namespace_di_ServiceRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceRegistryWritable.setLazy","file":".","doc":"","url":"././~/di.ServiceRegistryWritable.setLazy.html","deprecated":false},{"id":"namespace_di_ServiceRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceRegistryWritable.setScoped","file":".","doc":"","url":"././~/di.ServiceRegistryWritable.setScoped.html","deprecated":false},{"id":"namespace_di_ServiceRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceRegistryWritable.setTransient","file":".","doc":"","url":"././~/di.ServiceRegistryWritable.setTransient.html","deprecated":false},{"id":"namespace_di_ServiceResolution","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceResolution","file":".","doc":"","url":"././~/di.ServiceResolution.html","deprecated":false},{"id":"namespace_di_ServiceScope","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceScope","file":".","doc":"","url":"././~/di.ServiceScope.html","deprecated":false},{"id":"namespace_di_ServiceScopeQueryable","kind":[{"char":"I","kind":"Interface","title":"Interface"}],"name":"di.ServiceScopeQueryable","file":".","doc":"","url":"././~/di.ServiceScopeQueryable.html","deprecated":false},{"id":"namespace_di_ServiceScopeQueryable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceScopeQueryable.get","file":".","doc":"","url":"././~/di.ServiceScopeQueryable.get.html","deprecated":false},{"id":"namespace_di_ServiceScopeQueryable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceScopeQueryable.getMany","file":".","doc":"","url":"././~/di.ServiceScopeQueryable.getMany.html","deprecated":false},{"id":"namespace_di_ServiceScopeQueryable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceScopeQueryable.invoke","file":".","doc":"","url":"././~/di.ServiceScopeQueryable.invoke.html","deprecated":false},{"id":"namespace_di_ServiceScopeQueryable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"di.ServiceScopeQueryable.createScope","file":".","doc":"","url":"././~/di.ServiceScopeQueryable.createScope.html","deprecated":false},{"id":"namespace_di_ServiceType","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceType","file":".","doc":"","url":"././~/di.ServiceType.html","deprecated":false},{"id":"namespace_di_ServiceTypeKey","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceTypeKey","file":".","doc":"","url":"././~/di.ServiceTypeKey.html","deprecated":false},{"id":"namespace_di_ServiceTypes","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.ServiceTypes","file":".","doc":"","url":"././~/di.ServiceTypes.html","deprecated":false},{"id":"namespace_di_ServiceTypes","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.ServiceTypes.Singleton","file":".","doc":"","url":"././~/di.ServiceTypes.Singleton.html","deprecated":false},{"id":"namespace_di_ServiceTypes","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.ServiceTypes.Lazy","file":".","doc":"","url":"././~/di.ServiceTypes.Lazy.html","deprecated":false},{"id":"namespace_di_ServiceTypes","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.ServiceTypes.Scoped","file":".","doc":"","url":"././~/di.ServiceTypes.Scoped.html","deprecated":false},{"id":"namespace_di_ServiceTypes","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"di.ServiceTypes.Transient","file":".","doc":"","url":"././~/di.ServiceTypes.Transient.html","deprecated":false},{"id":"namespace_di_ServiceValue","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.ServiceValue","file":".","doc":"","url":"././~/di.ServiceValue.html","deprecated":false},{"id":"namespace_di_Value","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"di.Value","file":".","doc":"","url":"././~/di.Value.html","deprecated":false},{"id":"namespace_di_di","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.di","file":".","doc":"","url":"././~/di.di.html","deprecated":false},{"id":"namespace_di_inject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.inject","file":".","doc":"","url":"././~/di.inject.html","deprecated":false},{"id":"namespace_di_injectable","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.injectable","file":".","doc":"","url":"././~/di.injectable.html","deprecated":false},{"id":"namespace_di_registry","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.registry","file":".","doc":"","url":"././~/di.registry.html","deprecated":false},{"id":"namespace_di_services","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"di.services","file":".","doc":"","url":"././~/di.services.html","deprecated":false},{"id":"namespace_events","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"events","file":".","doc":"","url":"././~/events.html","deprecated":false},{"id":"namespace_events_Dispatcher","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"events.Dispatcher","file":".","doc":"","url":"././~/events.Dispatcher.html","deprecated":false},{"id":"namespace_events_Dispatcher","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.Dispatcher.prototype.dispatch","file":".","doc":"","url":"././~/events.Dispatcher.prototype.dispatch.html","deprecated":false},{"id":"namespace_events_Dispatcher","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"events.Dispatcher.prototype.state","file":".","doc":"","url":"././~/events.Dispatcher.prototype.state.html","deprecated":false},{"id":"namespace_events_DispatcherState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.DispatcherState","file":".","doc":"","url":"././~/events.DispatcherState.html","deprecated":false},{"id":"namespace_events_EventDispatcher","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.EventDispatcher","file":".","doc":"","url":"././~/events.EventDispatcher.html","deprecated":false},{"id":"namespace_events_EventDispatcher","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.EventDispatcher.dispatch","file":".","doc":"","url":"././~/events.EventDispatcher.dispatch.html","deprecated":false},{"id":"namespace_events_EventDispatcher","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"events.EventDispatcher.state","file":".","doc":"","url":"././~/events.EventDispatcher.state.html","deprecated":false},{"id":"namespace_events_EventDispatcherState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.EventDispatcherState","file":".","doc":"","url":"././~/events.EventDispatcherState.html","deprecated":false},{"id":"namespace_events_EventDispatcherState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"events.EventDispatcherState.registry","file":".","doc":"","url":"././~/events.EventDispatcherState.registry.html","deprecated":false},{"id":"namespace_events_EventRegistry","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.EventRegistry","file":".","doc":"","url":"././~/events.EventRegistry.html","deprecated":false},{"id":"namespace_events_EventRegistryState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.EventRegistryState","file":".","doc":"","url":"././~/events.EventRegistryState.html","deprecated":false},{"id":"namespace_events_EventRegistryState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"events.EventRegistryState.target","file":".","doc":"","url":"././~/events.EventRegistryState.target.html","deprecated":false},{"id":"namespace_events_EventRegistryWritable","kind":[{"char":"I","kind":"Interface","title":"Interface"}],"name":"events.EventRegistryWritable","file":".","doc":"","url":"././~/events.EventRegistryWritable.html","deprecated":false},{"id":"namespace_events_EventRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.EventRegistryWritable.add","file":".","doc":"","url":"././~/events.EventRegistryWritable.add.html","deprecated":false},{"id":"namespace_events_EventRegistryWritable","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.EventRegistryWritable.remove","file":".","doc":"","url":"././~/events.EventRegistryWritable.remove.html","deprecated":false},{"id":"namespace_events_Factory","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.Factory","file":".","doc":"","url":"././~/events.Factory.html","deprecated":false},{"id":"namespace_events_Registry","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"events.Registry","file":".","doc":"","url":"././~/events.Registry.html","deprecated":false},{"id":"namespace_events_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.Registry.prototype.add","file":".","doc":"","url":"././~/events.Registry.prototype.add.html","deprecated":false},{"id":"namespace_events_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.Registry.prototype.remove","file":".","doc":"","url":"././~/events.Registry.prototype.remove.html","deprecated":false},{"id":"namespace_events_Registry","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"events.Registry.prototype.build","file":".","doc":"","url":"././~/events.Registry.prototype.build.html","deprecated":false},{"id":"namespace_events_Registry","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"events.Registry.prototype.state","file":".","doc":"","url":"././~/events.Registry.prototype.state.html","deprecated":false},{"id":"namespace_events_RegistryState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"events.RegistryState","file":".","doc":"","url":"././~/events.RegistryState.html","deprecated":false},{"id":"namespace_events_createDispatcherState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"events.createDispatcherState","file":".","doc":"","url":"././~/events.createDispatcherState.html","deprecated":false},{"id":"namespace_events_createRegistryState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"events.createRegistryState","file":".","doc":"","url":"././~/events.createRegistryState.html","deprecated":false},{"id":"namespace_events_dispatcher","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"events.dispatcher","file":".","doc":"","url":"././~/events.dispatcher.html","deprecated":false},{"id":"namespace_events_events","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"events.events","file":".","doc":"","url":"././~/events.events.html","deprecated":false},{"id":"namespace_events_registry","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"events.registry","file":".","doc":"","url":"././~/events.registry.html","deprecated":false},{"id":"namespace_fp","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"fp","file":".","doc":"","url":"././~/fp.html","deprecated":false},{"id":"namespace_fp_Callback","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.Callback","file":".","doc":"","url":"././~/fp.Callback.html","deprecated":false},{"id":"namespace_fp_Decorated","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.Decorated","file":".","doc":"","url":"././~/fp.Decorated.html","deprecated":false},{"id":"namespace_fp_LogType","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.LogType","file":".","doc":"","url":"././~/fp.LogType.html","deprecated":false},{"id":"namespace_fp_LogType","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.LogType.action","file":".","doc":"","url":"././~/fp.LogType.action.html","deprecated":false},{"id":"namespace_fp_LogType","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.LogType.previousState","file":".","doc":"","url":"././~/fp.LogType.previousState.html","deprecated":false},{"id":"namespace_fp_LogType","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.LogType.newState","file":".","doc":"","url":"././~/fp.LogType.newState.html","deprecated":false},{"id":"namespace_fp_LoggerType","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.LoggerType","file":".","doc":"","url":"././~/fp.LoggerType.html","deprecated":false},{"id":"namespace_fp_MutatorType","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.MutatorType","file":".","doc":"","url":"././~/fp.MutatorType.html","deprecated":false},{"id":"namespace_fp_NextType","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.NextType","file":".","doc":"","url":"././~/fp.NextType.html","deprecated":false},{"id":"namespace_fp_Pattern","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.Pattern","file":".","doc":"","url":"././~/fp.Pattern.html","deprecated":false},{"id":"namespace_fp_PickFromArrayResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.PickFromArrayResult","file":".","doc":"","url":"././~/fp.PickFromArrayResult.html","deprecated":false},{"id":"namespace_fp_PickFromArrayResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.PickFromArrayResult.items","file":".","doc":"","url":"././~/fp.PickFromArrayResult.items.html","deprecated":false},{"id":"namespace_fp_PickFromArrayResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.PickFromArrayResult.rest","file":".","doc":"","url":"././~/fp.PickFromArrayResult.rest.html","deprecated":false},{"id":"namespace_fp_PickFromObjectResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.PickFromObjectResult","file":".","doc":"","url":"././~/fp.PickFromObjectResult.html","deprecated":false},{"id":"namespace_fp_PickFromObjectResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.PickFromObjectResult.items","file":".","doc":"","url":"././~/fp.PickFromObjectResult.items.html","deprecated":false},{"id":"namespace_fp_PickFromObjectResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.PickFromObjectResult.rest","file":".","doc":"","url":"././~/fp.PickFromObjectResult.rest.html","deprecated":false},{"id":"namespace_fp_SplitArrayResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.SplitArrayResult","file":".","doc":"","url":"././~/fp.SplitArrayResult.html","deprecated":false},{"id":"namespace_fp_SplitArrayResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.SplitArrayResult.items","file":".","doc":"","url":"././~/fp.SplitArrayResult.items.html","deprecated":false},{"id":"namespace_fp_SplitArrayResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.SplitArrayResult.rest","file":".","doc":"","url":"././~/fp.SplitArrayResult.rest.html","deprecated":false},{"id":"namespace_fp_SplitObjectResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"fp.SplitObjectResult","file":".","doc":"","url":"././~/fp.SplitObjectResult.html","deprecated":false},{"id":"namespace_fp_SplitObjectResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.SplitObjectResult.items","file":".","doc":"","url":"././~/fp.SplitObjectResult.items.html","deprecated":false},{"id":"namespace_fp_SplitObjectResult","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"fp.SplitObjectResult.rest","file":".","doc":"","url":"././~/fp.SplitObjectResult.rest.html","deprecated":false},{"id":"namespace_fp_appendToArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.appendToArray","file":".","doc":"","url":"././~/fp.appendToArray.html","deprecated":false},{"id":"namespace_fp_appendToObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.appendToObject","file":".","doc":"","url":"././~/fp.appendToObject.html","deprecated":false},{"id":"namespace_fp_associateArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.associateArray","file":".","doc":"","url":"././~/fp.associateArray.html","deprecated":false},{"id":"namespace_fp_associateObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.associateObject","file":".","doc":"","url":"././~/fp.associateObject.html","deprecated":false},{"id":"namespace_fp_compose","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.compose","file":".","doc":"","url":"././~/fp.compose.html","deprecated":false},{"id":"namespace_fp_curry","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.curry","file":".","doc":"","url":"././~/fp.curry.html","deprecated":false},{"id":"namespace_fp_curryRight","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.curryRight","file":".","doc":"","url":"././~/fp.curryRight.html","deprecated":false},{"id":"namespace_fp_decorate","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.decorate","file":".","doc":"","url":"././~/fp.decorate.html","deprecated":false},{"id":"namespace_fp_deepCopy","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.deepCopy","file":".","doc":"","url":"././~/fp.deepCopy.html","deprecated":false},{"id":"namespace_fp_deepCopy2","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.deepCopy2","file":".","doc":"","url":"././~/fp.deepCopy2.html","deprecated":false},{"id":"namespace_fp_deepMerge","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.deepMerge","file":".","doc":"","url":"././~/fp.deepMerge.html","deprecated":false},{"id":"namespace_fp_dispatcher","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.dispatcher","file":".","doc":"","url":"././~/fp.dispatcher.html","deprecated":false},{"id":"namespace_fp_distinctArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.distinctArray","file":".","doc":"","url":"././~/fp.distinctArray.html","deprecated":false},{"id":"namespace_fp_distinctObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.distinctObject","file":".","doc":"","url":"././~/fp.distinctObject.html","deprecated":false},{"id":"namespace_fp_dropFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.dropFromArray","file":".","doc":"","url":"././~/fp.dropFromArray.html","deprecated":false},{"id":"namespace_fp_dropFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.dropFromObject","file":".","doc":"","url":"././~/fp.dropFromObject.html","deprecated":false},{"id":"namespace_fp_filterArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.filterArray","file":".","doc":"","url":"././~/fp.filterArray.html","deprecated":false},{"id":"namespace_fp_filterObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.filterObject","file":".","doc":"","url":"././~/fp.filterObject.html","deprecated":false},{"id":"namespace_fp_iterate","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.iterate","file":".","doc":"","url":"././~/fp.iterate.html","deprecated":false},{"id":"namespace_fp_mapArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.mapArray","file":".","doc":"","url":"././~/fp.mapArray.html","deprecated":false},{"id":"namespace_fp_mapObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.mapObject","file":".","doc":"","url":"././~/fp.mapObject.html","deprecated":false},{"id":"namespace_fp_match","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.match","file":".","doc":"","url":"././~/fp.match.html","deprecated":false},{"id":"namespace_fp_mergeArrays","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.mergeArrays","file":".","doc":"","url":"././~/fp.mergeArrays.html","deprecated":false},{"id":"namespace_fp_mergeObjects","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.mergeObjects","file":".","doc":"","url":"././~/fp.mergeObjects.html","deprecated":false},{"id":"namespace_fp_mutate","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.mutate","file":".","doc":"","url":"././~/fp.mutate.html","deprecated":false},{"id":"namespace_fp_pickFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.pickFromArray","file":".","doc":"","url":"././~/fp.pickFromArray.html","deprecated":false},{"id":"namespace_fp_pickFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.pickFromObject","file":".","doc":"","url":"././~/fp.pickFromObject.html","deprecated":false},{"id":"namespace_fp_pipe","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.pipe","file":".","doc":"","url":"././~/fp.pipe.html","deprecated":false},{"id":"namespace_fp_prependToArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.prependToArray","file":".","doc":"","url":"././~/fp.prependToArray.html","deprecated":false},{"id":"namespace_fp_prependToObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.prependToObject","file":".","doc":"","url":"././~/fp.prependToObject.html","deprecated":false},{"id":"namespace_fp_removeFirstMatchFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeFirstMatchFromArray","file":".","doc":"","url":"././~/fp.removeFirstMatchFromArray.html","deprecated":false},{"id":"namespace_fp_removeFirstMatchFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeFirstMatchFromObject","file":".","doc":"","url":"././~/fp.removeFirstMatchFromObject.html","deprecated":false},{"id":"namespace_fp_removeIndexFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeIndexFromArray","file":".","doc":"","url":"././~/fp.removeIndexFromArray.html","deprecated":false},{"id":"namespace_fp_removeKeyFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeKeyFromObject","file":".","doc":"","url":"././~/fp.removeKeyFromObject.html","deprecated":false},{"id":"namespace_fp_removeValueFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeValueFromArray","file":".","doc":"","url":"././~/fp.removeValueFromArray.html","deprecated":false},{"id":"namespace_fp_removeValueFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.removeValueFromObject","file":".","doc":"","url":"././~/fp.removeValueFromObject.html","deprecated":false},{"id":"namespace_fp_reverseArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.reverseArray","file":".","doc":"","url":"././~/fp.reverseArray.html","deprecated":false},{"id":"namespace_fp_reverseObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.reverseObject","file":".","doc":"","url":"././~/fp.reverseObject.html","deprecated":false},{"id":"namespace_fp_splitArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.splitArray","file":".","doc":"","url":"././~/fp.splitArray.html","deprecated":false},{"id":"namespace_fp_splitObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.splitObject","file":".","doc":"The splitObject function splits an object into two parts based on a given number, with the first part containing the specified number of items and the second part containing the remaining items.","url":"././~/fp.splitObject.html","deprecated":false},{"id":"namespace_fp_takeFromArray","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.takeFromArray","file":".","doc":"","url":"././~/fp.takeFromArray.html","deprecated":false},{"id":"namespace_fp_takeFromObject","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.takeFromObject","file":".","doc":"","url":"././~/fp.takeFromObject.html","deprecated":false},{"id":"namespace_fp_wth","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.wth","file":".","doc":"","url":"././~/fp.wth.html","deprecated":false},{"id":"namespace_fp_wthout","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"fp.wthout","file":".","doc":"","url":"././~/fp.wthout.html","deprecated":false},{"id":"namespace_functions","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"functions","file":".","doc":"","url":"././~/functions.html","deprecated":false},{"id":"namespace_functions_AcceptableResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.AcceptableResult","file":".","doc":"","url":"././~/functions.AcceptableResult.html","deprecated":false},{"id":"namespace_functions_Context","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.Context","file":".","doc":"","url":"././~/functions.Context.html","deprecated":false},{"id":"namespace_functions_Context","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Context.state","file":".","doc":"","url":"././~/functions.Context.state.html","deprecated":false},{"id":"namespace_functions_Context","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Context.next","file":".","doc":"","url":"././~/functions.Context.next.html","deprecated":false},{"id":"namespace_functions_ExtraData","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.ExtraData","file":".","doc":"","url":"././~/functions.ExtraData.html","deprecated":false},{"id":"namespace_functions_Fail","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"functions.Fail","file":".","doc":"","url":"././~/functions.Fail.html","deprecated":false},{"id":"namespace_functions_Fn","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.Fn","file":".","doc":"","url":"././~/functions.Fn.html","deprecated":false},{"id":"namespace_functions_FnResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.FnResult","file":".","doc":"","url":"././~/functions.FnResult.html","deprecated":false},{"id":"namespace_functions_Ok","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"functions.Ok","file":".","doc":"","url":"././~/functions.Ok.html","deprecated":false},{"id":"namespace_functions_Pipeline","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.Pipeline","file":".","doc":"","url":"././~/functions.Pipeline.html","deprecated":false},{"id":"namespace_functions_Pipeline","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Pipeline.use","file":".","doc":"","url":"././~/functions.Pipeline.use.html","deprecated":false},{"id":"namespace_functions_Pipeline","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Pipeline.set","file":".","doc":"","url":"././~/functions.Pipeline.set.html","deprecated":false},{"id":"namespace_functions_Pipeline","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Pipeline.iterate","file":".","doc":"","url":"././~/functions.Pipeline.iterate.html","deprecated":false},{"id":"namespace_functions_Pipeline","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Pipeline.run","file":".","doc":"","url":"././~/functions.Pipeline.run.html","deprecated":false},{"id":"namespace_functions_Result","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.Result","file":".","doc":"","url":"././~/functions.Result.html","deprecated":false},{"id":"namespace_functions_Result","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Result.error","file":".","doc":"","url":"././~/functions.Result.error.html","deprecated":false},{"id":"namespace_functions_Result","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Result.payload","file":".","doc":"","url":"././~/functions.Result.payload.html","deprecated":false},{"id":"namespace_functions_Result","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"functions.Result.extraData","file":".","doc":"","url":"././~/functions.Result.extraData.html","deprecated":false},{"id":"namespace_functions_State","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"functions.State","file":".","doc":"","url":"././~/functions.State.html","deprecated":false},{"id":"namespace_functions_fn","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"functions.fn","file":".","doc":"","url":"././~/functions.fn.html","deprecated":false},{"id":"namespace_logging","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"logging","file":".","doc":"","url":"././~/logging.html","deprecated":false},{"id":"namespace_logging_DEFAULT_LEVEL","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"logging.DEFAULT_LEVEL","file":".","doc":"","url":"././~/logging.DEFAULT_LEVEL.html","deprecated":false},{"id":"namespace_logging_FormatterFn","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"logging.FormatterFn","file":".","doc":"","url":"././~/logging.FormatterFn.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"logging.LogRecord","file":".","doc":"","url":"././~/logging.LogRecord.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LogRecord.message","file":".","doc":"","url":"././~/logging.LogRecord.message.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LogRecord.args","file":".","doc":"","url":"././~/logging.LogRecord.args.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LogRecord.datetime","file":".","doc":"","url":"././~/logging.LogRecord.datetime.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LogRecord.severity","file":".","doc":"","url":"././~/logging.LogRecord.severity.html","deprecated":false},{"id":"namespace_logging_LogRecord","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LogRecord.loggerName","file":".","doc":"","url":"././~/logging.LogRecord.loggerName.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"logging.Logger","file":".","doc":"","url":"././~/logging.Logger.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.log","file":".","doc":"If the level of the logger is greater than the level to log, then nothing is logged, otherwise a log record is passed to each log handler. message data passed in is returned. If a function is passed in, it is only evaluated if the message will be logged and the return value will be the result of the function, not the function itself, unless the function isn't called, in which case undefined is returned. All types are coerced to strings for logging.","url":"././~/logging.Logger.prototype.log.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.asString","file":".","doc":"","url":"././~/logging.Logger.prototype.asString.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.debug","file":".","doc":"","url":"././~/logging.Logger.prototype.debug.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.debug","file":".","doc":"","url":"././~/logging.Logger.prototype.debug.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.debug","file":".","doc":"","url":"././~/logging.Logger.prototype.debug.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.info","file":".","doc":"","url":"././~/logging.Logger.prototype.info.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.info","file":".","doc":"","url":"././~/logging.Logger.prototype.info.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.info","file":".","doc":"","url":"././~/logging.Logger.prototype.info.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.warn","file":".","doc":"","url":"././~/logging.Logger.prototype.warn.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.warn","file":".","doc":"","url":"././~/logging.Logger.prototype.warn.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.warn","file":".","doc":"","url":"././~/logging.Logger.prototype.warn.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.error","file":".","doc":"","url":"././~/logging.Logger.prototype.error.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.error","file":".","doc":"","url":"././~/logging.Logger.prototype.error.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.error","file":".","doc":"","url":"././~/logging.Logger.prototype.error.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.critical","file":".","doc":"","url":"././~/logging.Logger.prototype.critical.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.critical","file":".","doc":"","url":"././~/logging.Logger.prototype.critical.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"logging.Logger.prototype.critical","file":".","doc":"","url":"././~/logging.Logger.prototype.critical.html","deprecated":false},{"id":"namespace_logging_Logger","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.Logger.prototype.state","file":".","doc":"","url":"././~/logging.Logger.prototype.state.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"logging.LoggerState","file":".","doc":"","url":"././~/logging.LoggerState.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LoggerState.loggerName","file":".","doc":"","url":"././~/logging.LoggerState.loggerName.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LoggerState.targetStream","file":".","doc":"","url":"././~/logging.LoggerState.targetStream.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LoggerState.loglevel","file":".","doc":"","url":"././~/logging.LoggerState.loglevel.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LoggerState.formatter","file":".","doc":"","url":"././~/logging.LoggerState.formatter.html","deprecated":false},{"id":"namespace_logging_LoggerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"logging.LoggerState.encoder","file":".","doc":"","url":"././~/logging.LoggerState.encoder.html","deprecated":false},{"id":"namespace_logging_createLoggerState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"logging.createLoggerState","file":".","doc":"","url":"././~/logging.createLoggerState.html","deprecated":false},{"id":"namespace_logging_current","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"logging.current","file":".","doc":"","url":"././~/logging.current.html","deprecated":false},{"id":"namespace_logging_jsonFormatter","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"logging.jsonFormatter","file":".","doc":"","url":"././~/logging.jsonFormatter.html","deprecated":false},{"id":"namespace_parsing","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"parsing","file":".","doc":"","url":"././~/parsing.html","deprecated":false},{"id":"namespace_parsing_ASTNode","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.ASTNode","file":".","doc":"","url":"././~/parsing.ASTNode.html","deprecated":false},{"id":"namespace_parsing_ASTNode","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.ASTNode.kind","file":".","doc":"","url":"././~/parsing.ASTNode.kind.html","deprecated":false},{"id":"namespace_parsing_Parser","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.Parser","file":".","doc":"","url":"././~/parsing.Parser.html","deprecated":false},{"id":"namespace_parsing_PatternFunction","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.PatternFunction","file":".","doc":"","url":"././~/parsing.PatternFunction.html","deprecated":false},{"id":"namespace_parsing_PatternFunctionResult","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.PatternFunctionResult","file":".","doc":"","url":"././~/parsing.PatternFunctionResult.html","deprecated":false},{"id":"namespace_parsing_Token","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.Token","file":".","doc":"","url":"././~/parsing.Token.html","deprecated":false},{"id":"namespace_parsing_Token","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.Token.kind","file":".","doc":"","url":"././~/parsing.Token.kind.html","deprecated":false},{"id":"namespace_parsing_Token","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.Token.value","file":".","doc":"","url":"././~/parsing.Token.value.html","deprecated":false},{"id":"namespace_parsing_TokenDefinitions","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.TokenDefinitions","file":".","doc":"","url":"././~/parsing.TokenDefinitions.html","deprecated":false},{"id":"namespace_parsing_TokenDefinitions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.TokenDefinitions.T_UNKNOWN","file":".","doc":"","url":"././~/parsing.TokenDefinitions.T_UNKNOWN.html","deprecated":false},{"id":"namespace_parsing_TokenDefinitions","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.TokenDefinitions.T_END","file":".","doc":"","url":"././~/parsing.TokenDefinitions.T_END.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"c","kind":"Class","title":"Class"}],"name":"parsing.Tokenizer","file":".","doc":"","url":"././~/parsing.Tokenizer.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"parsing.Tokenizer.prototype.tokenizeFromString","file":".","doc":"","url":"././~/parsing.Tokenizer.prototype.tokenizeFromString.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"parsing.Tokenizer.prototype.tokenize","file":".","doc":"","url":"././~/parsing.Tokenizer.prototype.tokenize.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"parsing.Tokenizer.prototype._tokenizeChunk","file":".","doc":"","url":"././~/parsing.Tokenizer.prototype._tokenizeChunk.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"m","kind":"Method","title":"Method"}],"name":"parsing.Tokenizer.prototype._reset","file":".","doc":"","url":"././~/parsing.Tokenizer.prototype._reset.html","deprecated":false},{"id":"namespace_parsing_Tokenizer","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.Tokenizer.prototype.state","file":".","doc":"","url":"././~/parsing.Tokenizer.prototype.state.html","deprecated":false},{"id":"namespace_parsing_TokenizerState","kind":[{"char":"T","kind":"TypeAlias","title":"Type Alias"}],"name":"parsing.TokenizerState","file":".","doc":"","url":"././~/parsing.TokenizerState.html","deprecated":false},{"id":"namespace_parsing_TokenizerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.TokenizerState.tokenDefs","file":".","doc":"","url":"././~/parsing.TokenizerState.tokenDefs.html","deprecated":false},{"id":"namespace_parsing_TokenizerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.TokenizerState.isDone","file":".","doc":"","url":"././~/parsing.TokenizerState.isDone.html","deprecated":false},{"id":"namespace_parsing_TokenizerState","kind":[{"char":"p","kind":"Property","title":"Property"}],"name":"parsing.TokenizerState.buffer","file":".","doc":"","url":"././~/parsing.TokenizerState.buffer.html","deprecated":false},{"id":"namespace_parsing_createTokenizerState","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"parsing.createTokenizerState","file":".","doc":"","url":"././~/parsing.createTokenizerState.html","deprecated":false},{"id":"namespace_parsing_sequence","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"parsing.sequence","file":".","doc":"","url":"././~/parsing.sequence.html","deprecated":false},{"id":"namespace_parsing_simpleTokens","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"parsing.simpleTokens","file":".","doc":"","url":"././~/parsing.simpleTokens.html","deprecated":false},{"id":"namespace_parsing_token","kind":[{"char":"v","kind":"Variable","title":"Variable"}],"name":"parsing.token","file":".","doc":"","url":"././~/parsing.token.html","deprecated":false},{"id":"namespace_standards","kind":[{"char":"N","kind":"Namespace","title":"Namespace"}],"name":"standards","file":".","doc":"","url":"././~/standards.html","deprecated":false}]}; -})() diff --git a/docs/styles.css b/docs/styles.css deleted file mode 100644 index 7093c02e3..000000000 --- a/docs/styles.css +++ /dev/null @@ -1 +0,0 @@ -.ddoc .container{width:100%}@media (min-width:640px){.ddoc .container{max-width:640px}}@media (min-width:768px){.ddoc .container{max-width:768px}}@media (min-width:1024px){.ddoc .container{max-width:1024px}}@media (min-width:1280px){.ddoc .container{max-width:1280px}}@media (min-width:1536px){.ddoc .container{max-width:1536px}}.ddoc .static{position:static}.ddoc .\!mb-0{margin-bottom:0!important}.ddoc .\!mt-2{margin-top:.5rem!important}.ddoc .mb-1{margin-bottom:.25rem}.ddoc .ml-4{margin-left:1rem}.ddoc .ml-indent{margin-left:2ch}.ddoc .mt-3{margin-top:.75rem}.ddoc .inline{display:inline}.ddoc .\!flex{display:flex!important}.ddoc .flex{display:flex}.ddoc .inline-flex{display:inline-flex}.ddoc .table{display:table}.ddoc .contents{display:contents}.ddoc .hidden{display:none}.ddoc .h-4{height:1rem}.ddoc .h-5{height:1.25rem}.ddoc .max-w-\[75ch\]{max-width:75ch}.ddoc .flex-1{flex:1}.ddoc .flex-none{flex:none}.ddoc .rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y))rotate(var(--tw-rotate))skewX(var(--tw-skew-x))skewY(var(--tw-skew-y))scaleX(var(--tw-scale-x))scaleY(var(--tw-scale-y))}.ddoc .scroll-mt-16{scroll-margin-top:4rem}.ddoc .items-center{align-items:center}.ddoc .gap-0{gap:0}.ddoc .gap-0\.5{gap:.125rem}.ddoc .gap-1{gap:.25rem}.ddoc .space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .space-y-7>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.75rem*var(--tw-space-y-reverse))}.ddoc .space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.ddoc .break-words{overflow-wrap:break-word}.ddoc .break-all{word-break:break-all}.ddoc .rounded{border-radius:.25rem}.ddoc .rounded-md{border-radius:.375rem}.ddoc .border{border-width:1px}.ddoc .border-b{border-bottom-width:1px}.ddoc .border-l-2{border-left-width:2px}.ddoc .border-Class\/50{border-color:#20b44b80}.ddoc .border-ClassDark\/50{border-color:#87eea480}.ddoc .border-Enum\/50{border-color:#22abb080}.ddoc .border-EnumDark\/50{border-color:#3fced180}.ddoc .border-Function\/50{border-color:#056cf080}.ddoc .border-FunctionDark\/50{border-color:#4cc3ff80}.ddoc .border-Interface\/50{border-color:#d2a06480}.ddoc .border-InterfaceDark\/50{border-color:#bb733b80}.ddoc .border-Method\/50{border-color:#056cf080}.ddoc .border-MethodDark\/50{border-color:#4cc3ff80}.ddoc .border-Namespace\/50{border-color:#d2564680}.ddoc .border-NamespaceDark\/50{border-color:#e57e6b80}.ddoc .border-Property\/50{border-color:#7e57c080}.ddoc .border-PropertyDark\/50{border-color:#b37feb80}.ddoc .border-TypeAlias\/50{border-color:#a4478c80}.ddoc .border-TypeAliasDark\/50{border-color:#dd95cc80}.ddoc .border-Variable\/50{border-color:#7e57c080}.ddoc .border-VariableDark\/50{border-color:#b37feb80}.ddoc .border-abstract\/50{border-color:#0cafc680}.ddoc .border-deprecated\/50{border-color:#dc262680}.ddoc .border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.ddoc .border-new\/50{border-color:#7b61ff80}.ddoc .border-optional\/50{border-color:#0cafc680}.ddoc .border-other\/50{border-color:#57534e80}.ddoc .border-permissions\/50,.ddoc .border-private\/50{border-color:#0cafc680}.ddoc .border-protected\/50,.ddoc .border-readonly\/50{border-color:#7b61ff80}.ddoc .border-stone-300{--tw-border-opacity:1;border-color:rgb(214 211 209/var(--tw-border-opacity))}.ddoc .border-unstable\/50,.ddoc .border-writeonly\/50{border-color:#7b61ff80}.ddoc .bg-Class\/15{background-color:#20b44b26}.ddoc .bg-Class\/5{background-color:#20b44b0d}.ddoc .bg-ClassDark\/15{background-color:#87eea426}.ddoc .bg-ClassDark\/5{background-color:#87eea40d}.ddoc .bg-Enum\/15{background-color:#22abb026}.ddoc .bg-Enum\/5{background-color:#22abb00d}.ddoc .bg-EnumDark\/15{background-color:#3fced126}.ddoc .bg-EnumDark\/5{background-color:#3fced10d}.ddoc .bg-Function\/15{background-color:#056cf026}.ddoc .bg-Function\/5{background-color:#056cf00d}.ddoc .bg-FunctionDark\/15{background-color:#4cc3ff26}.ddoc .bg-FunctionDark\/5{background-color:#4cc3ff0d}.ddoc .bg-Interface\/15{background-color:#d2a06426}.ddoc .bg-Interface\/5{background-color:#d2a0640d}.ddoc .bg-InterfaceDark\/15{background-color:#bb733b26}.ddoc .bg-InterfaceDark\/5{background-color:#bb733b0d}.ddoc .bg-Method\/15{background-color:#056cf026}.ddoc .bg-Method\/5{background-color:#056cf00d}.ddoc .bg-MethodDark\/15{background-color:#4cc3ff26}.ddoc .bg-MethodDark\/5{background-color:#4cc3ff0d}.ddoc .bg-Namespace\/15{background-color:#d2564626}.ddoc .bg-Namespace\/5{background-color:#d256460d}.ddoc .bg-NamespaceDark\/15{background-color:#e57e6b26}.ddoc .bg-NamespaceDark\/5{background-color:#e57e6b0d}.ddoc .bg-Property\/15{background-color:#7e57c026}.ddoc .bg-Property\/5{background-color:#7e57c00d}.ddoc .bg-PropertyDark\/15{background-color:#b37feb26}.ddoc .bg-PropertyDark\/5{background-color:#b37feb0d}.ddoc .bg-TypeAlias\/15{background-color:#a4478c26}.ddoc .bg-TypeAlias\/5{background-color:#a4478c0d}.ddoc .bg-TypeAliasDark\/15{background-color:#dd95cc26}.ddoc .bg-TypeAliasDark\/5{background-color:#dd95cc0d}.ddoc .bg-Variable\/15{background-color:#7e57c026}.ddoc .bg-Variable\/5{background-color:#7e57c00d}.ddoc .bg-VariableDark\/15{background-color:#b37feb26}.ddoc .bg-VariableDark\/5{background-color:#b37feb0d}.ddoc .bg-abstract\/15{background-color:#0cafc626}.ddoc .bg-abstract\/5{background-color:#0cafc60d}.ddoc .bg-deprecated\/15{background-color:#dc262626}.ddoc .bg-deprecated\/5{background-color:#dc26260d}.ddoc .bg-new\/15{background-color:#7b61ff26}.ddoc .bg-new\/5{background-color:#7b61ff0d}.ddoc .bg-optional\/15{background-color:#0cafc626}.ddoc .bg-optional\/5{background-color:#0cafc60d}.ddoc .bg-other\/15{background-color:#57534e26}.ddoc .bg-other\/5{background-color:#57534e0d}.ddoc .bg-permissions\/15{background-color:#0cafc626}.ddoc .bg-permissions\/5{background-color:#0cafc60d}.ddoc .bg-private\/15{background-color:#0cafc626}.ddoc .bg-private\/5{background-color:#0cafc60d}.ddoc .bg-protected\/15{background-color:#7b61ff26}.ddoc .bg-protected\/5{background-color:#7b61ff0d}.ddoc .bg-readonly\/15{background-color:#7b61ff26}.ddoc .bg-readonly\/5{background-color:#7b61ff0d}.ddoc .bg-stone-100{--tw-bg-opacity:1;background-color:rgb(245 245 244/var(--tw-bg-opacity))}.ddoc .bg-unstable\/15{background-color:#7b61ff26}.ddoc .bg-unstable\/5{background-color:#7b61ff0d}.ddoc .bg-writeonly\/15{background-color:#7b61ff26}.ddoc .bg-writeonly\/5{background-color:#7b61ff0d}.ddoc .px-2{padding-left:.5rem;padding-right:.5rem}.ddoc .px-3{padding-left:.75rem;padding-right:.75rem}.ddoc .px-4{padding-left:1rem;padding-right:1rem}.ddoc .py-1{padding-top:.25rem;padding-bottom:.25rem}.ddoc .py-2{padding-top:.5rem;padding-bottom:.5rem}.ddoc .pb-5{padding-bottom:1.25rem}.ddoc .pt-4{padding-top:1rem}.ddoc .text-2xl{font-size:1.5rem;line-height:2rem}.ddoc .text-base{font-size:1rem;line-height:1.5rem}.ddoc .text-sm{font-size:.875rem;line-height:1.25rem}.ddoc .font-bold{font-weight:700}.ddoc .font-medium{font-weight:500}.ddoc .font-normal{font-weight:400}.ddoc .italic{font-style:italic}.ddoc .leading-none{line-height:1}.ddoc .text-Class{--tw-text-opacity:1;color:rgb(32 180 75/var(--tw-text-opacity))}.ddoc .text-ClassDark{--tw-text-opacity:1;color:rgb(135 238 164/var(--tw-text-opacity))}.ddoc .text-Enum{--tw-text-opacity:1;color:rgb(34 171 176/var(--tw-text-opacity))}.ddoc .text-EnumDark{--tw-text-opacity:1;color:rgb(63 206 209/var(--tw-text-opacity))}.ddoc .text-Function{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-FunctionDark{--tw-text-opacity:1;color:rgb(76 195 255/var(--tw-text-opacity))}.ddoc .text-Interface{--tw-text-opacity:1;color:rgb(210 160 100/var(--tw-text-opacity))}.ddoc .text-InterfaceDark{--tw-text-opacity:1;color:rgb(187 115 59/var(--tw-text-opacity))}.ddoc .text-Method{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-MethodDark{--tw-text-opacity:1;color:rgb(76 195 255/var(--tw-text-opacity))}.ddoc .text-Namespace{--tw-text-opacity:1;color:rgb(210 86 70/var(--tw-text-opacity))}.ddoc .text-NamespaceDark{--tw-text-opacity:1;color:rgb(229 126 107/var(--tw-text-opacity))}.ddoc .text-Property{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-PropertyDark{--tw-text-opacity:1;color:rgb(179 127 235/var(--tw-text-opacity))}.ddoc .text-TypeAlias{--tw-text-opacity:1;color:rgb(164 71 140/var(--tw-text-opacity))}.ddoc .text-TypeAliasDark{--tw-text-opacity:1;color:rgb(221 149 204/var(--tw-text-opacity))}.ddoc .text-Variable{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-VariableDark{--tw-text-opacity:1;color:rgb(179 127 235/var(--tw-text-opacity))}.ddoc .text-abstract{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.ddoc .text-deprecated{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.ddoc .text-new{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-optional{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-other{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity))}.ddoc .text-permissions,.ddoc .text-private{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-protected,.ddoc .text-readonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-stone-500{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity))}.ddoc .text-unstable,.ddoc .text-writeonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .filter{filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.ddoc summary::-webkit-details-marker{display:none}.ddoc a{word-wrap:break-word}.ddoc{--ddoc-selection-border-width:2px;--ddoc-selection-border-color-default:#d6d3d1;--ddoc-selection-selected-border-color:#2564eb;--ddoc-selection-selected-bg:#056cf00c;--ddoc-selection-padding:9px 15px;--ddoc-usage-active-bg:#ebf6ff}.ddoc:is(.dark *){--ddoc-usage-active-bg:#292524}.ddoc .link{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ddoc .link:is(.dark *),.ddoc .link:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.ddoc .link:hover:is(.dark *){--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.ddoc .anchor{float:left;--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-left:-24px;padding:.25rem;line-height:1;display:none;top:0;bottom:0}.ddoc .anchor:is(.dark *){--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity))}.ddoc .anchorable{scroll-margin-top:4rem;position:relative}.ddoc .anchorable:hover .anchor{display:block}.ddoc .deprecated>div:first-child{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity));align-items:center;gap:.25rem;padding-top:.25rem;padding-bottom:.25rem;display:flex}.ddoc .deprecated>div:first-child:is(.dark *){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.ddoc .deprecated>div:first-child>span{font-weight:600;line-height:1.5rem}.ddoc .deprecated>div:nth-child(2){--tw-border-opacity:1;border-left-width:4px;border-color:rgb(252 165 165/var(--tw-border-opacity));margin-left:.25rem;padding-left:.5rem}.ddoc .deprecated>div:nth-child(2):is(.dark *){--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity))}.ddoc .symbolSubtitle>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.ddoc .symbolSubtitle{font-size:.875rem;line-height:1rem}.ddoc .symbolSubtitle .type{--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity));font-style:italic}.ddoc .symbolSubtitle .type:is(.dark *){--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity))}.ddoc .docEntry{margin-bottom:1rem}.ddoc .docEntry>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .docEntry:hover .sourceButton,.ddoc .docEntry .docEntryHeader{display:flex}.ddoc .docEntry .docEntryHeader{justify-content:space-between;align-items:flex-start}@media (min-width:768px){.ddoc .docEntry .docEntryHeader{font-size:1rem;line-height:1.5rem}}.ddoc .docEntry .docEntryHeader>div{overflow-wrap:break-word}.ddoc .section{max-width:75ch;margin-bottom:.5rem;scroll-margin-top:4rem}.ddoc .section>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>h2{margin-bottom:1rem;padding-top:.25rem;padding-bottom:.25rem;font-size:1.25rem;font-weight:600;line-height:1.5rem}.ddoc .section>div:first-child>div{max-width:75ch;font-size:1rem;line-height:1.5rem}.ddoc .namespaceSection{max-width:75ch;margin-top:1rem}.ddoc .namespaceSection>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .namespaceSection .namespaceItem{-moz-column-gap:.625rem;column-gap:.625rem;min-height:0;display:flex}@media (min-width:768px){.ddoc .namespaceSection .namespaceItem{min-height:4rem}}@media (min-width:1024px){.ddoc .namespaceSection .namespaceItem{padding-right:1rem}}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon{flex-direction:column;justify-content:flex-start;gap:.25rem;width:auto}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon>*+*{margin-top:-.125rem;margin-left:0}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated]{opacity:.6}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated] .namespaceItemContent>a{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity));text-decoration-line:line-through;text-decoration-color:#78716cb3;text-decoration-thickness:2px}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated] .namespaceItemContent>a:is(.dark *){--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity))}.ddoc .namespaceSection .namespaceItem .namespaceItemContent{flex:1;width:0}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a{text-decoration-line:underline;text-decoration-color:#d6d3d1}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a:hover,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a:hover{text-decoration-line:none}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a:is(.dark *),.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a:is(.dark *){text-decoration-color:#78716c}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a{word-break:break-all;font-weight:500;line-height:1.25;display:block}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentDoc{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-top:.5rem;font-size:.875rem;line-height:1.25rem}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentDoc:is(.dark *){--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity))}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems{flex-wrap:wrap;row-gap:.25rem;margin-top:.375rem;font-size:.875rem;line-height:1.25rem;display:flex}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems>li:not(:last-child):after{content:"|";-webkit-user-select:none;user-select:none;--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity));margin-left:.5rem;margin-right:.5rem}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems>li:not(:last-child):is(.dark *):after{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.ddoc .symbolGroup>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(3rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(3rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article .symbolTitle{justify-content:space-between;align-items:flex-start;display:flex}.ddoc .symbolGroup article .symbolTitle:hover .sourceButton{display:flex}.ddoc .symbolGroup article .symbolTitle>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article .symbolTitle>div:first-child{font-weight:500}.ddoc .docNodeKindIcon{flex-shrink:0;justify-content:flex-end;display:inline-flex}.ddoc .docNodeKindIcon div{-webkit-user-select:none;user-select:none;text-align:center;vertical-align:middle;border-radius:9999px;flex-shrink:0;width:1rem;height:1rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.75rem;font-weight:500;line-height:1rem}.ddoc .docNodeKindIcon>*+*{margin-left:-.375rem}.ddoc .example-header{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc>div>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols{font-size:.875rem;line-height:1.25rem}.ddoc .toc .topSymbols ul{list-style-type:none}.ddoc .toc .topSymbols ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.625rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols ul li{display:block}.ddoc .toc .topSymbols ul li a{align-items:center;gap:.5rem;display:flex}.ddoc .toc .topSymbols ul li a>span{text-overflow:ellipsis;white-space:nowrap;border-radius:.25rem;width:100%;margin-top:-.125rem;margin-bottom:-.125rem;margin-left:-.25rem;padding-top:.125rem;padding-bottom:.125rem;padding-left:.25rem;display:block;overflow:hidden}.ddoc .toc .topSymbols>a:hover{text-decoration-line:underline}.ddoc .toc .documentNavigation>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation{font-size:.875rem;line-height:1.25rem}@media not all and (min-width:640px){.ddoc .toc .documentNavigation{display:none}}.ddoc .toc .documentNavigation>ul{flex-grow:1;display:block}.ddoc .toc .documentNavigation>ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul{overflow-y:auto}.ddoc .toc .documentNavigation>ul>li{margin-top:.125rem;margin-left:.75rem;margin-right:.75rem}.ddoc .toc .documentNavigation>ul li:has(>ul){margin-top:0!important}.ddoc .toc .documentNavigation>ul li:has(>a){padding-bottom:0!important}.ddoc .toc .documentNavigation>ul ul{margin-left:.875rem}.ddoc .toc .documentNavigation>ul ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul ul{--tw-text-opacity:1;color:rgb(134 135 137/var(--tw-text-opacity));font-size:.8rem;line-height:1}.ddoc .toc .documentNavigation>ul ul:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.ddoc .toc .documentNavigation>ul ul li{margin-top:.25rem!important}.ddoc .toc .documentNavigation>ul ul li a{padding:.25rem}.ddoc .toc .documentNavigation>ul ul li a:hover{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.ddoc .toc .documentNavigation>ul ul li a:hover:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.ddoc .toc .documentNavigation a{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.ddoc .toc .documentNavigation a:hover{text-decoration-line:underline}.ddoc .usages nav{flex-direction:row;align-items:center;gap:.5rem;margin-bottom:.75rem;font-weight:600;display:flex}.ddoc .usages nav details>summary{cursor:pointer;-webkit-user-select:none;user-select:none;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));border-radius:.25rem;gap:.25rem;padding:.5rem .75rem;display:flex}.ddoc .usages nav details>summary:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}@media (min-width:768px){.ddoc .usages nav details>div{position:relative}}.ddoc .usages nav details>div>div{z-index:30;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));margin-top:.375rem;padding:.5rem;display:block;position:absolute}.ddoc .usages nav details>div>div:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}@media not all and (min-width:768px){.ddoc .usages nav details>div>div{border-left-width:0;border-right-width:0;left:0;right:0}}@media (min-width:768px){.ddoc .usages nav details>div>div{border-radius:.25rem;width:12rem}}.ddoc .usages nav details>div>div label{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.125rem;align-items:center;gap:.5rem;padding:.25rem .5rem;line-height:1.5;display:flex}.ddoc .usages nav details>div>div label:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.ddoc .usages nav details>div>div label:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.ddoc .usageContent>h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .usageContent>div{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity));font-size:.75rem;line-height:1rem}.ddoc .usageContent>div:is(.dark *){--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.ddoc .usageContent>div p{margin:0}.ddoc .usageContent pre.highlight{--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.ddoc .usageContent pre.highlight:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}@media not all and (min-width:768px){.ddoc .usageContent pre.highlight{border-left-width:0;border-right-width:0}}.ddoc .usageContent pre.highlight>code:first-child{scrollbar-width:thin;padding:.5rem 2.5rem .5rem .5rem}.ddoc .usageContent pre.highlight .copyButton{opacity:0;top:.25rem;right:.5rem}.ddoc .usageContent pre.highlight:hover .copyButton{opacity:1}.ddoc pre.highlight .copyButton{opacity:0;position:absolute;top:.75rem;right:1rem}.ddoc pre.highlight:hover .copyButton{opacity:.6}.ddoc pre.highlight:hover .copyButton:hover{opacity:1}.ddoc #categoryPanel{padding-top:.75rem;font-size:.875rem;line-height:1.25rem}.ddoc #categoryPanel ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc #categoryPanel ul{overflow-y:auto}.ddoc #categoryPanel ul li{margin-left:.25rem;margin-right:.75rem}.ddoc #categoryPanel ul li a{text-overflow:ellipsis;white-space:nowrap;padding:.375rem .875rem;display:block;overflow:hidden}.ddoc #categoryPanel ul li a:hover{text-decoration-line:underline}.ddoc .contextLink{color:#0e6590cc;text-underline-offset:.15em;text-decoration-line:underline;text-decoration-color:#0e659080;text-decoration-thickness:1.5px}.ddoc .contextLink:hover{--tw-text-opacity:1;color:rgb(14 101 144/var(--tw-text-opacity));text-decoration-color:#0e6590}.ddoc .contextLink:is(.dark *){color:#86d3f3b3}.ddoc .contextLink:hover:is(.dark *){--tw-text-opacity:1;color:rgb(134 211 243/var(--tw-text-opacity))}.ddoc .contextLink{-webkit-text-decoration-skip:ink;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto}.ddoc .breadcrumbs{word-break:break-all;flex-wrap:wrap;align-items:center;gap:.25rem;display:inline-flex}.ddoc .breadcrumbs>li:first-child{font-size:1.5rem;font-weight:700;line-height:1}.ddoc .breadcrumbs li{font-size:1.125rem;line-height:.9em;display:inline}@media (min-width:1024px){.ddoc .breadcrumbs li{font-size:1.25rem;line-height:1.75rem}}.ddoc .see{list-style-type:disc;list-style-position:inside}.ddoc .see>li *{display:inline-block}.ddoc .sourceButton,.ddoc .copyButton{z-index:10;cursor:pointer;background-color:inherit;border-radius:.25rem;padding:.375rem;line-height:0;text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ddoc .sourceButton:hover,.ddoc .copyButton:hover{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity))}.ddoc .sourceButton:hover:is(.dark *),.ddoc .copyButton:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(41 37 36/var(--tw-bg-opacity))}.ddoc .sourceButton{flex-direction:row;align-items:center;gap:.5rem;margin-right:.5rem;display:none;position:relative}.ddoc .sourceButton:before{content:"View code";font-size:.75rem;line-height:1;display:none}@media (min-width:768px){.ddoc .sourceButton:before{display:block}}.ddoc .\*\:h-4>*{height:1rem}.ddoc .\*\:h-5>*{height:1.25rem}.ddoc .\*\:w-auto>*{width:auto}.ddoc .\*\:flex-none>*{flex:none}.ddoc .hover\:bg-Class\/15:hover{background-color:#20b44b26}.ddoc .hover\:bg-Class\/5:hover{background-color:#20b44b0d}.ddoc .hover\:bg-ClassDark\/15:hover{background-color:#87eea426}.ddoc .hover\:bg-ClassDark\/5:hover{background-color:#87eea40d}.ddoc .hover\:bg-Enum\/15:hover{background-color:#22abb026}.ddoc .hover\:bg-Enum\/5:hover{background-color:#22abb00d}.ddoc .hover\:bg-EnumDark\/15:hover{background-color:#3fced126}.ddoc .hover\:bg-EnumDark\/5:hover{background-color:#3fced10d}.ddoc .hover\:bg-Function\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Function\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-FunctionDark\/15:hover{background-color:#4cc3ff26}.ddoc .hover\:bg-FunctionDark\/5:hover{background-color:#4cc3ff0d}.ddoc .hover\:bg-Interface\/15:hover{background-color:#d2a06426}.ddoc .hover\:bg-Interface\/5:hover{background-color:#d2a0640d}.ddoc .hover\:bg-InterfaceDark\/15:hover{background-color:#bb733b26}.ddoc .hover\:bg-InterfaceDark\/5:hover{background-color:#bb733b0d}.ddoc .hover\:bg-Method\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Method\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-MethodDark\/15:hover{background-color:#4cc3ff26}.ddoc .hover\:bg-MethodDark\/5:hover{background-color:#4cc3ff0d}.ddoc .hover\:bg-Namespace\/15:hover{background-color:#d2564626}.ddoc .hover\:bg-Namespace\/5:hover{background-color:#d256460d}.ddoc .hover\:bg-NamespaceDark\/15:hover{background-color:#e57e6b26}.ddoc .hover\:bg-NamespaceDark\/5:hover{background-color:#e57e6b0d}.ddoc .hover\:bg-Property\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Property\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-PropertyDark\/15:hover{background-color:#b37feb26}.ddoc .hover\:bg-PropertyDark\/5:hover{background-color:#b37feb0d}.ddoc .hover\:bg-TypeAlias\/15:hover{background-color:#a4478c26}.ddoc .hover\:bg-TypeAlias\/5:hover{background-color:#a4478c0d}.ddoc .hover\:bg-TypeAliasDark\/15:hover{background-color:#dd95cc26}.ddoc .hover\:bg-TypeAliasDark\/5:hover{background-color:#dd95cc0d}.ddoc .hover\:bg-Variable\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Variable\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-VariableDark\/15:hover{background-color:#b37feb26}.ddoc .hover\:bg-VariableDark\/5:hover{background-color:#b37feb0d}.ddoc .hover\:bg-abstract\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-abstract\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-deprecated\/15:hover{background-color:#dc262626}.ddoc .hover\:bg-deprecated\/5:hover{background-color:#dc26260d}.ddoc .hover\:bg-new\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-new\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-optional\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-optional\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-other\/15:hover{background-color:#57534e26}.ddoc .hover\:bg-other\/5:hover{background-color:#57534e0d}.ddoc .hover\:bg-permissions\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-permissions\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-private\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-private\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-protected\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-protected\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-readonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-readonly\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-unstable\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-unstable\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-writeonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-writeonly\/5:hover{background-color:#7b61ff0d}.ddoc .dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.ddoc .dark\:bg-Class\/15:is(.dark *){background-color:#20b44b26}.ddoc .dark\:bg-Class\/5:is(.dark *){background-color:#20b44b0d}.ddoc .dark\:bg-ClassDark\/15:is(.dark *){background-color:#87eea426}.ddoc .dark\:bg-ClassDark\/5:is(.dark *){background-color:#87eea40d}.ddoc .dark\:bg-Enum\/15:is(.dark *){background-color:#22abb026}.ddoc .dark\:bg-Enum\/5:is(.dark *){background-color:#22abb00d}.ddoc .dark\:bg-EnumDark\/15:is(.dark *){background-color:#3fced126}.ddoc .dark\:bg-EnumDark\/5:is(.dark *){background-color:#3fced10d}.ddoc .dark\:bg-Function\/15:is(.dark *){background-color:#056cf026}.ddoc .dark\:bg-Function\/5:is(.dark *){background-color:#056cf00d}.ddoc .dark\:bg-FunctionDark\/15:is(.dark *){background-color:#4cc3ff26}.ddoc .dark\:bg-FunctionDark\/5:is(.dark *){background-color:#4cc3ff0d}.ddoc .dark\:bg-Interface\/15:is(.dark *){background-color:#d2a06426}.ddoc .dark\:bg-Interface\/5:is(.dark *){background-color:#d2a0640d}.ddoc .dark\:bg-InterfaceDark\/15:is(.dark *){background-color:#bb733b26}.ddoc .dark\:bg-InterfaceDark\/5:is(.dark *){background-color:#bb733b0d}.ddoc .dark\:bg-Method\/15:is(.dark *){background-color:#056cf026}.ddoc .dark\:bg-Method\/5:is(.dark *){background-color:#056cf00d}.ddoc .dark\:bg-MethodDark\/15:is(.dark *){background-color:#4cc3ff26}.ddoc .dark\:bg-MethodDark\/5:is(.dark *){background-color:#4cc3ff0d}.ddoc .dark\:bg-Namespace\/15:is(.dark *){background-color:#d2564626}.ddoc .dark\:bg-Namespace\/5:is(.dark *){background-color:#d256460d}.ddoc .dark\:bg-NamespaceDark\/15:is(.dark *){background-color:#e57e6b26}.ddoc .dark\:bg-NamespaceDark\/5:is(.dark *){background-color:#e57e6b0d}.ddoc .dark\:bg-Property\/15:is(.dark *){background-color:#7e57c026}.ddoc .dark\:bg-Property\/5:is(.dark *){background-color:#7e57c00d}.ddoc .dark\:bg-PropertyDark\/15:is(.dark *){background-color:#b37feb26}.ddoc .dark\:bg-PropertyDark\/5:is(.dark *){background-color:#b37feb0d}.ddoc .dark\:bg-TypeAlias\/15:is(.dark *){background-color:#a4478c26}.ddoc .dark\:bg-TypeAlias\/5:is(.dark *){background-color:#a4478c0d}.ddoc .dark\:bg-TypeAliasDark\/15:is(.dark *){background-color:#dd95cc26}.ddoc .dark\:bg-TypeAliasDark\/5:is(.dark *){background-color:#dd95cc0d}.ddoc .dark\:bg-Variable\/15:is(.dark *){background-color:#7e57c026}.ddoc .dark\:bg-Variable\/5:is(.dark *){background-color:#7e57c00d}.ddoc .dark\:bg-VariableDark\/15:is(.dark *){background-color:#b37feb26}.ddoc .dark\:bg-VariableDark\/5:is(.dark *){background-color:#b37feb0d}.ddoc .dark\:bg-abstract\/15:is(.dark *){background-color:#0cafc626}.ddoc .dark\:bg-abstract\/5:is(.dark *){background-color:#0cafc60d}.ddoc .dark\:bg-deprecated\/15:is(.dark *){background-color:#dc262626}.ddoc .dark\:bg-deprecated\/5:is(.dark *){background-color:#dc26260d}.ddoc .dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.ddoc .dark\:bg-new\/15:is(.dark *){background-color:#7b61ff26}.ddoc .dark\:bg-new\/5:is(.dark *){background-color:#7b61ff0d}.ddoc .dark\:bg-optional\/15:is(.dark *){background-color:#0cafc626}.ddoc .dark\:bg-optional\/5:is(.dark *){background-color:#0cafc60d}.ddoc .dark\:bg-other\/15:is(.dark *){background-color:#57534e26}.ddoc .dark\:bg-other\/5:is(.dark *){background-color:#57534e0d}.ddoc .dark\:bg-permissions\/15:is(.dark *){background-color:#0cafc626}.ddoc .dark\:bg-permissions\/5:is(.dark *){background-color:#0cafc60d}.ddoc .dark\:bg-private\/15:is(.dark *){background-color:#0cafc626}.ddoc .dark\:bg-private\/5:is(.dark *){background-color:#0cafc60d}.ddoc .dark\:bg-protected\/15:is(.dark *){background-color:#7b61ff26}.ddoc .dark\:bg-protected\/5:is(.dark *){background-color:#7b61ff0d}.ddoc .dark\:bg-readonly\/15:is(.dark *){background-color:#7b61ff26}.ddoc .dark\:bg-readonly\/5:is(.dark *){background-color:#7b61ff0d}.ddoc .dark\:bg-unstable\/15:is(.dark *){background-color:#7b61ff26}.ddoc .dark\:bg-unstable\/5:is(.dark *){background-color:#7b61ff0d}.ddoc .dark\:bg-writeonly\/15:is(.dark *){background-color:#7b61ff26}.ddoc .dark\:bg-writeonly\/5:is(.dark *){background-color:#7b61ff0d}.ddoc .dark\:text-Class:is(.dark *){--tw-text-opacity:1;color:rgb(32 180 75/var(--tw-text-opacity))}.ddoc .dark\:text-ClassDark:is(.dark *){--tw-text-opacity:1;color:rgb(135 238 164/var(--tw-text-opacity))}.ddoc .dark\:text-Enum:is(.dark *){--tw-text-opacity:1;color:rgb(34 171 176/var(--tw-text-opacity))}.ddoc .dark\:text-EnumDark:is(.dark *){--tw-text-opacity:1;color:rgb(63 206 209/var(--tw-text-opacity))}.ddoc .dark\:text-Function:is(.dark *){--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .dark\:text-FunctionDark:is(.dark *){--tw-text-opacity:1;color:rgb(76 195 255/var(--tw-text-opacity))}.ddoc .dark\:text-Interface:is(.dark *){--tw-text-opacity:1;color:rgb(210 160 100/var(--tw-text-opacity))}.ddoc .dark\:text-InterfaceDark:is(.dark *){--tw-text-opacity:1;color:rgb(187 115 59/var(--tw-text-opacity))}.ddoc .dark\:text-Method:is(.dark *){--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .dark\:text-MethodDark:is(.dark *){--tw-text-opacity:1;color:rgb(76 195 255/var(--tw-text-opacity))}.ddoc .dark\:text-Namespace:is(.dark *){--tw-text-opacity:1;color:rgb(210 86 70/var(--tw-text-opacity))}.ddoc .dark\:text-NamespaceDark:is(.dark *){--tw-text-opacity:1;color:rgb(229 126 107/var(--tw-text-opacity))}.ddoc .dark\:text-Property:is(.dark *){--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .dark\:text-PropertyDark:is(.dark *){--tw-text-opacity:1;color:rgb(179 127 235/var(--tw-text-opacity))}.ddoc .dark\:text-TypeAlias:is(.dark *){--tw-text-opacity:1;color:rgb(164 71 140/var(--tw-text-opacity))}.ddoc .dark\:text-TypeAliasDark:is(.dark *){--tw-text-opacity:1;color:rgb(221 149 204/var(--tw-text-opacity))}.ddoc .dark\:text-Variable:is(.dark *){--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .dark\:text-VariableDark:is(.dark *){--tw-text-opacity:1;color:rgb(179 127 235/var(--tw-text-opacity))}.ddoc .dark\:text-abstract:is(.dark *){--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .dark\:text-deprecated:is(.dark *){--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.ddoc .dark\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.ddoc .dark\:text-new:is(.dark *){--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .dark\:text-optional:is(.dark *){--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .dark\:text-other:is(.dark *){--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity))}.ddoc .dark\:text-permissions:is(.dark *),.ddoc .dark\:text-private:is(.dark *){--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .dark\:text-protected:is(.dark *),.ddoc .dark\:text-readonly:is(.dark *){--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .dark\:text-stone-200:is(.dark *){--tw-text-opacity:1;color:rgb(231 229 228/var(--tw-text-opacity))}.ddoc .dark\:text-unstable:is(.dark *){--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.ddoc .dark\:text-writeonly:is(.dark *){--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))} diff --git a/docs/~/appRuntime.AppRuntime.default.html b/docs/~/appRuntime.AppRuntime.default.html deleted file mode 100644 index 1dd915848..000000000 --- a/docs/~/appRuntime.AppRuntime.default.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntime.default - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.html b/docs/~/appRuntime.AppRuntime.html deleted file mode 100644 index 8c6fbd12b..000000000 --- a/docs/~/appRuntime.AppRuntime.html +++ /dev/null @@ -1,717 +0,0 @@ - - - - appRuntime.AppRuntime - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class appRuntime.AppRuntime -
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -AppRuntime(state?: S) - -
- - - - -
-
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Properties

- -
-
readonly
-
- - - - - - - - - - -state: S - -
- - - - -
-
-
- -
-
- - - - - - - - - - -addChannel(channel: Channel): void - -
- - - - -
-
- -
-
- -
-
- - - - - - - - - - -awaitAll(): Promise<void> - -
- - - - -
- -
-
-

- - - - - - - - - - -Static Properties

- -
-
readonly
-
- - - - - - - - - - -default: symbol - -
- - - - -
-
-
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.prototype.addChannel.html b/docs/~/appRuntime.AppRuntime.prototype.addChannel.html deleted file mode 100644 index 4954a641e..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.addChannel.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - appRuntime.AppRuntime.prototype.addChannel - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.prototype.addModule.html b/docs/~/appRuntime.AppRuntime.prototype.addModule.html deleted file mode 100644 index a229f7aa4..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.addModule.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - appRuntime.AppRuntime.prototype.addModule - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.prototype.awaitAll.html b/docs/~/appRuntime.AppRuntime.prototype.awaitAll.html deleted file mode 100644 index 8905979ee..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.awaitAll.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - appRuntime.AppRuntime.prototype.awaitAll - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.prototype.html b/docs/~/appRuntime.AppRuntime.prototype.html deleted file mode 100644 index b18bafec2..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/appRuntime.AppRuntime.prototype.setAsDefault.html b/docs/~/appRuntime.AppRuntime.prototype.setAsDefault.html deleted file mode 100644 index f28add37f..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.setAsDefault.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - appRuntime.AppRuntime.prototype.setAsDefault - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntime.prototype.state.html b/docs/~/appRuntime.AppRuntime.prototype.state.html deleted file mode 100644 index efeef907e..000000000 --- a/docs/~/appRuntime.AppRuntime.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntime.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.awaits.html b/docs/~/appRuntime.AppRuntimeState.awaits.html deleted file mode 100644 index 5fa180aab..000000000 --- a/docs/~/appRuntime.AppRuntimeState.awaits.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.awaits - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.channels.html b/docs/~/appRuntime.AppRuntimeState.channels.html deleted file mode 100644 index e7ea10f4e..000000000 --- a/docs/~/appRuntime.AppRuntimeState.channels.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.channels - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.di.html b/docs/~/appRuntime.AppRuntimeState.di.html deleted file mode 100644 index 9657eb666..000000000 --- a/docs/~/appRuntime.AppRuntimeState.di.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.di - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.events.html b/docs/~/appRuntime.AppRuntimeState.events.html deleted file mode 100644 index c12f7d91d..000000000 --- a/docs/~/appRuntime.AppRuntimeState.events.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.events - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.html b/docs/~/appRuntime.AppRuntimeState.html deleted file mode 100644 index cbf47da65..000000000 --- a/docs/~/appRuntime.AppRuntimeState.html +++ /dev/null @@ -1,512 +0,0 @@ - - - - appRuntime.AppRuntimeState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- type alias appRuntime.AppRuntimeState -
- - - - -
-

- - - - - - - - - - -Properties

- -
-
- - - - - - - - - - -runMode: runModes.RunMode - -
- - - - -
- -
- -
-
- - - - - - - - - - -di: services.di - -
- - - - -
-
- -
-
- - - - - - - - - - -channels: Map<string, Channel> - -
- - - - -
-
- -
-
- -
-
- - - - - - - - - - -awaits: Array<Promise<any>> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.modules.html b/docs/~/appRuntime.AppRuntimeState.modules.html deleted file mode 100644 index 0abd8591d..000000000 --- a/docs/~/appRuntime.AppRuntimeState.modules.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.modules - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.AppRuntimeState.runMode.html b/docs/~/appRuntime.AppRuntimeState.runMode.html deleted file mode 100644 index d24c32fac..000000000 --- a/docs/~/appRuntime.AppRuntimeState.runMode.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.AppRuntimeState.runMode - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.entrypoint.html b/docs/~/appRuntime.Module.entrypoint.html deleted file mode 100644 index 23adfa7ac..000000000 --- a/docs/~/appRuntime.Module.entrypoint.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.Module.entrypoint - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.html b/docs/~/appRuntime.Module.html deleted file mode 100644 index ff020df6d..000000000 --- a/docs/~/appRuntime.Module.html +++ /dev/null @@ -1,461 +0,0 @@ - - - - appRuntime.Module - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.manifest.html b/docs/~/appRuntime.Module.manifest.html deleted file mode 100644 index ce9c47e08..000000000 --- a/docs/~/appRuntime.Module.manifest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.Module.manifest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.name.html b/docs/~/appRuntime.Module.name.html deleted file mode 100644 index dd9aa180b..000000000 --- a/docs/~/appRuntime.Module.name.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.Module.name - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.provides.html b/docs/~/appRuntime.Module.provides.html deleted file mode 100644 index 76995dd5a..000000000 --- a/docs/~/appRuntime.Module.provides.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.Module.provides - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.Module.uses.html b/docs/~/appRuntime.Module.uses.html deleted file mode 100644 index 6eb8336d3..000000000 --- a/docs/~/appRuntime.Module.uses.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.Module.uses - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.createAppRuntimeState.html b/docs/~/appRuntime.createAppRuntimeState.html deleted file mode 100644 index 30fe2acb4..000000000 --- a/docs/~/appRuntime.createAppRuntimeState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - appRuntime.createAppRuntimeState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/appRuntime.html b/docs/~/appRuntime.html deleted file mode 100644 index 7bb678d33..000000000 --- a/docs/~/appRuntime.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - appRuntime - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace appRuntime -
- - - - -
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- appRuntime.Module - -
No documentation available
-
-
-
v
-
- appRuntime.createAppRuntimeState - -
No documentation available
-
-
-
-
-
-
-
-

Usage

import { appRuntime } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshot.html b/docs/~/bundler.AotSnapshot.html deleted file mode 100644 index 399210b70..000000000 --- a/docs/~/bundler.AotSnapshot.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - bundler.AotSnapshot - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class bundler.AotSnapshot -
- implements BuildSnapshot
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -AotSnapshot(state: AotSnapshotState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

- -
-
readonly
-
- - - - - - - - - - -paths: Array<string> - -
- - - - -
- -
-
- -
-
- - - - - - - - - - -dependencies(pathStr: string): Array<string> - -
- - - - -
-
- -
-
- - - - - - - - - - -read(pathStr: string): Promise<ReadableStream<Uint8Array> | null> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshot.prototype.dependencies.html b/docs/~/bundler.AotSnapshot.prototype.dependencies.html deleted file mode 100644 index 509bfc8db..000000000 --- a/docs/~/bundler.AotSnapshot.prototype.dependencies.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - bundler.AotSnapshot.prototype.dependencies - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshot.prototype.html b/docs/~/bundler.AotSnapshot.prototype.html deleted file mode 100644 index 42c318bbe..000000000 --- a/docs/~/bundler.AotSnapshot.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/bundler.AotSnapshot.prototype.paths.html b/docs/~/bundler.AotSnapshot.prototype.paths.html deleted file mode 100644 index 40c883136..000000000 --- a/docs/~/bundler.AotSnapshot.prototype.paths.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - bundler.AotSnapshot.prototype.paths - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshot.prototype.read.html b/docs/~/bundler.AotSnapshot.prototype.read.html deleted file mode 100644 index 9703c1b0f..000000000 --- a/docs/~/bundler.AotSnapshot.prototype.read.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - bundler.AotSnapshot.prototype.read - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshot.prototype.state.html b/docs/~/bundler.AotSnapshot.prototype.state.html deleted file mode 100644 index 532babad4..000000000 --- a/docs/~/bundler.AotSnapshot.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.AotSnapshot.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshotState.dependencyMapping.html b/docs/~/bundler.AotSnapshotState.dependencyMapping.html deleted file mode 100644 index fb21bedd5..000000000 --- a/docs/~/bundler.AotSnapshotState.dependencyMapping.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.AotSnapshotState.dependencyMapping - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshotState.files.html b/docs/~/bundler.AotSnapshotState.files.html deleted file mode 100644 index 309b5bce1..000000000 --- a/docs/~/bundler.AotSnapshotState.files.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.AotSnapshotState.files - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.AotSnapshotState.html b/docs/~/bundler.AotSnapshotState.html deleted file mode 100644 index e31554114..000000000 --- a/docs/~/bundler.AotSnapshotState.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - bundler.AotSnapshotState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshot.dependencies.html b/docs/~/bundler.BuildSnapshot.dependencies.html deleted file mode 100644 index 1202e8567..000000000 --- a/docs/~/bundler.BuildSnapshot.dependencies.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - bundler.BuildSnapshot.dependencies - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshot.html b/docs/~/bundler.BuildSnapshot.html deleted file mode 100644 index 0d515301e..000000000 --- a/docs/~/bundler.BuildSnapshot.html +++ /dev/null @@ -1,382 +0,0 @@ - - - - bundler.BuildSnapshot - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- type alias bundler.BuildSnapshot -
- - - - -
-

- - - - - - - - - - -Properties

- -
-
readonly
-
- - - - - - - - - - -paths: Array<string> - -
- - - - -

The list of files contained in this snapshot, not prefixed by a slash.

-
-
-
- -
-
- - - - - - - - - - -read(path: string):
ReadableStream<Uint8Array>
| Uint8Array
| null
| Promise<
ReadableStream<Uint8Array>
| Uint8Array
| null
>
-
-
- - - - -

For a given file, return it's contents.

-
-
- -
-
- - - - - - - - - - -dependencies(pathStr: string): Array<string> - -
- - - - -

For a given entrypoint, return it's list of dependencies.

-

Returns an empty array if the entrypoint does not exist.

-
-
-
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshot.paths.html b/docs/~/bundler.BuildSnapshot.paths.html deleted file mode 100644 index c2179ceea..000000000 --- a/docs/~/bundler.BuildSnapshot.paths.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.BuildSnapshot.paths - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- property bundler.BuildSnapshot.paths -
- - - - -

The list of files contained in this snapshot, not prefixed by a slash.

-
- -
-
Array<string> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshot.read.html b/docs/~/bundler.BuildSnapshot.read.html deleted file mode 100644 index 36438578b..000000000 --- a/docs/~/bundler.BuildSnapshot.read.html +++ /dev/null @@ -1,384 +0,0 @@ - - - - bundler.BuildSnapshot.read - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method bundler.BuildSnapshot.read -
- - - - -
- - - - - - - - - - - -BuildSnapshot.read(path: string):
ReadableStream<Uint8Array>
| Uint8Array
| null
| Promise<
ReadableStream<Uint8Array>
| Uint8Array
| null
>
-

For a given file, return it's contents.

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -path: string - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
ReadableStream<Uint8Array>
| Uint8Array
| null
| Promise<
ReadableStream<Uint8Array>
| Uint8Array
| null
>
-
-
- - - - -
-
-
- -
-
- -
- - - - -

If the file is not contained in this snapshot.

-
-
-
-
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshotSerialized.build_id.html b/docs/~/bundler.BuildSnapshotSerialized.build_id.html deleted file mode 100644 index 153831a04..000000000 --- a/docs/~/bundler.BuildSnapshotSerialized.build_id.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.BuildSnapshotSerialized.build_id - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshotSerialized.files.html b/docs/~/bundler.BuildSnapshotSerialized.files.html deleted file mode 100644 index 1d440c3e3..000000000 --- a/docs/~/bundler.BuildSnapshotSerialized.files.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.BuildSnapshotSerialized.files - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.BuildSnapshotSerialized.html b/docs/~/bundler.BuildSnapshotSerialized.html deleted file mode 100644 index e23ca433f..000000000 --- a/docs/~/bundler.BuildSnapshotSerialized.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - bundler.BuildSnapshotSerialized - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.Builder.build.html b/docs/~/bundler.Builder.build.html deleted file mode 100644 index dc2f3bc8a..000000000 --- a/docs/~/bundler.Builder.build.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - bundler.Builder.build - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.Builder.html b/docs/~/bundler.Builder.html deleted file mode 100644 index e92097bb6..000000000 --- a/docs/~/bundler.Builder.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - bundler.Builder - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilder.html b/docs/~/bundler.EsbuildBuilder.html deleted file mode 100644 index 0f3c9092d..000000000 --- a/docs/~/bundler.EsbuildBuilder.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - bundler.EsbuildBuilder - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilder.prototype.build.html b/docs/~/bundler.EsbuildBuilder.prototype.build.html deleted file mode 100644 index 2e5f566ed..000000000 --- a/docs/~/bundler.EsbuildBuilder.prototype.build.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - bundler.EsbuildBuilder.prototype.build - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilder.prototype.html b/docs/~/bundler.EsbuildBuilder.prototype.html deleted file mode 100644 index aba129828..000000000 --- a/docs/~/bundler.EsbuildBuilder.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/bundler.EsbuildBuilder.prototype.state.html b/docs/~/bundler.EsbuildBuilder.prototype.state.html deleted file mode 100644 index 83dc7ad04..000000000 --- a/docs/~/bundler.EsbuildBuilder.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilder.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.absoluteWorkingDir.html b/docs/~/bundler.EsbuildBuilderOptions.absoluteWorkingDir.html deleted file mode 100644 index bdb0e6fb1..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.absoluteWorkingDir.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.absoluteWorkingDir - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.basePath.html b/docs/~/bundler.EsbuildBuilderOptions.basePath.html deleted file mode 100644 index 66afa3e9f..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.basePath.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.basePath - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.buildID.html b/docs/~/bundler.EsbuildBuilderOptions.buildID.html deleted file mode 100644 index 1a9d59f4b..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.buildID.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.buildID - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.configPath.html b/docs/~/bundler.EsbuildBuilderOptions.configPath.html deleted file mode 100644 index 60d748ca1..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.configPath.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.configPath - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- property bundler.EsbuildBuilderOptions.configPath -
- - - - -

The path to the deno.json / deno.jsonc config file.

-
- -
-
string - -
- - - - -
-
-
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.dev.html b/docs/~/bundler.EsbuildBuilderOptions.dev.html deleted file mode 100644 index fc02ade00..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.dev.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.dev - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.entrypoints.html b/docs/~/bundler.EsbuildBuilderOptions.entrypoints.html deleted file mode 100644 index 53e71a968..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.entrypoints.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.entrypoints - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- property bundler.EsbuildBuilderOptions.entrypoints -
- - - - -

The entrypoints, mapped from name to URL.

-
- -
-
Record<string, string> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.html b/docs/~/bundler.EsbuildBuilderOptions.html deleted file mode 100644 index e32a271a4..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.html +++ /dev/null @@ -1,679 +0,0 @@ - - - - bundler.EsbuildBuilderOptions - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- type alias bundler.EsbuildBuilderOptions -
- - - - -
-

- - - - - - - - - - -Properties

- -

The build ID.

-
-
- -
-
- - - - - - - - - - -entrypoints: Record<string, string> - -
- - - - -

The entrypoints, mapped from name to URL.

-
-
- -

Whether or not this is a dev build.

-
-
- -

The path to the deno.json / deno.jsonc config file.

-
-
- -
-
optional
-
- - - - - - - - - - -jsx: string - -
- - - - -

The JSX configuration.

-
-
- -
-
optional
-
- - - - - - - - - - -jsxImportSource: string - -
- - - - -
-
- -
-
- - - - - - - - - - -target: string | Array<string> - -
- - - - -
- -
- -
-
optional
-
- - - - - - - - - - -basePath: string - -
- - - - -
-
-
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.jsx.html b/docs/~/bundler.EsbuildBuilderOptions.jsx.html deleted file mode 100644 index cb5c9f4c2..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.jsx.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.jsx - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.jsxImportSource.html b/docs/~/bundler.EsbuildBuilderOptions.jsxImportSource.html deleted file mode 100644 index 0c3fea014..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.jsxImportSource.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.jsxImportSource - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderOptions.target.html b/docs/~/bundler.EsbuildBuilderOptions.target.html deleted file mode 100644 index b8937a5d9..000000000 --- a/docs/~/bundler.EsbuildBuilderOptions.target.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilderOptions.target - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderState.html b/docs/~/bundler.EsbuildBuilderState.html deleted file mode 100644 index 7f168d9c8..000000000 --- a/docs/~/bundler.EsbuildBuilderState.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - bundler.EsbuildBuilderState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildBuilderState.options.html b/docs/~/bundler.EsbuildBuilderState.options.html deleted file mode 100644 index 93cdcfacd..000000000 --- a/docs/~/bundler.EsbuildBuilderState.options.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildBuilderState.options - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshot.html b/docs/~/bundler.EsbuildSnapshot.html deleted file mode 100644 index 389394713..000000000 --- a/docs/~/bundler.EsbuildSnapshot.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - bundler.EsbuildSnapshot - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class bundler.EsbuildSnapshot -
- implements BuildSnapshot
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -EsbuildSnapshot(state: EsbuildSnapshotState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

- -
-
readonly
-
- - - - - - - - - - -paths: Array<string> - -
- - - - -
- -
-
- -
-
- - - - - - - - - - -dependencies(pathStr: string): Array<string> - -
- - - - -
-
- -
-
- - - - - - - - - - -read(pathStr: string): Uint8Array | null - -
- - - - -
-
-
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshot.prototype.dependencies.html b/docs/~/bundler.EsbuildSnapshot.prototype.dependencies.html deleted file mode 100644 index fc43e5454..000000000 --- a/docs/~/bundler.EsbuildSnapshot.prototype.dependencies.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - bundler.EsbuildSnapshot.prototype.dependencies - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshot.prototype.html b/docs/~/bundler.EsbuildSnapshot.prototype.html deleted file mode 100644 index 34f034c4f..000000000 --- a/docs/~/bundler.EsbuildSnapshot.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/bundler.EsbuildSnapshot.prototype.paths.html b/docs/~/bundler.EsbuildSnapshot.prototype.paths.html deleted file mode 100644 index 1f5421619..000000000 --- a/docs/~/bundler.EsbuildSnapshot.prototype.paths.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - bundler.EsbuildSnapshot.prototype.paths - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshot.prototype.read.html b/docs/~/bundler.EsbuildSnapshot.prototype.read.html deleted file mode 100644 index 2c6a42c0d..000000000 --- a/docs/~/bundler.EsbuildSnapshot.prototype.read.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - bundler.EsbuildSnapshot.prototype.read - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshot.prototype.state.html b/docs/~/bundler.EsbuildSnapshot.prototype.state.html deleted file mode 100644 index a5b324f17..000000000 --- a/docs/~/bundler.EsbuildSnapshot.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildSnapshot.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshotState.dependencyMapping.html b/docs/~/bundler.EsbuildSnapshotState.dependencyMapping.html deleted file mode 100644 index 67350db99..000000000 --- a/docs/~/bundler.EsbuildSnapshotState.dependencyMapping.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildSnapshotState.dependencyMapping - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshotState.files.html b/docs/~/bundler.EsbuildSnapshotState.files.html deleted file mode 100644 index 9b703f2bc..000000000 --- a/docs/~/bundler.EsbuildSnapshotState.files.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.EsbuildSnapshotState.files - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.EsbuildSnapshotState.html b/docs/~/bundler.EsbuildSnapshotState.html deleted file mode 100644 index 27e15c260..000000000 --- a/docs/~/bundler.EsbuildSnapshotState.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - bundler.EsbuildSnapshotState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.createAotSnapshotState.html b/docs/~/bundler.createAotSnapshotState.html deleted file mode 100644 index 1913b7b8a..000000000 --- a/docs/~/bundler.createAotSnapshotState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.createAotSnapshotState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.createEsbuildBuilderState.html b/docs/~/bundler.createEsbuildBuilderState.html deleted file mode 100644 index 5e88bdc63..000000000 --- a/docs/~/bundler.createEsbuildBuilderState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.createEsbuildBuilderState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.createEsbuildSnapshotState.html b/docs/~/bundler.createEsbuildSnapshotState.html deleted file mode 100644 index dfe154841..000000000 --- a/docs/~/bundler.createEsbuildSnapshotState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.createEsbuildSnapshotState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/bundler.html b/docs/~/bundler.html deleted file mode 100644 index 865b32f1d..000000000 --- a/docs/~/bundler.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - bundler - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace bundler -
- - - - -
c
-
- bundler.AotSnapshot - -
No documentation available
-
c
-
- bundler.EsbuildBuilder - -
No documentation available
-
c
-
- bundler.EsbuildSnapshot - -
No documentation available
-
-
-
-

- - - - - - - - - - -Interfaces

I
-
- bundler.Builder - -
No documentation available
-
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- bundler.AotSnapshotState - -
No documentation available
-
T
-
- bundler.BuildSnapshot - -
No documentation available
-
T
-
- bundler.BuildSnapshotSerialized - -
No documentation available
-
T
-
- bundler.EsbuildBuilderState - -
No documentation available
-
T
-
- bundler.EsbuildSnapshotState - -
No documentation available
-
-
-
v
-
- bundler.createAotSnapshotState - -
No documentation available
-
v
-
- bundler.createEsbuildBuilderState - -
No documentation available
-
v
-
- bundler.createEsbuildSnapshotState - -
No documentation available
-
v
-
- bundler.loadAotSnapshot - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/bundler.loadAotSnapshot.html b/docs/~/bundler.loadAotSnapshot.html deleted file mode 100644 index 2d8ad6854..000000000 --- a/docs/~/bundler.loadAotSnapshot.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - bundler.loadAotSnapshot - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { bundler } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.CollectExportsOptions.baseDir.html b/docs/~/collector.CollectExportsOptions.baseDir.html deleted file mode 100644 index cdbfb4b7e..000000000 --- a/docs/~/collector.CollectExportsOptions.baseDir.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.CollectExportsOptions.baseDir - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.CollectExportsOptions.exportFilter.html b/docs/~/collector.CollectExportsOptions.exportFilter.html deleted file mode 100644 index b90d42546..000000000 --- a/docs/~/collector.CollectExportsOptions.exportFilter.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.CollectExportsOptions.exportFilter - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- property collector.CollectExportsOptions.exportFilter -
- - - - -
- -
-
(entries: [string, unknown][]) => Promise<[string, unknown][]> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { type collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.CollectExportsOptions.globFilter.html b/docs/~/collector.CollectExportsOptions.globFilter.html deleted file mode 100644 index 1ef7dc4e1..000000000 --- a/docs/~/collector.CollectExportsOptions.globFilter.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.CollectExportsOptions.globFilter - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.CollectExportsOptions.html b/docs/~/collector.CollectExportsOptions.html deleted file mode 100644 index 5eb7207ae..000000000 --- a/docs/~/collector.CollectExportsOptions.html +++ /dev/null @@ -1,409 +0,0 @@ - - - - collector.CollectExportsOptions - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.CollectExportsOptions.ignoreFilePattern.html b/docs/~/collector.CollectExportsOptions.ignoreFilePattern.html deleted file mode 100644 index a33f0af2e..000000000 --- a/docs/~/collector.CollectExportsOptions.ignoreFilePattern.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.CollectExportsOptions.ignoreFilePattern - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.ExportItem.html b/docs/~/collector.ExportItem.html deleted file mode 100644 index aa06c62b1..000000000 --- a/docs/~/collector.ExportItem.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.ExportItem - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.buildManifest.html b/docs/~/collector.buildManifest.html deleted file mode 100644 index f880ffd0f..000000000 --- a/docs/~/collector.buildManifest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.buildManifest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.buildManifestFile.html b/docs/~/collector.buildManifestFile.html deleted file mode 100644 index 13c5b6c60..000000000 --- a/docs/~/collector.buildManifestFile.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.buildManifestFile - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.collectExports.html b/docs/~/collector.collectExports.html deleted file mode 100644 index f662d4caa..000000000 --- a/docs/~/collector.collectExports.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.collectExports - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.html b/docs/~/collector.html deleted file mode 100644 index d0ddcc581..000000000 --- a/docs/~/collector.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - collector - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace collector -
- - - - -
f
-
- collector.walkFiles - -
No documentation available
-
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- collector.ExportItem - -
No documentation available
-
-
-
v
-
- collector.buildManifest - -
No documentation available
-
v
-
- collector.buildManifestFile - -
No documentation available
-
v
-
- collector.collectExports - -
No documentation available
-
v
-
- collector.specifierToIdentifier - -
No documentation available
-
v
-
- collector.writeManifestToString - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/collector.specifierToIdentifier.html b/docs/~/collector.specifierToIdentifier.html deleted file mode 100644 index bd65813eb..000000000 --- a/docs/~/collector.specifierToIdentifier.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.specifierToIdentifier - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.walkFiles.html b/docs/~/collector.walkFiles.html deleted file mode 100644 index 5a3ded0f4..000000000 --- a/docs/~/collector.walkFiles.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - collector.walkFiles - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- function collector.walkFiles -
- - - - -
- - - - - - - - - - - -walkFiles(
baseDir: string,
globFilter: string | undefined,
ignoreFilePattern: RegExp,
): AsyncGenerator<string>
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -baseDir: string - -
- - - - -
-
- -
-
- - - - - - - - - - -globFilter: string | undefined - -
- - - - -
-
- -
-
- - - - - - - - - - -ignoreFilePattern: RegExp - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
AsyncGenerator<string> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/collector.writeManifestToString.html b/docs/~/collector.writeManifestToString.html deleted file mode 100644 index cf7b55a6a..000000000 --- a/docs/~/collector.writeManifestToString.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - collector.writeManifestToString - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable collector.writeManifestToString -
- - - - -
- -
-
(collection: Array<[string, Array<[string, unknown]>]>) => Promise<string> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { collector } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Config.html b/docs/~/config.Config.html deleted file mode 100644 index 3250bd590..000000000 --- a/docs/~/config.Config.html +++ /dev/null @@ -1,403 +0,0 @@ - - - - config.Config - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Config.prototype.html b/docs/~/config.Config.prototype.html deleted file mode 100644 index f0ab115de..000000000 --- a/docs/~/config.Config.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/config.Config.prototype.setKeyMeta.html b/docs/~/config.Config.prototype.setKeyMeta.html deleted file mode 100644 index 0745edaa5..000000000 --- a/docs/~/config.Config.prototype.setKeyMeta.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - config.Config.prototype.setKeyMeta - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Config.prototype.state.html b/docs/~/config.Config.prototype.state.html deleted file mode 100644 index d472053e7..000000000 --- a/docs/~/config.Config.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.Config.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItem.html b/docs/~/config.ConfigItem.html deleted file mode 100644 index 092ed34f6..000000000 --- a/docs/~/config.ConfigItem.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - config.ConfigItem - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItem.key.html b/docs/~/config.ConfigItem.key.html deleted file mode 100644 index 535e6b19c..000000000 --- a/docs/~/config.ConfigItem.key.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItem.key - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItem.meta.html b/docs/~/config.ConfigItem.meta.html deleted file mode 100644 index 7cba60f3c..000000000 --- a/docs/~/config.ConfigItem.meta.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItem.meta - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItem.value.html b/docs/~/config.ConfigItem.value.html deleted file mode 100644 index 72abd7964..000000000 --- a/docs/~/config.ConfigItem.value.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItem.value - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItemMeta.description.html b/docs/~/config.ConfigItemMeta.description.html deleted file mode 100644 index a590f3a4c..000000000 --- a/docs/~/config.ConfigItemMeta.description.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItemMeta.description - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItemMeta.disallowSource.html b/docs/~/config.ConfigItemMeta.disallowSource.html deleted file mode 100644 index 022863781..000000000 --- a/docs/~/config.ConfigItemMeta.disallowSource.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItemMeta.disallowSource - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItemMeta.html b/docs/~/config.ConfigItemMeta.html deleted file mode 100644 index 77ed05bd9..000000000 --- a/docs/~/config.ConfigItemMeta.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - config.ConfigItemMeta - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItemMeta.ttl.html b/docs/~/config.ConfigItemMeta.ttl.html deleted file mode 100644 index d772fe4ea..000000000 --- a/docs/~/config.ConfigItemMeta.ttl.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItemMeta.ttl - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigItemMeta.type.html b/docs/~/config.ConfigItemMeta.type.html deleted file mode 100644 index 93f9ce37c..000000000 --- a/docs/~/config.ConfigItemMeta.type.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigItemMeta.type - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigState.html b/docs/~/config.ConfigState.html deleted file mode 100644 index e8afc4f5a..000000000 --- a/docs/~/config.ConfigState.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - config.ConfigState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ConfigState.meta.html b/docs/~/config.ConfigState.meta.html deleted file mode 100644 index a68bd377e..000000000 --- a/docs/~/config.ConfigState.meta.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.ConfigState.meta - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ContainerReadable.getBooleanValue.html b/docs/~/config.ContainerReadable.getBooleanValue.html deleted file mode 100644 index d3bb61ef4..000000000 --- a/docs/~/config.ContainerReadable.getBooleanValue.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - config.ContainerReadable.getBooleanValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.ContainerReadable.getBooleanValue -
- - - - -
- - - - - - - - - - - -ContainerReadable.getBooleanValue(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<boolean>
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: boolean - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -requestContext: RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<boolean> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ContainerReadable.getNumberValue.html b/docs/~/config.ContainerReadable.getNumberValue.html deleted file mode 100644 index d0beb88d6..000000000 --- a/docs/~/config.ContainerReadable.getNumberValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.ContainerReadable.getNumberValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.ContainerReadable.getNumberValue -
- - - - -
- - - - - - - - - - - -ContainerReadable.getNumberValue<T = number>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

- -
-
- - - - - - - - - - -T = number - -
- - - - -
-
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: boolean - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -requestContext: RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ContainerReadable.getObjectValue.html b/docs/~/config.ContainerReadable.getObjectValue.html deleted file mode 100644 index e9db2a587..000000000 --- a/docs/~/config.ContainerReadable.getObjectValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.ContainerReadable.getObjectValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.ContainerReadable.getObjectValue -
- - - - -
- - - - - - - - - - - -ContainerReadable.getObjectValue<T>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: boolean - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -requestContext: RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ContainerReadable.getStringValue.html b/docs/~/config.ContainerReadable.getStringValue.html deleted file mode 100644 index 8741a3792..000000000 --- a/docs/~/config.ContainerReadable.getStringValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.ContainerReadable.getStringValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.ContainerReadable.getStringValue -
- - - - -
- - - - - - - - - - - -ContainerReadable.getStringValue<T = string>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

- -
-
- - - - - - - - - - -T = string - -
- - - - -
-
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: boolean - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -requestContext: RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.ContainerReadable.html b/docs/~/config.ContainerReadable.html deleted file mode 100644 index 61594aa51..000000000 --- a/docs/~/config.ContainerReadable.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - config.ContainerReadable - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- interface config.ContainerReadable -
- - - - -
- -
-
- - - - - - - - - - -getBooleanValue(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<boolean>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getNumberValue<T = number>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getObjectValue<T>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getStringValue<T = string>(
flagKey: string,
defaultValue: boolean,
requestContext?: RequestContext,
): Promise<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.html b/docs/~/config.DotenvSource.html deleted file mode 100644 index 20adb39d6..000000000 --- a/docs/~/config.DotenvSource.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - config.DotenvSource - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class config.DotenvSource -
- implements [primitives.Source]
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -DotenvSource(state?: DotenvSourceState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

-
- -
-
- - - - - - - - - - -getBooleanValue(
_flagKey: string,
defaultValue: boolean,
_requestContext?: primitives.RequestContext,
): Promise<boolean>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getNumberValue<T = number>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getObjectValue<T>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getStringValue<T = string>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.prototype.getBooleanValue.html b/docs/~/config.DotenvSource.prototype.getBooleanValue.html deleted file mode 100644 index d6e665c1e..000000000 --- a/docs/~/config.DotenvSource.prototype.getBooleanValue.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - config.DotenvSource.prototype.getBooleanValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.DotenvSource.prototype.getBooleanValue -
- - - - -
- - - - - - - - - - - -DotenvSource.prototype.getBooleanValue(
_flagKey: string,
defaultValue: boolean,
_requestContext?: primitives.RequestContext,
): Promise<boolean>
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -_flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: boolean - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -_requestContext: primitives.RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<boolean> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.prototype.getNumberValue.html b/docs/~/config.DotenvSource.prototype.getNumberValue.html deleted file mode 100644 index 51b659e82..000000000 --- a/docs/~/config.DotenvSource.prototype.getNumberValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.DotenvSource.prototype.getNumberValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.DotenvSource.prototype.getNumberValue -
- - - - -
- - - - - - - - - - - -DotenvSource.prototype.getNumberValue<T = number>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

- -
-
- - - - - - - - - - -T = number - -
- - - - -
-
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -_flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: T - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -_requestContext: primitives.RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.prototype.getObjectValue.html b/docs/~/config.DotenvSource.prototype.getObjectValue.html deleted file mode 100644 index b7224e2cc..000000000 --- a/docs/~/config.DotenvSource.prototype.getObjectValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.DotenvSource.prototype.getObjectValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.DotenvSource.prototype.getObjectValue -
- - - - -
- - - - - - - - - - - -DotenvSource.prototype.getObjectValue<T>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -_flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: T - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -_requestContext: primitives.RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.prototype.getStringValue.html b/docs/~/config.DotenvSource.prototype.getStringValue.html deleted file mode 100644 index 7ea442dff..000000000 --- a/docs/~/config.DotenvSource.prototype.getStringValue.html +++ /dev/null @@ -1,511 +0,0 @@ - - - - config.DotenvSource.prototype.getStringValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method config.DotenvSource.prototype.getStringValue -
- - - - -
- - - - - - - - - - - -DotenvSource.prototype.getStringValue<T = string>(
_flagKey: string,
defaultValue: T,
_requestContext?: primitives.RequestContext,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

- -
-
- - - - - - - - - - -T = string - -
- - - - -
-
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -_flagKey: string - -
- - - - -
-
- -
-
- - - - - - - - - - -defaultValue: T - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -_requestContext: primitives.RequestContext - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSource.prototype.html b/docs/~/config.DotenvSource.prototype.html deleted file mode 100644 index 9aa91fc03..000000000 --- a/docs/~/config.DotenvSource.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/config.DotenvSource.prototype.state.html b/docs/~/config.DotenvSource.prototype.state.html deleted file mode 100644 index 5cb510ff1..000000000 --- a/docs/~/config.DotenvSource.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.DotenvSource.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.DotenvSourceState.html b/docs/~/config.DotenvSourceState.html deleted file mode 100644 index 2ed6d6bf6..000000000 --- a/docs/~/config.DotenvSourceState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.DotenvSourceState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Provider.html b/docs/~/config.Provider.html deleted file mode 100644 index 43a938cb4..000000000 --- a/docs/~/config.Provider.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.Provider - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.RequestContext.html b/docs/~/config.RequestContext.html deleted file mode 100644 index b7ef9bba3..000000000 --- a/docs/~/config.RequestContext.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - config.RequestContext - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Source.html b/docs/~/config.Source.html deleted file mode 100644 index afb06e37a..000000000 --- a/docs/~/config.Source.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - config.Source - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.Source.state.html b/docs/~/config.Source.state.html deleted file mode 100644 index aa9bf0b22..000000000 --- a/docs/~/config.Source.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.Source.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.SourceState.html b/docs/~/config.SourceState.html deleted file mode 100644 index f0527d0af..000000000 --- a/docs/~/config.SourceState.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - config.SourceState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.SourceState.id.html b/docs/~/config.SourceState.id.html deleted file mode 100644 index 67c4895a3..000000000 --- a/docs/~/config.SourceState.id.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.SourceState.id - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.createConfigState.html b/docs/~/config.createConfigState.html deleted file mode 100644 index 72df5c34a..000000000 --- a/docs/~/config.createConfigState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.createConfigState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.createDotenvSourceState.html b/docs/~/config.createDotenvSourceState.html deleted file mode 100644 index dc357a1b3..000000000 --- a/docs/~/config.createDotenvSourceState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - config.createDotenvSourceState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { config } from ".";
-
-
-
-
-
- - diff --git a/docs/~/config.html b/docs/~/config.html deleted file mode 100644 index f5024c875..000000000 --- a/docs/~/config.html +++ /dev/null @@ -1,335 +0,0 @@ - - - - config - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace config -
- - - - -
c
-
- config.Config - -
No documentation available
-
-
-
-

- - - - - - - - - - -Interfaces

-
-
-

- - - - - - - - - - -Type Aliases

T
-
- config.ConfigItem - -
No documentation available
-
T
- -
T
-
- config.ConfigState - -
No documentation available
-
T
-
- config.DotenvSourceState - -
No documentation available
-
T
-
- config.Provider - -
No documentation available
-
T
-
- config.RequestContext - -
No documentation available
-
T
-
- config.Source - -
No documentation available
-
T
-
- config.SourceState - -
No documentation available
-
-
-
v
-
- config.createConfigState - -
No documentation available
-
v
-
- config.createDotenvSourceState - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/di.ClassService.html b/docs/~/di.ClassService.html deleted file mode 100644 index 2658a2e6b..000000000 --- a/docs/~/di.ClassService.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ClassService - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Factory.html b/docs/~/di.Factory.html deleted file mode 100644 index fa5415cbb..000000000 --- a/docs/~/di.Factory.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - di.Factory - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.FunctionService.html b/docs/~/di.FunctionService.html deleted file mode 100644 index c907517cc..000000000 --- a/docs/~/di.FunctionService.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.FunctionService - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.PromisableBuilder.html b/docs/~/di.PromisableBuilder.html deleted file mode 100644 index db5a1d58b..000000000 --- a/docs/~/di.PromisableBuilder.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - di.PromisableBuilder - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.build.html b/docs/~/di.Registry.prototype.build.html deleted file mode 100644 index 28403825f..000000000 --- a/docs/~/di.Registry.prototype.build.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - di.Registry.prototype.build - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.descriptors.html b/docs/~/di.Registry.prototype.descriptors.html deleted file mode 100644 index 494416a53..000000000 --- a/docs/~/di.Registry.prototype.descriptors.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.Registry.prototype.descriptors - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.html b/docs/~/di.Registry.prototype.html deleted file mode 100644 index c77220ccf..000000000 --- a/docs/~/di.Registry.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/di.Registry.prototype.set.html b/docs/~/di.Registry.prototype.set.html deleted file mode 100644 index 8aa6fe785..000000000 --- a/docs/~/di.Registry.prototype.set.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.Registry.prototype.set - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.setLazy.html b/docs/~/di.Registry.prototype.setLazy.html deleted file mode 100644 index 2286b03ca..000000000 --- a/docs/~/di.Registry.prototype.setLazy.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.Registry.prototype.setLazy - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.setScoped.html b/docs/~/di.Registry.prototype.setScoped.html deleted file mode 100644 index 387948151..000000000 --- a/docs/~/di.Registry.prototype.setScoped.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.Registry.prototype.setScoped - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Registry.prototype.setTransient.html b/docs/~/di.Registry.prototype.setTransient.html deleted file mode 100644 index fb2d3606c..000000000 --- a/docs/~/di.Registry.prototype.setTransient.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.Registry.prototype.setTransient - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.html b/docs/~/di.Scope.html deleted file mode 100644 index 66e0156e8..000000000 --- a/docs/~/di.Scope.html +++ /dev/null @@ -1,801 +0,0 @@ - - - - di.Scope - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class di.Scope -
- implements ServiceScope<K, V>
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -Scope(
registry: ServiceRegistry<K, V>,
parent?: ServiceScope<K, V>,
)
-
-
- - - - -
-
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Properties

- -
- - -
-
-
- -
-
- - - - - - - - - - -get<V2 = V>(
token: K,
defaultValue?: V2,
): ServiceResolution<V2>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getMany(...tokens: ReadonlyArray<K>): ReadonlyArray<ServiceResolution<V>> - -
- - - - -
-
- -
-
- - - - - - - - - - -invoke<T extends functions.GenericFunction<ReturnType<T>, any>>(fn: T): ReturnType<T> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.createScope.html b/docs/~/di.Scope.prototype.createScope.html deleted file mode 100644 index 0d312b694..000000000 --- a/docs/~/di.Scope.prototype.createScope.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - di.Scope.prototype.createScope - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.get.html b/docs/~/di.Scope.prototype.get.html deleted file mode 100644 index c4de74f59..000000000 --- a/docs/~/di.Scope.prototype.get.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - di.Scope.prototype.get - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.getMany.html b/docs/~/di.Scope.prototype.getMany.html deleted file mode 100644 index 63bb3020c..000000000 --- a/docs/~/di.Scope.prototype.getMany.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - di.Scope.prototype.getMany - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.html b/docs/~/di.Scope.prototype.html deleted file mode 100644 index 24bc8c08b..000000000 --- a/docs/~/di.Scope.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/di.Scope.prototype.invoke.html b/docs/~/di.Scope.prototype.invoke.html deleted file mode 100644 index be3fd02e0..000000000 --- a/docs/~/di.Scope.prototype.invoke.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - di.Scope.prototype.invoke - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.items.html b/docs/~/di.Scope.prototype.items.html deleted file mode 100644 index 5bba8c85a..000000000 --- a/docs/~/di.Scope.prototype.items.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.Scope.prototype.items - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.registry.html b/docs/~/di.Scope.prototype.registry.html deleted file mode 100644 index d9562f63d..000000000 --- a/docs/~/di.Scope.prototype.registry.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.Scope.prototype.registry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Scope.prototype.rootScope.html b/docs/~/di.Scope.prototype.rootScope.html deleted file mode 100644 index 40b00e1a7..000000000 --- a/docs/~/di.Scope.prototype.rootScope.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.Scope.prototype.rootScope - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceDescriptor.html b/docs/~/di.ServiceDescriptor.html deleted file mode 100644 index 5888ea0ec..000000000 --- a/docs/~/di.ServiceDescriptor.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - di.ServiceDescriptor - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceKey.html b/docs/~/di.ServiceKey.html deleted file mode 100644 index c7ebb0443..000000000 --- a/docs/~/di.ServiceKey.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceKey - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistry.html b/docs/~/di.ServiceRegistry.html deleted file mode 100644 index 334eaca83..000000000 --- a/docs/~/di.ServiceRegistry.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - di.ServiceRegistry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistryWritable.html b/docs/~/di.ServiceRegistryWritable.html deleted file mode 100644 index 8b2a72b6c..000000000 --- a/docs/~/di.ServiceRegistryWritable.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - di.ServiceRegistryWritable - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- interface di.ServiceRegistryWritable -
- - - - -
-

- - - - - - - - - - -Type Parameters

-
- -
-
- - - - - - - - - - -set(
token: K,
value: promises.Promisable<V>,
): ServiceRegistry<K, V>
-
-
- - - - -
- - - -
-
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistryWritable.set.html b/docs/~/di.ServiceRegistryWritable.set.html deleted file mode 100644 index 8a0c5619f..000000000 --- a/docs/~/di.ServiceRegistryWritable.set.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.ServiceRegistryWritable.set - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistryWritable.setLazy.html b/docs/~/di.ServiceRegistryWritable.setLazy.html deleted file mode 100644 index 3743168b7..000000000 --- a/docs/~/di.ServiceRegistryWritable.setLazy.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.ServiceRegistryWritable.setLazy - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistryWritable.setScoped.html b/docs/~/di.ServiceRegistryWritable.setScoped.html deleted file mode 100644 index 4dfea8cd0..000000000 --- a/docs/~/di.ServiceRegistryWritable.setScoped.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.ServiceRegistryWritable.setScoped - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceRegistryWritable.setTransient.html b/docs/~/di.ServiceRegistryWritable.setTransient.html deleted file mode 100644 index 45ed094ea..000000000 --- a/docs/~/di.ServiceRegistryWritable.setTransient.html +++ /dev/null @@ -1,380 +0,0 @@ - - - - di.ServiceRegistryWritable.setTransient - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceResolution.html b/docs/~/di.ServiceResolution.html deleted file mode 100644 index e93efa894..000000000 --- a/docs/~/di.ServiceResolution.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - di.ServiceResolution - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScope.html b/docs/~/di.ServiceScope.html deleted file mode 100644 index 3685a8d91..000000000 --- a/docs/~/di.ServiceScope.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - di.ServiceScope - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScopeQueryable.createScope.html b/docs/~/di.ServiceScopeQueryable.createScope.html deleted file mode 100644 index 3aecfb265..000000000 --- a/docs/~/di.ServiceScopeQueryable.createScope.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - di.ServiceScopeQueryable.createScope - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScopeQueryable.get.html b/docs/~/di.ServiceScopeQueryable.get.html deleted file mode 100644 index aa2c47163..000000000 --- a/docs/~/di.ServiceScopeQueryable.get.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - di.ServiceScopeQueryable.get - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method di.ServiceScopeQueryable.get -
- - - - -
- - - - - - - - - - - -ServiceScopeQueryable.get<V2 = V>(
token: K,
defaultValue?: V2,
): ServiceResolution<V2>
-
-

- - - - - - - - - - -Type Parameters

- -
-
-
-

- - - - - - - - - - -Parameters

- -
-
- -
-
optional
-
- - - - - - - - - - -defaultValue: V2 - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

-
-
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScopeQueryable.getMany.html b/docs/~/di.ServiceScopeQueryable.getMany.html deleted file mode 100644 index 39a6a74bc..000000000 --- a/docs/~/di.ServiceScopeQueryable.getMany.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - di.ServiceScopeQueryable.getMany - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScopeQueryable.html b/docs/~/di.ServiceScopeQueryable.html deleted file mode 100644 index ff8d0b606..000000000 --- a/docs/~/di.ServiceScopeQueryable.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - di.ServiceScopeQueryable - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- interface di.ServiceScopeQueryable -
- - - - -
-

- - - - - - - - - - -Type Parameters

-
- -
-
- - - - - - - - - - -get<V2 = V>(
token: K,
defaultValue?: V2,
): ServiceResolution<V2>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -getMany(...tokens: ReadonlyArray<K>): ReadonlyArray<ServiceResolution<V>> - -
- - - - -
-
- -
-
- - - - - - - - - - -invoke<T extends functions.GenericFunction<ReturnType<T>, any>>(fn: T): ReturnType<T> - -
- - - - -
- -
-
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceScopeQueryable.invoke.html b/docs/~/di.ServiceScopeQueryable.invoke.html deleted file mode 100644 index 62c48485e..000000000 --- a/docs/~/di.ServiceScopeQueryable.invoke.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - di.ServiceScopeQueryable.invoke - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceType.html b/docs/~/di.ServiceType.html deleted file mode 100644 index fd4acff7e..000000000 --- a/docs/~/di.ServiceType.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceType - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypeKey.html b/docs/~/di.ServiceTypeKey.html deleted file mode 100644 index 53b13ec68..000000000 --- a/docs/~/di.ServiceTypeKey.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceTypeKey - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypes.Lazy.html b/docs/~/di.ServiceTypes.Lazy.html deleted file mode 100644 index 90cf72e5b..000000000 --- a/docs/~/di.ServiceTypes.Lazy.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceTypes.Lazy - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypes.Scoped.html b/docs/~/di.ServiceTypes.Scoped.html deleted file mode 100644 index ac5eb5baa..000000000 --- a/docs/~/di.ServiceTypes.Scoped.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceTypes.Scoped - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypes.Singleton.html b/docs/~/di.ServiceTypes.Singleton.html deleted file mode 100644 index 910afd837..000000000 --- a/docs/~/di.ServiceTypes.Singleton.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceTypes.Singleton - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypes.Transient.html b/docs/~/di.ServiceTypes.Transient.html deleted file mode 100644 index 61362b4ff..000000000 --- a/docs/~/di.ServiceTypes.Transient.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceTypes.Transient - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceTypes.html b/docs/~/di.ServiceTypes.html deleted file mode 100644 index 709cac9f5..000000000 --- a/docs/~/di.ServiceTypes.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - di.ServiceTypes - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.ServiceValue.html b/docs/~/di.ServiceValue.html deleted file mode 100644 index 1ea3a02e9..000000000 --- a/docs/~/di.ServiceValue.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.ServiceValue - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.Value.html b/docs/~/di.Value.html deleted file mode 100644 index eb89aa09d..000000000 --- a/docs/~/di.Value.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.Value - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.di.html b/docs/~/di.di.html deleted file mode 100644 index 892a9ddd4..000000000 --- a/docs/~/di.di.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.di - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.html b/docs/~/di.html deleted file mode 100644 index d37d809ce..000000000 --- a/docs/~/di.html +++ /dev/null @@ -1,385 +0,0 @@ - - - - di - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace di -
- - - - -
c
- -
c
- -
-
-
-

- - - - - - - - - - -Interfaces

I
- -
I
-
- di.ServiceScopeQueryable - -
No documentation available
-
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- di.ClassService - -
No documentation available
-
T
-
- di.Factory - -
No documentation available
-
T
-
- di.FunctionService - -
No documentation available
-
T
-
- di.PromisableBuilder - -
No documentation available
-
T
-
- di.ServiceDescriptor - -
No documentation available
-
T
-
- di.ServiceKey - -
No documentation available
-
T
-
- di.ServiceRegistry - -
No documentation available
-
T
-
- di.ServiceResolution - -
No documentation available
-
T
-
- di.ServiceScope - -
No documentation available
-
T
-
- di.ServiceType - -
No documentation available
-
T
-
- di.ServiceTypeKey - -
No documentation available
-
T
-
- di.ServiceValue - -
No documentation available
-
T
-
- di.Value - -
No documentation available
-
-
-
v
-
- di.di - -
No documentation available
-
v
-
- di.inject - -
No documentation available
-
v
-
- di.injectable - -
No documentation available
-
v
-
- di.registry - -
No documentation available
-
v
-
- di.services - -
No documentation available
-
v
-
- di.ServiceTypes - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/di.inject.html b/docs/~/di.inject.html deleted file mode 100644 index e0acfdb22..000000000 --- a/docs/~/di.inject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.inject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.injectable.html b/docs/~/di.injectable.html deleted file mode 100644 index e101f34c5..000000000 --- a/docs/~/di.injectable.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.injectable - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.registry.html b/docs/~/di.registry.html deleted file mode 100644 index e5a4ddbb8..000000000 --- a/docs/~/di.registry.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.registry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/di.services.html b/docs/~/di.services.html deleted file mode 100644 index 23f4cd018..000000000 --- a/docs/~/di.services.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - di.services - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { di } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Dispatcher.prototype.dispatch.html b/docs/~/events.Dispatcher.prototype.dispatch.html deleted file mode 100644 index 295e1332a..000000000 --- a/docs/~/events.Dispatcher.prototype.dispatch.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - events.Dispatcher.prototype.dispatch - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.Dispatcher.prototype.dispatch -
- - - - -
- - - - - - - - - - - -Dispatcher.prototype.dispatch<T>(
type: string,
eventInitDict?: CustomEventInit<T>,
): boolean
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -eventInitDict: CustomEventInit<T> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
boolean - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Dispatcher.prototype.html b/docs/~/events.Dispatcher.prototype.html deleted file mode 100644 index 0aaf80d02..000000000 --- a/docs/~/events.Dispatcher.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/events.Dispatcher.prototype.state.html b/docs/~/events.Dispatcher.prototype.state.html deleted file mode 100644 index 16e851959..000000000 --- a/docs/~/events.Dispatcher.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.Dispatcher.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.DispatcherState.html b/docs/~/events.DispatcherState.html deleted file mode 100644 index 08fe3d6be..000000000 --- a/docs/~/events.DispatcherState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.DispatcherState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventDispatcher.dispatch.html b/docs/~/events.EventDispatcher.dispatch.html deleted file mode 100644 index 1c958a74e..000000000 --- a/docs/~/events.EventDispatcher.dispatch.html +++ /dev/null @@ -1,458 +0,0 @@ - - - - events.EventDispatcher.dispatch - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.EventDispatcher.dispatch -
- - - - -
- - - - - - - - - - - -EventDispatcher.dispatch<T>(
type: string,
eventInitDict?: CustomEventInit<T>,
): boolean
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
-
- -
-
optional
-
- - - - - - - - - - -eventInitDict: CustomEventInit<T> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
boolean - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventDispatcher.html b/docs/~/events.EventDispatcher.html deleted file mode 100644 index eae7dbf4f..000000000 --- a/docs/~/events.EventDispatcher.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - events.EventDispatcher - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventDispatcher.state.html b/docs/~/events.EventDispatcher.state.html deleted file mode 100644 index de9da49ad..000000000 --- a/docs/~/events.EventDispatcher.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.EventDispatcher.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventDispatcherState.html b/docs/~/events.EventDispatcherState.html deleted file mode 100644 index 5f4b140b8..000000000 --- a/docs/~/events.EventDispatcherState.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - events.EventDispatcherState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventDispatcherState.registry.html b/docs/~/events.EventDispatcherState.registry.html deleted file mode 100644 index bea6f6f93..000000000 --- a/docs/~/events.EventDispatcherState.registry.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.EventDispatcherState.registry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistry.html b/docs/~/events.EventRegistry.html deleted file mode 100644 index 0564b9f29..000000000 --- a/docs/~/events.EventRegistry.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.EventRegistry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistryState.html b/docs/~/events.EventRegistryState.html deleted file mode 100644 index 678774aca..000000000 --- a/docs/~/events.EventRegistryState.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - events.EventRegistryState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistryState.target.html b/docs/~/events.EventRegistryState.target.html deleted file mode 100644 index 98b4bb39b..000000000 --- a/docs/~/events.EventRegistryState.target.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.EventRegistryState.target - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistryWritable.add.html b/docs/~/events.EventRegistryWritable.add.html deleted file mode 100644 index 1d995f261..000000000 --- a/docs/~/events.EventRegistryWritable.add.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - events.EventRegistryWritable.add - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.EventRegistryWritable.add -
- - - - -
- - - - - - - - - - - -EventRegistryWritable.add(
type: string,
listener: EventListener,
): void
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
- -
- -
-
-
-

- - - - - - - - - - -Return Type

- -
-
void - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistryWritable.html b/docs/~/events.EventRegistryWritable.html deleted file mode 100644 index 6b46f4065..000000000 --- a/docs/~/events.EventRegistryWritable.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - events.EventRegistryWritable - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.EventRegistryWritable.remove.html b/docs/~/events.EventRegistryWritable.remove.html deleted file mode 100644 index b202ea05b..000000000 --- a/docs/~/events.EventRegistryWritable.remove.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - events.EventRegistryWritable.remove - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.EventRegistryWritable.remove -
- - - - -
- - - - - - - - - - - -EventRegistryWritable.remove(
type: string,
listener: EventListener,
): void
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
- -
- -
-
-
-

- - - - - - - - - - -Return Type

- -
-
void - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { type events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Factory.html b/docs/~/events.Factory.html deleted file mode 100644 index 9e168c748..000000000 --- a/docs/~/events.Factory.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.Factory - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Registry.html b/docs/~/events.Registry.html deleted file mode 100644 index 8daed0102..000000000 --- a/docs/~/events.Registry.html +++ /dev/null @@ -1,510 +0,0 @@ - - - - events.Registry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class events.Registry -
- implements [primitives.EventRegistry]
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -Registry(state?: RegistryState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

-
- -
-
- - - - - - - - - - -add(
type: string,
listener: EventListener,
): this
-
-
- - - - -
-
- -
-
- - - - - - - - - - -build(): primitives.EventDispatcher - -
- - - - -
-
- -
-
- - - - - - - - - - -remove(
type: string,
listener: EventListener,
): this
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Registry.prototype.add.html b/docs/~/events.Registry.prototype.add.html deleted file mode 100644 index b11d3a47c..000000000 --- a/docs/~/events.Registry.prototype.add.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - events.Registry.prototype.add - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.Registry.prototype.add -
- - - - -
- - - - - - - - - - - -Registry.prototype.add(
type: string,
listener: EventListener,
): this
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
- -
- -
-
-
-

- - - - - - - - - - -Return Type

- -
-
this - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Registry.prototype.build.html b/docs/~/events.Registry.prototype.build.html deleted file mode 100644 index 0e46d4cbb..000000000 --- a/docs/~/events.Registry.prototype.build.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - events.Registry.prototype.build - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Registry.prototype.html b/docs/~/events.Registry.prototype.html deleted file mode 100644 index 2545c47ba..000000000 --- a/docs/~/events.Registry.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/events.Registry.prototype.remove.html b/docs/~/events.Registry.prototype.remove.html deleted file mode 100644 index 05ad65de0..000000000 --- a/docs/~/events.Registry.prototype.remove.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - events.Registry.prototype.remove - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method events.Registry.prototype.remove -
- - - - -
- - - - - - - - - - - -Registry.prototype.remove(
type: string,
listener: EventListener,
): this
-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -type: string - -
- - - - -
- -
- -
-
-
-

- - - - - - - - - - -Return Type

- -
-
this - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.Registry.prototype.state.html b/docs/~/events.Registry.prototype.state.html deleted file mode 100644 index 0673b1bc4..000000000 --- a/docs/~/events.Registry.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.Registry.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.RegistryState.html b/docs/~/events.RegistryState.html deleted file mode 100644 index fbe04d29c..000000000 --- a/docs/~/events.RegistryState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.RegistryState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.createDispatcherState.html b/docs/~/events.createDispatcherState.html deleted file mode 100644 index d9e67376e..000000000 --- a/docs/~/events.createDispatcherState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.createDispatcherState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.createRegistryState.html b/docs/~/events.createRegistryState.html deleted file mode 100644 index 9a3af2dcd..000000000 --- a/docs/~/events.createRegistryState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.createRegistryState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.dispatcher.html b/docs/~/events.dispatcher.html deleted file mode 100644 index e8eca54e9..000000000 --- a/docs/~/events.dispatcher.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.dispatcher - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.events.html b/docs/~/events.events.html deleted file mode 100644 index 0d3611f6c..000000000 --- a/docs/~/events.events.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - events.events - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { events } from ".";
-
-
-
-
-
- - diff --git a/docs/~/events.html b/docs/~/events.html deleted file mode 100644 index ed0f92c98..000000000 --- a/docs/~/events.html +++ /dev/null @@ -1,345 +0,0 @@ - - - - events - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace events -
- - - - -
c
-
- events.Dispatcher - -
No documentation available
-
c
-
- events.Registry - -
No documentation available
-
-
-
-

- - - - - - - - - - -Interfaces

I
-
- events.EventRegistryWritable - -
No documentation available
-
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- events.DispatcherState - -
No documentation available
-
T
-
- events.EventDispatcher - -
No documentation available
-
T
-
- events.EventDispatcherState - -
No documentation available
-
T
-
- events.EventRegistry - -
No documentation available
-
T
-
- events.EventRegistryState - -
No documentation available
-
T
-
- events.Factory - -
No documentation available
-
T
-
- events.RegistryState - -
No documentation available
-
-
-
v
-
- events.createDispatcherState - -
No documentation available
-
v
-
- events.createRegistryState - -
No documentation available
-
v
-
- events.dispatcher - -
No documentation available
-
v
-
- events.events - -
No documentation available
-
v
-
- events.registry - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/fp.Callback.html b/docs/~/fp.Callback.html deleted file mode 100644 index 60e7fd6ea..000000000 --- a/docs/~/fp.Callback.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - fp.Callback - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.Decorated.html b/docs/~/fp.Decorated.html deleted file mode 100644 index eacd4810d..000000000 --- a/docs/~/fp.Decorated.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - fp.Decorated - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.LogType.action.html b/docs/~/fp.LogType.action.html deleted file mode 100644 index 67ee2084a..000000000 --- a/docs/~/fp.LogType.action.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.LogType.action - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.LogType.html b/docs/~/fp.LogType.html deleted file mode 100644 index edeafd26f..000000000 --- a/docs/~/fp.LogType.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - fp.LogType - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.LogType.newState.html b/docs/~/fp.LogType.newState.html deleted file mode 100644 index 03ee0bb59..000000000 --- a/docs/~/fp.LogType.newState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.LogType.newState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.LogType.previousState.html b/docs/~/fp.LogType.previousState.html deleted file mode 100644 index 8fd374adb..000000000 --- a/docs/~/fp.LogType.previousState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.LogType.previousState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.LoggerType.html b/docs/~/fp.LoggerType.html deleted file mode 100644 index d06d83c38..000000000 --- a/docs/~/fp.LoggerType.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - fp.LoggerType - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.MutatorType.html b/docs/~/fp.MutatorType.html deleted file mode 100644 index 45611d6ba..000000000 --- a/docs/~/fp.MutatorType.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - fp.MutatorType - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.NextType.html b/docs/~/fp.NextType.html deleted file mode 100644 index f590b5f1f..000000000 --- a/docs/~/fp.NextType.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - fp.NextType - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.Pattern.html b/docs/~/fp.Pattern.html deleted file mode 100644 index a121d9c3a..000000000 --- a/docs/~/fp.Pattern.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - fp.Pattern - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromArrayResult.html b/docs/~/fp.PickFromArrayResult.html deleted file mode 100644 index 8cb024de0..000000000 --- a/docs/~/fp.PickFromArrayResult.html +++ /dev/null @@ -1,377 +0,0 @@ - - - - fp.PickFromArrayResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromArrayResult.items.html b/docs/~/fp.PickFromArrayResult.items.html deleted file mode 100644 index 57985f454..000000000 --- a/docs/~/fp.PickFromArrayResult.items.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.PickFromArrayResult.items - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromArrayResult.rest.html b/docs/~/fp.PickFromArrayResult.rest.html deleted file mode 100644 index 687b9a015..000000000 --- a/docs/~/fp.PickFromArrayResult.rest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.PickFromArrayResult.rest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromObjectResult.html b/docs/~/fp.PickFromObjectResult.html deleted file mode 100644 index 61d830bc7..000000000 --- a/docs/~/fp.PickFromObjectResult.html +++ /dev/null @@ -1,377 +0,0 @@ - - - - fp.PickFromObjectResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromObjectResult.items.html b/docs/~/fp.PickFromObjectResult.items.html deleted file mode 100644 index 9c1a1f4d5..000000000 --- a/docs/~/fp.PickFromObjectResult.items.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.PickFromObjectResult.items - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.PickFromObjectResult.rest.html b/docs/~/fp.PickFromObjectResult.rest.html deleted file mode 100644 index bb295589d..000000000 --- a/docs/~/fp.PickFromObjectResult.rest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.PickFromObjectResult.rest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitArrayResult.html b/docs/~/fp.SplitArrayResult.html deleted file mode 100644 index 922fb66f4..000000000 --- a/docs/~/fp.SplitArrayResult.html +++ /dev/null @@ -1,377 +0,0 @@ - - - - fp.SplitArrayResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitArrayResult.items.html b/docs/~/fp.SplitArrayResult.items.html deleted file mode 100644 index 4911bdc3d..000000000 --- a/docs/~/fp.SplitArrayResult.items.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.SplitArrayResult.items - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitArrayResult.rest.html b/docs/~/fp.SplitArrayResult.rest.html deleted file mode 100644 index a95c1c060..000000000 --- a/docs/~/fp.SplitArrayResult.rest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.SplitArrayResult.rest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitObjectResult.html b/docs/~/fp.SplitObjectResult.html deleted file mode 100644 index 991280d24..000000000 --- a/docs/~/fp.SplitObjectResult.html +++ /dev/null @@ -1,377 +0,0 @@ - - - - fp.SplitObjectResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitObjectResult.items.html b/docs/~/fp.SplitObjectResult.items.html deleted file mode 100644 index 92077ba01..000000000 --- a/docs/~/fp.SplitObjectResult.items.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.SplitObjectResult.items - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.SplitObjectResult.rest.html b/docs/~/fp.SplitObjectResult.rest.html deleted file mode 100644 index 91048cdd5..000000000 --- a/docs/~/fp.SplitObjectResult.rest.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.SplitObjectResult.rest - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.appendToArray.html b/docs/~/fp.appendToArray.html deleted file mode 100644 index ecfba9a09..000000000 --- a/docs/~/fp.appendToArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.appendToArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.appendToObject.html b/docs/~/fp.appendToObject.html deleted file mode 100644 index adc1993b0..000000000 --- a/docs/~/fp.appendToObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.appendToObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.appendToObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
...values: ReadonlyArray<Record<
string
| number
| symbol
, T>>
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.associateArray.html b/docs/~/fp.associateArray.html deleted file mode 100644 index 5b527d18d..000000000 --- a/docs/~/fp.associateArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.associateArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.associateArray -
- - - - -
- -
-
<T>(
instance: Iterable<T>,
predicate: (
value: T,
index: number,
instance: Record<
string
| number
| symbol
, T>
,
) =>
string
| number
| symbol
| undefined
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.associateObject.html b/docs/~/fp.associateObject.html deleted file mode 100644 index 95bcbd84f..000000000 --- a/docs/~/fp.associateObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.associateObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.associateObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
predicate: (
value: T,
key:
string
| number
| symbol
,
instance: Record<
string
| number
| symbol
, T>
,
) =>
string
| number
| symbol
| undefined
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.compose.html b/docs/~/fp.compose.html deleted file mode 100644 index 6d77f49ce..000000000 --- a/docs/~/fp.compose.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.compose - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.curry.html b/docs/~/fp.curry.html deleted file mode 100644 index a476b8d89..000000000 --- a/docs/~/fp.curry.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.curry - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.curry -
- - - - -
- -
-
<
TF extends functions.GenericFunction<any, any>,
TP extends Partial<Parameters<TF>>,
>
(
func: TF,
...argsOnLeft: TP,
) => (...argsOnRight: utilities.RemainingParameters<Parameters<TF>, TP>) => ReturnType<TF>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.curryRight.html b/docs/~/fp.curryRight.html deleted file mode 100644 index 3a429c6d7..000000000 --- a/docs/~/fp.curryRight.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.curryRight - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.curryRight -
- - - - -
- -
-
<
TF extends functions.GenericFunction<any, any>,
TP extends Partial<Parameters<TF>>,
>
(
func: TF,
...argsOnRight: TP,
) => (...argsOnLeft: utilities.RemainingParametersRight<Parameters<TF>, TP>) => ReturnType<TF>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.decorate.html b/docs/~/fp.decorate.html deleted file mode 100644 index 1ddcbf722..000000000 --- a/docs/~/fp.decorate.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.decorate - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.deepCopy.html b/docs/~/fp.deepCopy.html deleted file mode 100644 index f8e0b383c..000000000 --- a/docs/~/fp.deepCopy.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.deepCopy - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.deepCopy2.html b/docs/~/fp.deepCopy2.html deleted file mode 100644 index 6123ad6e6..000000000 --- a/docs/~/fp.deepCopy2.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.deepCopy2 - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.deepMerge.html b/docs/~/fp.deepMerge.html deleted file mode 100644 index 79cf75419..000000000 --- a/docs/~/fp.deepMerge.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.deepMerge - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.deepMerge -
- - - - -
- -
-
<
T1 extends ObjectType,
T2 extends ObjectType,
TR extends T1 & T2,
>
(
instance: T1,
other: T2,
) => TR
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.dispatcher.html b/docs/~/fp.dispatcher.html deleted file mode 100644 index e2fb7113c..000000000 --- a/docs/~/fp.dispatcher.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.dispatcher - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.distinctArray.html b/docs/~/fp.distinctArray.html deleted file mode 100644 index 054c568ac..000000000 --- a/docs/~/fp.distinctArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.distinctArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.distinctArray -
- - - - -
- -
-
<T>(
instance: Iterable<T>,
predicate: (
value: T,
index: number,
instance: Iterable<T>,
) => unknown
,
) => Array<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.distinctObject.html b/docs/~/fp.distinctObject.html deleted file mode 100644 index 2eec078e5..000000000 --- a/docs/~/fp.distinctObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.distinctObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.distinctObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
predicate: (
value: T,
key:
string
| number
| symbol
,
instance: Record<
string
| number
| symbol
, T>
,
) => unknown
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.dropFromArray.html b/docs/~/fp.dropFromArray.html deleted file mode 100644 index d9792a68c..000000000 --- a/docs/~/fp.dropFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.dropFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.dropFromObject.html b/docs/~/fp.dropFromObject.html deleted file mode 100644 index 72bce2bef..000000000 --- a/docs/~/fp.dropFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.dropFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.dropFromObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
n: number,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.filterArray.html b/docs/~/fp.filterArray.html deleted file mode 100644 index 8feacb7ea..000000000 --- a/docs/~/fp.filterArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.filterArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.filterArray -
- - - - -
- -
-
<T>(
instance: Iterable<T>,
predicate: (
value: T,
index: number,
instance: Iterable<T>,
) => boolean
,
) => Array<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.filterObject.html b/docs/~/fp.filterObject.html deleted file mode 100644 index b88559e07..000000000 --- a/docs/~/fp.filterObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.filterObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.filterObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
predicate: (
value: T,
key:
string
| number
| symbol
,
instance: Record<
string
| number
| symbol
, T>
,
) => boolean
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.html b/docs/~/fp.html deleted file mode 100644 index d703f2975..000000000 --- a/docs/~/fp.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - fp - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace fp -
- - - - -
-

- - - - - - - - - - -Type Aliases

T
-
- fp.Callback - -
No documentation available
-
T
-
- fp.Decorated - -
No documentation available
-
T
-
- fp.LoggerType - -
No documentation available
-
T
-
- fp.LogType - -
No documentation available
-
T
-
- fp.MutatorType - -
No documentation available
-
T
-
- fp.NextType - -
No documentation available
-
T
-
- fp.Pattern - -
No documentation available
-
T
-
- fp.PickFromArrayResult - -
No documentation available
-
T
-
- fp.PickFromObjectResult - -
No documentation available
-
T
-
- fp.SplitArrayResult - -
No documentation available
-
T
-
- fp.SplitObjectResult - -
No documentation available
-
-
-
v
-
- fp.appendToArray - -
No documentation available
-
v
-
- fp.appendToObject - -
No documentation available
-
v
-
- fp.associateArray - -
No documentation available
-
v
-
- fp.associateObject - -
No documentation available
-
v
-
- fp.compose - -
No documentation available
-
v
-
- fp.curry - -
No documentation available
-
v
-
- fp.curryRight - -
No documentation available
-
v
-
- fp.decorate - -
No documentation available
-
v
-
- fp.deepCopy - -
No documentation available
-
v
-
- fp.deepCopy2 - -
No documentation available
-
v
-
- fp.deepMerge - -
No documentation available
-
v
-
- fp.dispatcher - -
No documentation available
-
v
-
- fp.distinctArray - -
No documentation available
-
v
-
- fp.distinctObject - -
No documentation available
-
v
-
- fp.dropFromArray - -
No documentation available
-
v
-
- fp.dropFromObject - -
No documentation available
-
v
-
- fp.filterArray - -
No documentation available
-
v
-
- fp.filterObject - -
No documentation available
-
v
-
- fp.iterate - -
No documentation available
-
v
-
- fp.mapArray - -
No documentation available
-
v
-
- fp.mapObject - -
No documentation available
-
v
-
- fp.match - -
No documentation available
-
v
-
- fp.mergeArrays - -
No documentation available
-
v
-
- fp.mergeObjects - -
No documentation available
-
v
-
- fp.mutate - -
No documentation available
-
v
-
- fp.pickFromArray - -
No documentation available
-
v
-
- fp.pickFromObject - -
No documentation available
-
v
-
- fp.pipe - -
No documentation available
-
v
-
- fp.prependToArray - -
No documentation available
-
v
-
- fp.prependToObject - -
No documentation available
-
v
-
- fp.removeFirstMatchFromArray - -
No documentation available
-
v
-
- fp.removeFirstMatchFromObject - -
No documentation available
-
v
-
- fp.removeIndexFromArray - -
No documentation available
-
v
-
- fp.removeKeyFromObject - -
No documentation available
-
v
-
- fp.removeValueFromArray - -
No documentation available
-
v
-
- fp.removeValueFromObject - -
No documentation available
-
v
-
- fp.reverseArray - -
No documentation available
-
v
-
- fp.reverseObject - -
No documentation available
-
v
-
- fp.splitArray - -
No documentation available
-
v
-
- fp.splitObject - -

The splitObject function splits an object into two parts based on a given number, with the first -part containing the specified number of items and the second part containing the remaining items.

-
-
v
-
- fp.takeFromArray - -
No documentation available
-
v
-
- fp.takeFromObject - -
No documentation available
-
v
-
- fp.wth - -
No documentation available
-
v
-
- fp.wthout - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/fp.iterate.html b/docs/~/fp.iterate.html deleted file mode 100644 index ecebcfdf4..000000000 --- a/docs/~/fp.iterate.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.iterate - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.iterate -
- - - - -
- -
-
(
iterable: promises.Promisable<Iterable<any>>,
func: (...args: functions.ArgList<any>) => promises.Promisable<any>,
) => Promise<void>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.mapArray.html b/docs/~/fp.mapArray.html deleted file mode 100644 index 01afa7486..000000000 --- a/docs/~/fp.mapArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.mapArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.mapArray -
- - - - -
- -
-
<
T1,
T2,
>
(
instance: Iterable<T1>,
predicate: (
value: T1,
index: number,
instance: Iterable<T1>,
) => T2
,
) => Array<T2>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.mapObject.html b/docs/~/fp.mapObject.html deleted file mode 100644 index b21a1abe8..000000000 --- a/docs/~/fp.mapObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.mapObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.mapObject -
- - - - -
- -
-
<
T1,
T2,
>
(
instance: Record<
string
| number
| symbol
, T1>
,
predicate: (
value: T1,
key:
string
| number
| symbol
,
instance: Record<
string
| number
| symbol
, T1>
,
) => Record<
string
| number
| symbol
, T2> | null
,
) => Record<
string
| number
| symbol
, T2>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.match.html b/docs/~/fp.match.html deleted file mode 100644 index db503ec6b..000000000 --- a/docs/~/fp.match.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.match - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.mergeArrays.html b/docs/~/fp.mergeArrays.html deleted file mode 100644 index 83f8162b0..000000000 --- a/docs/~/fp.mergeArrays.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.mergeArrays - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.mergeObjects.html b/docs/~/fp.mergeObjects.html deleted file mode 100644 index cafd2a8f2..000000000 --- a/docs/~/fp.mergeObjects.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.mergeObjects - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.mergeObjects -
- - - - -
- -
-
<T>(...instances: ReadonlyArray<Record<
string
| number
| symbol
, T>>
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.mutate.html b/docs/~/fp.mutate.html deleted file mode 100644 index df763b9f4..000000000 --- a/docs/~/fp.mutate.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.mutate - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.pickFromArray.html b/docs/~/fp.pickFromArray.html deleted file mode 100644 index 4a230dbf5..000000000 --- a/docs/~/fp.pickFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.pickFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.pickFromObject.html b/docs/~/fp.pickFromObject.html deleted file mode 100644 index d9b827176..000000000 --- a/docs/~/fp.pickFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.pickFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.pipe.html b/docs/~/fp.pipe.html deleted file mode 100644 index e04755808..000000000 --- a/docs/~/fp.pipe.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.pipe - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.pipe -
- - - - -
- -
-
<TF extends functions.ArgList<functions.GenericFunction<any, any>>>(...funcs: TF) => utilities.First<TF> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.prependToArray.html b/docs/~/fp.prependToArray.html deleted file mode 100644 index e0a96c6cb..000000000 --- a/docs/~/fp.prependToArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.prependToArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.prependToObject.html b/docs/~/fp.prependToObject.html deleted file mode 100644 index 80190fc93..000000000 --- a/docs/~/fp.prependToObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.prependToObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.prependToObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
...values: ReadonlyArray<Record<
string
| number
| symbol
, T>>
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeFirstMatchFromArray.html b/docs/~/fp.removeFirstMatchFromArray.html deleted file mode 100644 index 8e4eccb38..000000000 --- a/docs/~/fp.removeFirstMatchFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeFirstMatchFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.removeFirstMatchFromArray -
- - - - -
- -
-
<T>(
instance: Iterable<T>,
predicate: (
value: T,
index: number,
instance: Iterable<T>,
) => boolean
,
) => Array<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeFirstMatchFromObject.html b/docs/~/fp.removeFirstMatchFromObject.html deleted file mode 100644 index 358c26e1e..000000000 --- a/docs/~/fp.removeFirstMatchFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeFirstMatchFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.removeFirstMatchFromObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
predicate: (
value: T,
key:
string
| number
| symbol
,
instance: Record<
string
| number
| symbol
, T>
,
) => boolean
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeIndexFromArray.html b/docs/~/fp.removeIndexFromArray.html deleted file mode 100644 index cab28434b..000000000 --- a/docs/~/fp.removeIndexFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeIndexFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.removeIndexFromArray -
- - - - -
- -
-
<T>(
instance: Iterable<T>,
...indexes: ReadonlyArray<number>,
) => Array<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeKeyFromObject.html b/docs/~/fp.removeKeyFromObject.html deleted file mode 100644 index 6ef594374..000000000 --- a/docs/~/fp.removeKeyFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeKeyFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.removeKeyFromObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
...keys: ReadonlyArray<
string
| number
| symbol
>
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeValueFromArray.html b/docs/~/fp.removeValueFromArray.html deleted file mode 100644 index a94eb2ce5..000000000 --- a/docs/~/fp.removeValueFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeValueFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.removeValueFromObject.html b/docs/~/fp.removeValueFromObject.html deleted file mode 100644 index d3c9a8755..000000000 --- a/docs/~/fp.removeValueFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.removeValueFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.removeValueFromObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
...values: ReadonlyArray<T>,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.reverseArray.html b/docs/~/fp.reverseArray.html deleted file mode 100644 index af311ac45..000000000 --- a/docs/~/fp.reverseArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.reverseArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.reverseObject.html b/docs/~/fp.reverseObject.html deleted file mode 100644 index 9eaea579d..000000000 --- a/docs/~/fp.reverseObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.reverseObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.reverseObject -
- - - - -
- -
-
<T>(instance: Record<
string
| number
| symbol
, T>
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.splitArray.html b/docs/~/fp.splitArray.html deleted file mode 100644 index 35b14b138..000000000 --- a/docs/~/fp.splitArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.splitArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.splitObject.html b/docs/~/fp.splitObject.html deleted file mode 100644 index 137fcecf3..000000000 --- a/docs/~/fp.splitObject.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - fp.splitObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.splitObject -
- - - - -

The splitObject function splits an object into two parts based on a given number, with the first -part containing the specified number of items and the second part containing the remaining items.

-
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
n: number,
) => SplitObjectResult<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.takeFromArray.html b/docs/~/fp.takeFromArray.html deleted file mode 100644 index 5b23dabeb..000000000 --- a/docs/~/fp.takeFromArray.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.takeFromArray - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.takeFromObject.html b/docs/~/fp.takeFromObject.html deleted file mode 100644 index 4df4bb3c2..000000000 --- a/docs/~/fp.takeFromObject.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.takeFromObject - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.takeFromObject -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
n: number,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.wth.html b/docs/~/fp.wth.html deleted file mode 100644 index 96d87da18..000000000 --- a/docs/~/fp.wth.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.wth - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable fp.wth -
- - - - -
- -
-
<T>(
instance: Record<
string
| number
| symbol
, T>
,
mapping: Record<
string
| number
| symbol
, T>
,
) => Record<
string
| number
| symbol
, T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/fp.wthout.html b/docs/~/fp.wthout.html deleted file mode 100644 index 3a74b77b6..000000000 --- a/docs/~/fp.wthout.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - fp.wthout - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { fp } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.AcceptableResult.html b/docs/~/functions.AcceptableResult.html deleted file mode 100644 index 61e94e430..000000000 --- a/docs/~/functions.AcceptableResult.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.AcceptableResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Context.html b/docs/~/functions.Context.html deleted file mode 100644 index f2e3bb2ab..000000000 --- a/docs/~/functions.Context.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - functions.Context - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Context.next.html b/docs/~/functions.Context.next.html deleted file mode 100644 index abcde652f..000000000 --- a/docs/~/functions.Context.next.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Context.next - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Context.state.html b/docs/~/functions.Context.state.html deleted file mode 100644 index 3fed9d05e..000000000 --- a/docs/~/functions.Context.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Context.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.ExtraData.html b/docs/~/functions.ExtraData.html deleted file mode 100644 index 4aef188c9..000000000 --- a/docs/~/functions.ExtraData.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.ExtraData - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Fail.html b/docs/~/functions.Fail.html deleted file mode 100644 index 867b9a1ac..000000000 --- a/docs/~/functions.Fail.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Fail - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.FnResult.html b/docs/~/functions.FnResult.html deleted file mode 100644 index 99f184dd2..000000000 --- a/docs/~/functions.FnResult.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - functions.FnResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Ok.html b/docs/~/functions.Ok.html deleted file mode 100644 index ae6ffb73d..000000000 --- a/docs/~/functions.Ok.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Ok - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Pipeline.html b/docs/~/functions.Pipeline.html deleted file mode 100644 index 217a20735..000000000 --- a/docs/~/functions.Pipeline.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - functions.Pipeline - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- type alias functions.Pipeline -
- - - - -
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Properties

- -
-
- - - - - - - - - - -use: (...fns: ReadonlyArray<Fn<TR, TS>>) => Pipeline<TR, TS> - -
- - - - -
-
- -
-
- -
-
- - - - - - - - - - -iterate: (...args: functions.ArgList<any>) => AsyncGenerator<TR> - -
- - - - -
-
- -
-
- - - - - - - - - - -run: (...args: functions.ArgList<any>) => Promise<Array<TR>> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Pipeline.iterate.html b/docs/~/functions.Pipeline.iterate.html deleted file mode 100644 index 3a9c11b8b..000000000 --- a/docs/~/functions.Pipeline.iterate.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Pipeline.iterate - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Pipeline.run.html b/docs/~/functions.Pipeline.run.html deleted file mode 100644 index ce84284b3..000000000 --- a/docs/~/functions.Pipeline.run.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Pipeline.run - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Pipeline.set.html b/docs/~/functions.Pipeline.set.html deleted file mode 100644 index 22402958a..000000000 --- a/docs/~/functions.Pipeline.set.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Pipeline.set - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Pipeline.use.html b/docs/~/functions.Pipeline.use.html deleted file mode 100644 index 929389166..000000000 --- a/docs/~/functions.Pipeline.use.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Pipeline.use - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Result.error.html b/docs/~/functions.Result.error.html deleted file mode 100644 index d0659f078..000000000 --- a/docs/~/functions.Result.error.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Result.error - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Result.extraData.html b/docs/~/functions.Result.extraData.html deleted file mode 100644 index 1c50b0948..000000000 --- a/docs/~/functions.Result.extraData.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Result.extraData - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Result.html b/docs/~/functions.Result.html deleted file mode 100644 index 4860772a7..000000000 --- a/docs/~/functions.Result.html +++ /dev/null @@ -1,433 +0,0 @@ - - - - functions.Result - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.Result.payload.html b/docs/~/functions.Result.payload.html deleted file mode 100644 index 85c0ba178..000000000 --- a/docs/~/functions.Result.payload.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.Result.payload - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.State.html b/docs/~/functions.State.html deleted file mode 100644 index d057fc65b..000000000 --- a/docs/~/functions.State.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - functions.State - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.fn.html b/docs/~/functions.fn.html deleted file mode 100644 index 9e89c9147..000000000 --- a/docs/~/functions.fn.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - functions.Fn - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { functions } from ".";
-
-
-
-
-
- - diff --git a/docs/~/functions.html b/docs/~/functions.html deleted file mode 100644 index 359c208d6..000000000 --- a/docs/~/functions.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - functions - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace functions -
- - - - -
-

- - - - - - - - - - -Type Aliases

T
-
- functions.AcceptableResult - -
No documentation available
-
T
-
- functions.Context - -
No documentation available
-
T
-
- functions.ExtraData - -
No documentation available
-
T
-
- functions.Fn - -
No documentation available
-
T
-
- functions.FnResult - -
No documentation available
-
T
-
- functions.Pipeline - -
No documentation available
-
T
-
- functions.Result - -
No documentation available
-
T
-
- functions.State - -
No documentation available
-
-
-
v
-
- functions.Fail - -
No documentation available
-
v
-
- functions.fn - -
No documentation available
-
v
-
- functions.Ok - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/logging.DEFAULT_LEVEL.html b/docs/~/logging.DEFAULT_LEVEL.html deleted file mode 100644 index 4d43cd992..000000000 --- a/docs/~/logging.DEFAULT_LEVEL.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - logging.DEFAULT_LEVEL - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- variable logging.DEFAULT_LEVEL -
- - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.FormatterFn.html b/docs/~/logging.FormatterFn.html deleted file mode 100644 index 412777f04..000000000 --- a/docs/~/logging.FormatterFn.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.FormatterFn - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.args.html b/docs/~/logging.LogRecord.args.html deleted file mode 100644 index fea673ecf..000000000 --- a/docs/~/logging.LogRecord.args.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LogRecord.args - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.datetime.html b/docs/~/logging.LogRecord.datetime.html deleted file mode 100644 index 923be9009..000000000 --- a/docs/~/logging.LogRecord.datetime.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LogRecord.datetime - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.html b/docs/~/logging.LogRecord.html deleted file mode 100644 index b3c680493..000000000 --- a/docs/~/logging.LogRecord.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - logging.LogRecord - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.loggerName.html b/docs/~/logging.LogRecord.loggerName.html deleted file mode 100644 index 714a67a43..000000000 --- a/docs/~/logging.LogRecord.loggerName.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LogRecord.loggerName - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.message.html b/docs/~/logging.LogRecord.message.html deleted file mode 100644 index 2b9ebcf37..000000000 --- a/docs/~/logging.LogRecord.message.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LogRecord.message - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LogRecord.severity.html b/docs/~/logging.LogRecord.severity.html deleted file mode 100644 index 6f1982bcf..000000000 --- a/docs/~/logging.LogRecord.severity.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LogRecord.severity - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.Logger.html b/docs/~/logging.Logger.html deleted file mode 100644 index 6983a88ab..000000000 --- a/docs/~/logging.Logger.html +++ /dev/null @@ -1,993 +0,0 @@ - - - - logging.Logger - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class logging.Logger -
- implements [logging.Logger]
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -Logger(state: LoggerState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

-
- -
-
- - - - - - - - - - -asString(
data: unknown,
isProperty?: boolean,
): string
-
-
- - - - -
-
- -
-
- - - - - - - - - - -critical<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -critical<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -debug<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -debug<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -error<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -error<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -info<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -info<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -log<T>(
severity: logging.Severity,
message: (T extends functions.GenericFunction<any, any> ? never : T) | (() => T),
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -

If the level of the logger is greater than the level to log, then nothing -is logged, otherwise a log record is passed to each log handler. message data -passed in is returned. If a function is passed in, it is only evaluated -if the message will be logged and the return value will be the result of the -function, not the function itself, unless the function isn't called, in which -case undefined is returned. All types are coerced to strings for logging.

-
-
- -
-
- - - - - - - - - - -warn<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-
- - - - -
-
- -
-
- - - - - - - - - - -warn<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-
- - - - -
-
-
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.Logger.prototype.asString.html b/docs/~/logging.Logger.prototype.asString.html deleted file mode 100644 index b2088a677..000000000 --- a/docs/~/logging.Logger.prototype.asString.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - logging.Logger.prototype.asString - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.Logger.prototype.critical.html b/docs/~/logging.Logger.prototype.critical.html deleted file mode 100644 index b7b6462fc..000000000 --- a/docs/~/logging.Logger.prototype.critical.html +++ /dev/null @@ -1,744 +0,0 @@ - - - - logging.Logger.prototype.critical - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.critical -
- - - - -
- - - - - - - - - - - -Logger.prototype.critical<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: () => T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
- - - - - - - - - - - -Logger.prototype.critical<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: T extends functions.GenericFunction<any, any> ? never : T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
- -
-
-
- - diff --git a/docs/~/logging.Logger.prototype.debug.html b/docs/~/logging.Logger.prototype.debug.html deleted file mode 100644 index ef9ed56fe..000000000 --- a/docs/~/logging.Logger.prototype.debug.html +++ /dev/null @@ -1,744 +0,0 @@ - - - - logging.Logger.prototype.debug - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.debug -
- - - - -
- - - - - - - - - - - -Logger.prototype.debug<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: () => T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
- - - - - - - - - - - -Logger.prototype.debug<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: T extends functions.GenericFunction<any, any> ? never : T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
- -
-
-
- - diff --git a/docs/~/logging.Logger.prototype.error.html b/docs/~/logging.Logger.prototype.error.html deleted file mode 100644 index d7f6e2d57..000000000 --- a/docs/~/logging.Logger.prototype.error.html +++ /dev/null @@ -1,744 +0,0 @@ - - - - logging.Logger.prototype.error - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.error -
- - - - -
- - - - - - - - - - - -Logger.prototype.error<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: () => T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
- - - - - - - - - - - -Logger.prototype.error<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: T extends functions.GenericFunction<any, any> ? never : T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
- -
-
-
- - diff --git a/docs/~/logging.Logger.prototype.html b/docs/~/logging.Logger.prototype.html deleted file mode 100644 index e76b15ead..000000000 --- a/docs/~/logging.Logger.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/logging.Logger.prototype.info.html b/docs/~/logging.Logger.prototype.info.html deleted file mode 100644 index f59e60b27..000000000 --- a/docs/~/logging.Logger.prototype.info.html +++ /dev/null @@ -1,744 +0,0 @@ - - - - logging.Logger.prototype.info - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.info -
- - - - -
- - - - - - - - - - - -Logger.prototype.info<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: () => T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
- - - - - - - - - - - -Logger.prototype.info<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: T extends functions.GenericFunction<any, any> ? never : T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
- -
-
-
- - diff --git a/docs/~/logging.Logger.prototype.log.html b/docs/~/logging.Logger.prototype.log.html deleted file mode 100644 index 521b6ee49..000000000 --- a/docs/~/logging.Logger.prototype.log.html +++ /dev/null @@ -1,516 +0,0 @@ - - - - logging.Logger.prototype.log - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.log -
- - - - -
- - - - - - - - - - - -Logger.prototype.log<T>(
severity: logging.Severity,
message: (T extends functions.GenericFunction<any, any> ? never : T) | (() => T),
...args: functions.ArgList<any>,
): Promise<T | undefined>
-

If the level of the logger is greater than the level to log, then nothing -is logged, otherwise a log record is passed to each log handler. message data -passed in is returned. If a function is passed in, it is only evaluated -if the message will be logged and the return value will be the result of the -function, not the function itself, unless the function isn't called, in which -case undefined is returned. All types are coerced to strings for logging.

-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -severity: logging.Severity - -
- - - - -
-
- -
-
- - - - - - - - - - -message: (T extends functions.GenericFunction<any, any> ? never : T) | (() => T) - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.Logger.prototype.state.html b/docs/~/logging.Logger.prototype.state.html deleted file mode 100644 index 63dac4e8f..000000000 --- a/docs/~/logging.Logger.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.Logger.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.Logger.prototype.warn.html b/docs/~/logging.Logger.prototype.warn.html deleted file mode 100644 index afc32a71b..000000000 --- a/docs/~/logging.Logger.prototype.warn.html +++ /dev/null @@ -1,744 +0,0 @@ - - - - logging.Logger.prototype.warn - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- method logging.Logger.prototype.warn -
- - - - -
- - - - - - - - - - - -Logger.prototype.warn<T>(
message: () => T,
...args: functions.ArgList<any>,
): Promise<T | undefined>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: () => T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T | undefined> - -
- - - - -
-
-
-
- - - - - - - - - - - -Logger.prototype.warn<T>(
message: T extends functions.GenericFunction<any, any> ? never : T,
...args: functions.ArgList<any>,
): Promise<T>
-
-

- - - - - - - - - - -Type Parameters

-
-

- - - - - - - - - - -Parameters

- -
-
- - - - - - - - - - -message: T extends functions.GenericFunction<any, any> ? never : T - -
- - - - -
-
- -
-
- - - - - - - - - - -...args: functions.ArgList<any> - -
- - - - -
-
-
-

- - - - - - - - - - -Return Type

- -
-
Promise<T> - -
- - - - -
-
-
-
-
-
-
- -
-
-
- - diff --git a/docs/~/logging.LoggerState.encoder.html b/docs/~/logging.LoggerState.encoder.html deleted file mode 100644 index 9a7a06214..000000000 --- a/docs/~/logging.LoggerState.encoder.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LoggerState.encoder - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LoggerState.formatter.html b/docs/~/logging.LoggerState.formatter.html deleted file mode 100644 index 444707b33..000000000 --- a/docs/~/logging.LoggerState.formatter.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LoggerState.formatter - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LoggerState.html b/docs/~/logging.LoggerState.html deleted file mode 100644 index 40a2c9a47..000000000 --- a/docs/~/logging.LoggerState.html +++ /dev/null @@ -1,462 +0,0 @@ - - - - logging.LoggerState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LoggerState.loggerName.html b/docs/~/logging.LoggerState.loggerName.html deleted file mode 100644 index 0545be3cf..000000000 --- a/docs/~/logging.LoggerState.loggerName.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LoggerState.loggerName - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LoggerState.loglevel.html b/docs/~/logging.LoggerState.loglevel.html deleted file mode 100644 index bf7de0e69..000000000 --- a/docs/~/logging.LoggerState.loglevel.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LoggerState.loglevel - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.LoggerState.targetStream.html b/docs/~/logging.LoggerState.targetStream.html deleted file mode 100644 index 472a06a6a..000000000 --- a/docs/~/logging.LoggerState.targetStream.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.LoggerState.targetStream - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.createLoggerState.html b/docs/~/logging.createLoggerState.html deleted file mode 100644 index 834d07e84..000000000 --- a/docs/~/logging.createLoggerState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.createLoggerState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.current.html b/docs/~/logging.current.html deleted file mode 100644 index 166aeabdd..000000000 --- a/docs/~/logging.current.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.current - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/logging.html b/docs/~/logging.html deleted file mode 100644 index 3109f45ce..000000000 --- a/docs/~/logging.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - logging - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace logging -
- - - - -
c
- -
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- logging.FormatterFn - -
No documentation available
-
T
- -
-
-
v
-
- logging.createLoggerState - -
No documentation available
-
v
-
- logging.current - -
No documentation available
-
v
-
- logging.DEFAULT_LEVEL - -
No documentation available
-
v
-
- logging.jsonFormatter - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/logging.jsonFormatter.html b/docs/~/logging.jsonFormatter.html deleted file mode 100644 index 93019be13..000000000 --- a/docs/~/logging.jsonFormatter.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - logging.jsonFormatter - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { logging } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.ASTNode.html b/docs/~/parsing.ASTNode.html deleted file mode 100644 index 2df7b82e3..000000000 --- a/docs/~/parsing.ASTNode.html +++ /dev/null @@ -1,294 +0,0 @@ - - - - parsing.ASTNode - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.ASTNode.kind.html b/docs/~/parsing.ASTNode.kind.html deleted file mode 100644 index 34e759851..000000000 --- a/docs/~/parsing.ASTNode.kind.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.ASTNode.kind - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Parser.html b/docs/~/parsing.Parser.html deleted file mode 100644 index 5024a2f7e..000000000 --- a/docs/~/parsing.Parser.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.Parser - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.PatternFunction.html b/docs/~/parsing.PatternFunction.html deleted file mode 100644 index e767cba21..000000000 --- a/docs/~/parsing.PatternFunction.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.PatternFunction - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.PatternFunctionResult.html b/docs/~/parsing.PatternFunctionResult.html deleted file mode 100644 index d9f63b875..000000000 --- a/docs/~/parsing.PatternFunctionResult.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.PatternFunctionResult - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Token.kind.html b/docs/~/parsing.Token.kind.html deleted file mode 100644 index e68861c5e..000000000 --- a/docs/~/parsing.Token.kind.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.Token.kind - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Token.value.html b/docs/~/parsing.Token.value.html deleted file mode 100644 index a143fd500..000000000 --- a/docs/~/parsing.Token.value.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.Token.value - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenDefinitions.T_END.html b/docs/~/parsing.TokenDefinitions.T_END.html deleted file mode 100644 index 4c3a615e4..000000000 --- a/docs/~/parsing.TokenDefinitions.T_END.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.TokenDefinitions.T_END - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenDefinitions.T_UNKNOWN.html b/docs/~/parsing.TokenDefinitions.T_UNKNOWN.html deleted file mode 100644 index 816918b89..000000000 --- a/docs/~/parsing.TokenDefinitions.T_UNKNOWN.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.TokenDefinitions.T_UNKNOWN - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenDefinitions.html b/docs/~/parsing.TokenDefinitions.html deleted file mode 100644 index 5e2bd2ea7..000000000 --- a/docs/~/parsing.TokenDefinitions.html +++ /dev/null @@ -1,347 +0,0 @@ - - - - parsing.TokenDefinitions - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.html b/docs/~/parsing.Tokenizer.html deleted file mode 100644 index 396c17eaf..000000000 --- a/docs/~/parsing.Tokenizer.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - parsing.Tokenizer - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- class parsing.Tokenizer -
- - - - -
-

- - - - - - - - - - -Constructors

- -
-
new
-
- - - - - - - - - - -Tokenizer(state: TokenizerState) - -
- - - - -
-
-
-

- - - - - - - - - - -Properties

-
-
- -
-
- - - - - - - - - - -_tokenizeChunk(input: string | null): Generator<Token> - -
- - - - -
-
- -
-
- - - - - - - - - - -tokenize(input: ReadableStream): AsyncGenerator<Token> - -
- - - - -
-
- -
-
- - - - - - - - - - -tokenizeFromString(input: string): Generator<Token> - -
- - - - -
-
-
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.prototype._reset.html b/docs/~/parsing.Tokenizer.prototype._reset.html deleted file mode 100644 index 52e7ef7ed..000000000 --- a/docs/~/parsing.Tokenizer.prototype._reset.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - parsing.Tokenizer.prototype._reset - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.prototype._tokenizeChunk.html b/docs/~/parsing.Tokenizer.prototype._tokenizeChunk.html deleted file mode 100644 index ae40c94b3..000000000 --- a/docs/~/parsing.Tokenizer.prototype._tokenizeChunk.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - parsing.Tokenizer.prototype._tokenizeChunk - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.prototype.html b/docs/~/parsing.Tokenizer.prototype.html deleted file mode 100644 index f979ff295..000000000 --- a/docs/~/parsing.Tokenizer.prototype.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/~/parsing.Tokenizer.prototype.state.html b/docs/~/parsing.Tokenizer.prototype.state.html deleted file mode 100644 index 3a5c88317..000000000 --- a/docs/~/parsing.Tokenizer.prototype.state.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.Tokenizer.prototype.state - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.prototype.tokenize.html b/docs/~/parsing.Tokenizer.prototype.tokenize.html deleted file mode 100644 index eb49cfd70..000000000 --- a/docs/~/parsing.Tokenizer.prototype.tokenize.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - parsing.Tokenizer.prototype.tokenize - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.Tokenizer.prototype.tokenizeFromString.html b/docs/~/parsing.Tokenizer.prototype.tokenizeFromString.html deleted file mode 100644 index 463b99eba..000000000 --- a/docs/~/parsing.Tokenizer.prototype.tokenizeFromString.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - parsing.Tokenizer.prototype.tokenizeFromString - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenizerState.buffer.html b/docs/~/parsing.TokenizerState.buffer.html deleted file mode 100644 index 7f15cd2d9..000000000 --- a/docs/~/parsing.TokenizerState.buffer.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.TokenizerState.buffer - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenizerState.html b/docs/~/parsing.TokenizerState.html deleted file mode 100644 index 3b4c79361..000000000 --- a/docs/~/parsing.TokenizerState.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - parsing.TokenizerState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenizerState.isDone.html b/docs/~/parsing.TokenizerState.isDone.html deleted file mode 100644 index 8ebd6a293..000000000 --- a/docs/~/parsing.TokenizerState.isDone.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.TokenizerState.isDone - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.TokenizerState.tokenDefs.html b/docs/~/parsing.TokenizerState.tokenDefs.html deleted file mode 100644 index 14d9a7fc7..000000000 --- a/docs/~/parsing.TokenizerState.tokenDefs.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.TokenizerState.tokenDefs - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { type parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.createTokenizerState.html b/docs/~/parsing.createTokenizerState.html deleted file mode 100644 index 215043d20..000000000 --- a/docs/~/parsing.createTokenizerState.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.createTokenizerState - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.html b/docs/~/parsing.html deleted file mode 100644 index 9d1f448a2..000000000 --- a/docs/~/parsing.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - parsing - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace parsing -
- - - - -
-
-
-

- - - - - - - - - - -Type Aliases

T
-
- parsing.ASTNode - -
No documentation available
-
T
-
- parsing.Parser - -
No documentation available
-
T
-
- parsing.PatternFunction - -
No documentation available
-
T
-
- parsing.PatternFunctionResult - -
No documentation available
-
T
-
- parsing.Token - -
No documentation available
-
T
-
- parsing.TokenDefinitions - -
No documentation available
-
T
-
- parsing.TokenizerState - -
No documentation available
-
-
-
v
-
- parsing.createTokenizerState - -
No documentation available
-
v
-
- parsing.sequence - -
No documentation available
-
v
-
- parsing.simpleTokens - -
No documentation available
-
v
-
- parsing.token - -
No documentation available
-
-
-
-
-
-
-
-
- - diff --git a/docs/~/parsing.sequence.html b/docs/~/parsing.sequence.html deleted file mode 100644 index 2034aed48..000000000 --- a/docs/~/parsing.sequence.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.sequence - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.simpleTokens.html b/docs/~/parsing.simpleTokens.html deleted file mode 100644 index 06c527252..000000000 --- a/docs/~/parsing.simpleTokens.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.simpleTokens - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/parsing.token.html b/docs/~/parsing.token.html deleted file mode 100644 index a30e1a151..000000000 --- a/docs/~/parsing.token.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - parsing.token - eserstack documentation - - - - - - - - - - -
-
-
-
-
-

Usage

import { parsing } from ".";
-
-
-
-
-
- - diff --git a/docs/~/standards.html b/docs/~/standards.html deleted file mode 100644 index 5c1ca2772..000000000 --- a/docs/~/standards.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - standards - eserstack documentation - - - - - - - - - - -
-
-
-
-
-
- namespace standards -
- - - - -
-
-
-
-
-

Usage

import { standards } from ".";
-
-
-
-
-
- - diff --git a/etc/adrs/0001-ssr-react-key-handling-strategy.md b/etc/adrs/0001-ssr-react-key-handling-strategy.md deleted file mode 100644 index 09e5da931..000000000 --- a/etc/adrs/0001-ssr-react-key-handling-strategy.md +++ /dev/null @@ -1,397 +0,0 @@ -# SSR React Key Handling Strategy - -## Status - -Accepted - -## Context and Problem Statement - -During Server-Side Rendering (SSR) in `@eser/laroux-react-adapter`, React -generates "Each child in a list should have a unique key prop" warnings for -elements like `