From 1dc1a1a07a06f4bc61c0039b138d9faaeb0e072e Mon Sep 17 00:00:00 2001 From: "Angelo B. J. Luidens" Date: Sun, 19 Apr 2026 07:22:07 -0400 Subject: [PATCH] Stage 0: governance scaffolding + monorepo bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 foundation for the Stargue Publishing Engine (plan v2, BMAD panel-reviewed 2026-04-19 — 1 APPROVE, 6 REVISE, 0 REJECT; all principles >=3). - Governance doctrine adopted from DQMS (.clinerules/12-foundational-principles.md, .claude/hooks/gate-plan-exit.sh, .claude/skills/bmad-plan/SKILL.md) - Bun workspaces + Turbo; apps/{mcp-linkedin,scheduler,admin}; packages/{schema,sanitize,linkedin-client,observability} - Drizzle schema (content, publications, approvals, metrics, linkedin_tokens, audit, outlet_feature_flags) with idempotency_key UNIQUE and kill-switch table per TEA/dev panel revisions - LinkedIn API canon: Posts API /rest/posts (not legacy UGC); OAuth auth-code without PKCE; secretbox (not sealed-box); Community Management API as separate approval gate from MDP - Frontmatter Zod schema (status, language, outlets[], sanitize, scheduled, version) - Pino observability with PII redaction - Expand-then-contract migration runbook - Plan + panel verdicts mirrored to docs/plans/ - Deferred gates logged (Dokploy PaaS verification, LinkedIn Dev Portal app registration) bun install + bun run typecheck both exit 0 across 11 workspaces. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/hooks/gate-plan-exit.sh | 122 +++ .claude/skills/bmad-plan/SKILL.md | 192 ++++ .clinerules/12-foundational-principles.md | 155 ++++ .gitignore | 10 + CLAUDE.md | 97 ++ apps/admin/package.json | 32 + apps/admin/src/index.ts | 2 + apps/mcp-linkedin/package.json | 24 + apps/mcp-linkedin/src/server.ts | 4 + apps/scheduler/package.json | 24 + apps/scheduler/src/worker.ts | 2 + bun.lock | 870 ++++++++++++++++++ db/migrations/README.md | 38 + docs/deferred-gates.md | 27 + docs/linkedin-apps.md | 46 + docs/plans/2026-04-19-phase1-plan.md | 397 ++++++++ ...cts-publishing-engine-phase1-20260419.json | 193 ++++ package.json | 29 + packages/linkedin-client/package.json | 25 + packages/linkedin-client/src/index.ts | 6 + packages/observability/package.json | 24 + packages/observability/src/index.ts | 27 + packages/sanitize/package.json | 29 + packages/sanitize/src/index.ts | 2 + packages/schema/package.json | 30 + packages/schema/src/db.ts | 91 ++ packages/schema/src/frontmatter.ts | 50 + packages/schema/src/index.ts | 2 + tsconfig.json | 23 + turbo.json | 23 + 30 files changed, 2596 insertions(+) create mode 100755 .claude/hooks/gate-plan-exit.sh create mode 100644 .claude/skills/bmad-plan/SKILL.md create mode 100644 .clinerules/12-foundational-principles.md create mode 100644 CLAUDE.md create mode 100644 apps/admin/package.json create mode 100644 apps/admin/src/index.ts create mode 100644 apps/mcp-linkedin/package.json create mode 100644 apps/mcp-linkedin/src/server.ts create mode 100644 apps/scheduler/package.json create mode 100644 apps/scheduler/src/worker.ts create mode 100644 bun.lock create mode 100644 db/migrations/README.md create mode 100644 docs/deferred-gates.md create mode 100644 docs/linkedin-apps.md create mode 100755 docs/plans/2026-04-19-phase1-plan.md create mode 100644 docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json create mode 100644 package.json create mode 100644 packages/linkedin-client/package.json create mode 100644 packages/linkedin-client/src/index.ts create mode 100644 packages/observability/package.json create mode 100644 packages/observability/src/index.ts create mode 100644 packages/sanitize/package.json create mode 100644 packages/sanitize/src/index.ts create mode 100644 packages/schema/package.json create mode 100644 packages/schema/src/db.ts create mode 100644 packages/schema/src/frontmatter.ts create mode 100644 packages/schema/src/index.ts create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.claude/hooks/gate-plan-exit.sh b/.claude/hooks/gate-plan-exit.sh new file mode 100755 index 0000000..34f6173 --- /dev/null +++ b/.claude/hooks/gate-plan-exit.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# BMAD Plan Review Gate — PreToolUse hook for ExitPlanMode. +# +# Enforces CLAUDE.md "Multi-Agent Plan Review Gate": blocks ExitPlanMode +# until /tmp/bmad-panel-verdicts-.json contains verdicts from +# all 7 BMAD panel agents (analyst, architect, pm, sm, ux-designer, dev, tea). +# +# Uses /usr/bin/node (always present; no jq dependency which is missing in +# this box's non-interactive PATH). Fail-closed via permissionDecision=deny +# in hookSpecificOutput JSON on stdout. +# +# The Claude session is responsible for writing verdicts via Agent calls — +# this hook only validates the file exists and is well-formed. + +set -eu + +export HOOK_PAYLOAD +HOOK_PAYLOAD="$(cat)" + +/usr/bin/node -e ' +const raw = process.env.HOOK_PAYLOAD || ""; +let payload; +try { payload = JSON.parse(raw); } catch (e) { + // Malformed payload — allow through; not our job to second-guess. + process.exit(0); +} +if ((payload.tool_name || "") !== "ExitPlanMode") process.exit(0); + +const fs = require("fs"); +const sessionId = payload.session_id || "unknown"; +const verdictFile = "/tmp/bmad-panel-verdicts-" + sessionId + ".json"; + +function deny(reason) { + process.stdout.write(JSON.stringify({ + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: reason + } + })); + process.exit(0); +} + +if (!fs.existsSync(verdictFile)) { + deny("BMAD plan review gate: verdicts file missing at " + verdictFile + + ". Invoke the /bmad-plan skill (or launch the 7 panel agents manually) " + + "and write verdicts to this path before ExitPlanMode. See CLAUDE.md > Plan Review Gate."); +} + +let doc; +try { doc = JSON.parse(fs.readFileSync(verdictFile, "utf8")); } catch (e) { + deny("BMAD plan review gate: " + verdictFile + " is not valid JSON (" + e.message + ")."); +} + +const verdicts = Array.isArray(doc && doc.verdicts) ? doc.verdicts : null; +if (!verdicts || verdicts.length < 7) { + const n = verdicts ? verdicts.length : 0; + deny("BMAD plan review gate: " + verdictFile + " has only " + n + + "/7 panel verdicts. All 7 (analyst, architect, pm, sm, ux-designer, dev, tea) are required."); +} + +const missing = verdicts.filter(function (v) { return !v || !v.agent || !v.verdict; }).length; +if (missing > 0) { + deny("BMAD plan review gate: " + missing + " verdict entries missing .agent or .verdict fields."); +} + +const rejects = verdicts.filter(function (v) { + return String(v.verdict).toUpperCase() === "REJECT"; +}).length; +if (rejects > 0) { + deny("BMAD plan review gate: " + rejects + + " panel agent(s) returned REJECT. Revise the plan or escalate before ExitPlanMode."); +} + +// Foundational Principles gate (two-tier). +// Tier-1 principles MUST be scored on every verdict. +// Tier-2 principles are scored only when declared in doc.applicable_tier2. +// See .clinerules/12-foundational-principles.md for doctrine and rubric. +var TIER1 = ["SEBP", "SSoT", "FPT", "DiDSP", "PbD", "OF"]; +var TIER2_ALLOWED = ["RBR", "A11y", "Testability"]; +var MIN_SCORE = 3; + +var declaredTier2 = Array.isArray(doc.applicable_tier2) ? doc.applicable_tier2.slice() : []; +var invalidTier2 = declaredTier2.filter(function (k) { return TIER2_ALLOWED.indexOf(k) === -1; }); +if (invalidTier2.length > 0) { + deny("BMAD plan review gate: applicable_tier2 contains unknown principle(s): " + + invalidTier2.join(", ") + ". Allowed values: " + TIER2_ALLOWED.join(", ") + + ". See .clinerules/12-foundational-principles.md > Tier-2 Principles."); +} +var requiredKeys = TIER1.concat(declaredTier2); + +var principleViolations = []; +verdicts.forEach(function (v) { + var p = v && v.principles; + if (!p || typeof p !== "object") { + principleViolations.push((v && v.agent ? v.agent : "?") + ": missing principles object"); + return; + } + requiredKeys.forEach(function (key) { + var entry = p[key]; + if (!entry || typeof entry.score !== "number") { + principleViolations.push(v.agent + "." + key + ": missing or non-numeric score"); + } else if (entry.score < MIN_SCORE) { + principleViolations.push(v.agent + "." + key + ": score " + entry.score + " < " + MIN_SCORE); + } else if (typeof entry.rationale !== "string" || !entry.rationale.trim()) { + principleViolations.push(v.agent + "." + key + ": missing rationale"); + } + }); +}); +if (principleViolations.length > 0) { + var tier2Note = declaredTier2.length > 0 + ? " plus declared tier-2 {" + declaredTier2.join(",") + "}" + : " (no tier-2 declared)"; + deny("BMAD plan review gate: foundational principle scoring failed:\n - " + + principleViolations.join("\n - ") + + "\n\nEvery verdict must include principles for tier-1 {" + TIER1.join(",") + "}" + tier2Note + + " with .score >= " + MIN_SCORE + " and a non-empty .rationale. " + + "See .clinerules/12-foundational-principles.md."); +} + +process.exit(0); +' diff --git a/.claude/skills/bmad-plan/SKILL.md b/.claude/skills/bmad-plan/SKILL.md new file mode 100644 index 0000000..9a79e2f --- /dev/null +++ b/.claude/skills/bmad-plan/SKILL.md @@ -0,0 +1,192 @@ +--- +name: bmad-plan +description: | + BMAD 7-agent plan review workflow. Use when drafting any implementation + plan that the CLAUDE.md "Multi-Agent Plan Review Gate" directive applies + to. Ensures the gate is satisfied before ExitPlanMode is attempted — + the PreToolUse hook at .claude/hooks/gate-plan-exit.sh will deny + ExitPlanMode otherwise. +--- + +# BMAD Plan + 7-Agent Panel Review + +## When to invoke + +Every plan-mode session for a non-trivial implementation. CLAUDE.md mandates this for anything beyond the smallest of fixes. Skip only for genuine one-liners (typo, comment tweak) — and even then, think twice. + +## Workflow (non-negotiable) + +### Phase 1 — Draft + +1. Write the plan to the plan file supplied by plan mode (e.g. `/home/devuser/.claude/plans/.md`). +2. The plan must follow CLAUDE.md format: `Context`, stages with DoD, critical files, verification, authorization gates, risks. +3. **Declare tier-2 applicability** at the top of the plan as an `Applicability:` line, e.g.: + + ``` + Applicability: tier-2 = [RBR, A11y] # plan touches a migration and adds new UI + ``` + + Allowed values: `RBR` (Reversibility / Blast Radius), `A11y` (Accessibility), `Testability`. Empty list (`tier-2 = []`) is valid when none apply. The Analyst (Mary) is responsible for the call — see `.clinerules/12-foundational-principles.md > Tier-2 Principles` for when to declare each. + +### Phase 1.5 — Harden the per-agent dispatch prompt via prompt-master + +The 7 BMAD panel charters (`_bmad/bmm/agents/{analyst,architect,pm,sm,ux-designer,dev,tea}.md`) are the SSoT for each panelist's role and stay untouched. What this skill DOES build per-plan is the **dispatch prompt** — the message the session sends to each panelist along with the plan to review, the cross-cutting principle rubric (tier-1 + declared tier-2), and the verdict shape. + +Per [`.clinerules/13-agent-spawning-protocol.md`](../../../.clinerules/13-agent-spawning-protocol.md), this dispatch prompt MUST be hardened before Phase 2: + +1. Draft the dispatch template (one shape, parameterized per panelist). +2. Invoke the `prompt-master` skill with the draft + plan summary + principle rubric. One invocation covers all 7 panelists since they share dispatch structure. +3. Apply prompt-master's hardening to the dispatch (output-format lock, severity anchors, structural caps on verdict length, link-format lock, verification discipline). +4. **Add the standard graph-tool leverage block** (per [`memory-bank/agent-prompts/README.md > Graph-tool leverage`](../../../memory-bank/agent-prompts/README.md)) to the dispatch — instruct each panelist to consult CRG (`crg_*` MCP tools) and Graphify (`docs/architecture/knowledge-graph/GRAPH_REPORT.md`) before speculative file reads. CRG benchmarks at 8.2× token reduction; Dev (Amelia) and TEA (Murat) gain the most leverage from this. +5. Use the hardened dispatch in Phase 2 — substituting only the panelist-specific focus from the Phase 2 table. + +This is the pattern that catches: link-format drift, unanchored severity calibration, soft length caps ignored, asymmetric specificity across panelists, and unverifiable aggregate claims. Skipping this step is the documented failure mode of the 2026-04-16 9-agent baseline audit. + +### Phase 2 — Launch the 7 panel agents in parallel + +**MUST launch each agent separately in parallel** per memory `feedback_separate_agents` — never consolidate them into one prompt. + +Panel members (charters at `_bmad/bmm/agents/.md`): + +| Agent | Focus | +| --- | --- | +| Mary (analyst) | Requirements completeness, stakeholder alignment | +| Winston (architect) | Architectural soundness, scalability, technology selection | +| John (pm) | Business value, prioritization, risk identification | +| Bob (sm) | Story clarity, actionability, sprint-readiness | +| Sally (ux-designer) | User impact, UX considerations, accessibility | +| Amelia (dev) | Implementation feasibility, code patterns, existing code reuse | +| Murat (tea) | Test strategy, quality gates, CI/CD impact | + +Each agent MUST return a structured verdict. Tier-1 principles are scored on every plan; tier-2 principles are scored only when declared in the plan's `Applicability` line (see Phase 1 step 3). + +``` +VERDICT: APPROVE | REVISE | REJECT +CONFIDENCE: low | medium | high +STRENGTHS: - … +GAPS: - … +PRINCIPLES (tier-1, always): + SEBP: { score: 1-5, rationale: "…" } + SSoT: { score: 1-5, rationale: "…" } + FPT: { score: 1-5, rationale: "…" } + DiDSP: { score: 1-5, rationale: "…" } + PbD: { score: 1-5, rationale: "…" } + OF: { score: 1-5, rationale: "…" } +PRINCIPLES (tier-2, only if declared): + RBR: { score: 1-5, rationale: "…" } # if applicable + A11y: { score: 1-5, rationale: "…" } # if applicable + Testability: { score: 1-5, rationale: "…" } # if applicable +ONE-LINE SUMMARY: … +``` + +### Foundational Principle Scoring (gate-enforced) + +All 7 agents MUST score the plan against the **six tier-1 principles**. They MUST also score every **tier-2 principle declared in `applicable_tier2`** for this plan. The hook denies `ExitPlanMode` if any required score is below 3, missing, or has no rationale. + +**Tier-1 (always scored):** + +| Principle | Definition | Score 1 (violation) → 5 (exemplary) | +|-----------|------------|-------------------------------------| +| **SEBP** — Software Engineering Best Practices | SOLID, DRY, separation of concerns, clear boundaries | 1 = duplicated logic / leaky abstractions; 5 = clean composition, no smell | +| **SSoT** — Single Source of Truth | One authoritative source per concept (constants, types, schemas, config) | 1 = duplicated constants/types/config; 5 = every fact has exactly one home | +| **FPT** — First Principle Thinking | Reasoning grounded in this codebase + verified docs, not training-data assumptions | 1 = "Next.js probably does X" without checking; 5 = cites file paths, runs probes | +| **DiDSP** — Defense in Depth Security | Layered security: input validation, authz, rate limits, audit, least privilege | 1 = unvalidated input or missing authz on sensitive op; 5 = multiple independent layers + audit | +| **PbD** — Privacy by Design | GDPR Art. 25: minimisation, purpose limitation, retention, lawful basis, user rights | 1 = collects PII without purpose / no erasure path; 5 = active minimisation + lifecycle wired | +| **OF** — Observability First | Structured logs, metrics, audit on every meaningful boundary | 1 = silent failure paths; 5 = correlation IDs, audit on regulated ops, dashboards referenced | + +**Tier-2 (scored only when declared):** + +| Principle | Declare when… | Score 1 → 5 | +|-----------|---------------|-------------| +| **RBR** — Reversibility / Blast Radius | Plan touches migrations, deletes, third-party calls, shared infra, CI/CD, prod data | 1 = destructive, no rollback; 5 = expand-then-contract + flags + recovery procedure | +| **A11y** — Accessibility (WCAG 2.2 AA) | Plan introduces or significantly changes user-facing UI | 1 = keyboard-trap / colour-only state; 5 = keyboard parity, contrast, screen-reader tested | +| **Testability** | Plan introduces or significantly changes business logic / domain rules / boundaries | 1 = logic embedded in framework callbacks, hidden globals; 5 = pure cores, injectable seams | + +A score of 3 means "adequate, no blocking concerns." Below 3 blocks the plan. Score 1–2 must be paired with a `REVISE` or `REJECT` verdict and a concrete gap entry. + +Full doctrine, definitions, and acceptance signals: [`.clinerules/12-foundational-principles.md`](../../../.clinerules/12-foundational-principles.md). + +### Phase 3 — Write verdicts file + +After all 7 agents return, write: + +``` +/tmp/bmad-panel-verdicts-${CLAUDE_SESSION_ID}.json +``` + +Shape (enforced by the hook): + +```json +{ + "plan_file": "", + "created_at": "", + "applicable_tier2": ["RBR", "A11y"], + "verdicts": [ + { + "agent": "analyst", + "verdict": "APPROVE | REVISE | REJECT", + "confidence": "low | medium | high", + "summary": "one-line summary from the agent", + "gaps": ["…", "…"], + "principles": { + "SEBP": { "score": 4, "rationale": "…" }, + "SSoT": { "score": 5, "rationale": "…" }, + "FPT": { "score": 4, "rationale": "…" }, + "DiDSP": { "score": 3, "rationale": "…" }, + "PbD": { "score": 4, "rationale": "…" }, + "OF": { "score": 4, "rationale": "…" }, + "RBR": { "score": 3, "rationale": "…" }, + "A11y": { "score": 4, "rationale": "…" } + } + }, + { "agent": "architect", ... }, + { "agent": "pm", ... }, + { "agent": "sm", ... }, + { "agent": "ux-designer", ... }, + { "agent": "dev", ... }, + { "agent": "tea", ... } + ] +} +``` + +Notes on the shape: + +- `applicable_tier2` mirrors the `Applicability:` line from the plan (Phase 1 step 3). Allowed values: `RBR`, `A11y`, `Testability`. An empty array `[]` is valid and means no tier-2 principles apply. +- Each verdict's `principles` object MUST cover all six tier-1 keys plus every key in `applicable_tier2`. Tier-2 keys not declared SHOULD be omitted (the hook ignores them). + +Requirements enforced by `gate-plan-exit.sh`: + +- `verdicts` is an array of ≥7 entries. +- Every entry has non-empty `agent` and `verdict`. +- Zero entries with `verdict: "REJECT"` (any REJECT blocks exit). +- `applicable_tier2` (when present) contains only `RBR`, `A11y`, or `Testability`. +- Every verdict has `principles.{SEBP,SSoT,FPT,DiDSP,PbD,OF}` with numeric `.score >= 3` and non-empty `.rationale`. +- Every verdict has the same fields for every key in `applicable_tier2`. + +### Phase 4 — Consolidate + +1. Read all 7 verdicts. Merge gap/concern items into an action list. +2. Revise the plan file to address every REVISE item. Cite which agent raised each concern (traceability). +3. If unanimous APPROVE: proceed to Phase 5. +4. If any REVISE remains after plan revision: re-run the panel or escalate to the user. +5. If any REJECT: revise or escalate; do not bypass. + +### Phase 5 — Exit + +1. Call `ExitPlanMode`. The hook reads the verdicts file, confirms ≥7 entries and no REJECTs, and allows the call. +2. After user approves, copy the plan to `docs/plans/{YYYY-MM-DD}-{topic}.md` per CLAUDE.md filing rule (plans in `.claude/plans/` are ephemeral). + +## Failure modes + +- **Hook denies with "verdicts file missing"**: you haven't written `/tmp/bmad-panel-verdicts-${CLAUDE_SESSION_ID}.json` yet. Run the panel (Phase 2) and write the file (Phase 3). +- **Hook denies with "only N/7 verdicts"**: an agent result was dropped. Re-launch only the missing agents and update the file. +- **Hook denies with "N panel agent(s) returned REJECT"**: revise the plan or escalate — do not remove REJECTs from the file without addressing their substance. +- **Hook denies with "foundational principle scoring failed"**: one or more verdicts is missing the `principles` object, has a required `.score < 3`, or has an empty `.rationale` for a tier-1 key or a declared tier-2 key. Re-engage the agent(s) whose verdicts triggered the failure with the rubric in *Foundational Principle Scoring* — never raise a score without substantive justification. +- **Hook denies with "applicable_tier2 contains unknown principle(s)"**: the `applicable_tier2` array contains a value other than `RBR`, `A11y`, or `Testability`. Fix the typo or remove the entry — do not extend the allowed set without updating both this skill and `.clinerules/12-foundational-principles.md`. + +## Anti-patterns (do not do) + +- Do not skip the panel and call ExitPlanMode directly. The hook will deny. +- Do not batch agents into a single prompt. Per memory `feedback_separate_agents`, always separate + parallel. +- Do not write fabricated verdicts to the file to bypass the gate. The gate exists to protect plan quality; bypassing it defeats the purpose and will be caught by the next review. +- Do not use this skill for trivial one-line fixes — see CLAUDE.md scope rules. diff --git a/.clinerules/12-foundational-principles.md b/.clinerules/12-foundational-principles.md new file mode 100644 index 0000000..6d325e8 --- /dev/null +++ b/.clinerules/12-foundational-principles.md @@ -0,0 +1,155 @@ +# 12 — Foundational Principles (Non-Negotiable) + +**Status:** Active doctrine. Enforced by [`.claude/hooks/gate-plan-exit.sh`](../.claude/hooks/gate-plan-exit.sh) and the [`/bmad-plan` skill](../.claude/skills/bmad-plan/SKILL.md). + +Every implementation plan and the execution of that plan MUST satisfy these principles. They apply to BMAD workflows, ad-hoc fixes, refactors, and tooling work alike. + +--- + +## Two-Tier Model + +Principles are split into two tiers to keep the rubric discriminating instead of ritualistic. + +| Tier | When scored | Hook behaviour | +| ---- | ----------- | -------------- | +| **Tier-1 (Always)** | Every verdict, every plan | Hook **denies** `ExitPlanMode` if any verdict scores any tier-1 principle <3 or omits its rationale. | +| **Tier-2 (Conditional)** | Only when declared `applicable_tier2` for the plan | Hook **denies** `ExitPlanMode` if a declared tier-2 principle is unscored, scored <3, or has no rationale. | + +The Analyst (Mary) declares `applicable_tier2` at the end of Phase 1 (plan draft). The list lives at the top of the verdicts file: + +```json +{ + "plan_file": "", + "applicable_tier2": ["RBR", "A11y"], + "verdicts": [ … ] +} +``` + +Allowed tier-2 values: `RBR`, `A11y`, `Testability`. Any other value causes the hook to deny. + +--- + +## Tier-1 Principles (always scored) + +### SEBP — Software Engineering Best Practices + +**Definition:** SOLID (single-responsibility, open-closed, Liskov, interface-segregation, dependency-inversion), DRY, separation of concerns, clear module boundaries. + +**Score 1 (violation):** duplicated logic across files; god-objects; leaky abstractions; mixed concerns (e.g., a route handler that also formats PDFs and writes audit logs inline). +**Score 3 (adequate):** clean enough; no immediate smell; reviewer would not block. +**Score 5 (exemplary):** composition over inheritance; each unit has one reason to change; boundaries documented in code shape, not just comments. + +**Acceptance signals:** named exports with single responsibility; no copy-pasted blocks ≥6 lines; route handlers delegate to `src/lib/*` modules. + +### SSoT — Single Source of Truth + +**Definition:** every concept (constant, type, schema, config, secret, user-facing string, status enum) has exactly one authoritative location. + +**Score 1:** the same constant or enum is duplicated in two or more files; type drift between API and DB; magic numbers in components. +**Score 3:** core domain types/constants centralised; minor leakage acceptable when behind a TODO or feature-flag. +**Score 5:** generated artifacts (Prisma client, OpenAPI types) flow from a single schema; constants live in `src/lib/constants/*`; i18n strings in one bundle. + +**Acceptance signals:** no `const RETRY_LIMIT = 3` defined in two files; Zod schemas re-used (not re-declared) on both client and server; status enums imported from one module. + +### FPT — First Principle Thinking + +**Definition:** reasoning grounded in this codebase, this stack version, and verified docs — not training-data assumptions or generic patterns. + +**Score 1:** "Next.js v15 probably does X" without checking; copy-pastes a pattern that doesn't fit the project's existing shape; ignores Memory-Bank lessons. +**Score 3:** cites at least one file or doc; checks a non-obvious assumption. +**Score 5:** identifies the actual constraint, not the symptom; runs probes (small scripts, type-checks) to verify before committing to a design; references prior incidents in `memory-bank/systemPatterns.md`. + +**Acceptance signals:** plan references concrete file paths; cites Tailwind v4 / Prisma 6 / NextAuth v5 docs (not v3 patterns); names the prior pattern being followed or deliberately diverged from. + +### DiDSP — Defense in Depth Security Practices + +**Definition:** multiple independent layers of defence: input validation, authentication, authorization, rate limiting, audit logging, least privilege, output encoding. + +**Score 1:** unvalidated user input reaches the database; missing authz on a sensitive operation; secrets in `public/`; tokens logged; SQL/HTML built by string concatenation. +**Score 3:** Zod validation + authn + authz on all touched API routes; secrets via env; no obvious OWASP top-10 hole. +**Score 5:** layered controls (Cerbos policy + route guard + DB constraint); audit log on sensitive ops; rate limit on abuse-prone endpoints; structured error responses that don't leak internals. + +**Acceptance signals:** every new `src/app/api/**/route.ts` calls a Zod parse; every `auth()` call is paired with a Cerbos check for authz; no `console.log` of tokens, PII, or secrets; sensitive assets out of `public/`. + +### PbD — Privacy by Design + +**Definition:** GDPR Article 25 — data minimisation, purpose limitation, retention limits, lawful basis, transparency, user rights (access, portability, erasure, rectification). + +**Score 1:** collects PII without a documented purpose; retains indefinitely; no consent record for a new processing activity; identity documents accessible without authn; cross-tenant data leakage possible. +**Score 3:** new fields have a documented purpose; consent is recorded if required; deletion path exists; no obvious GDPR regression. +**Score 5:** data minimisation actively reduces surface area; retention timer wired to lifecycle; consent versioning supports SAR/portability/erasure flows; brand-agnostic (no tenant cross-leakage); DPIA reasoning in the plan if processing is high-risk. + +**Acceptance signals:** new PII columns have a `retention` policy reference; consent records updated when processing scope changes; SAR/erasure paths tested; identity-document endpoints require authn + authz + audit; no PII in URLs, logs, or error messages. + +### OF — Observability First + +**Definition:** every meaningful boundary emits structured signals (logs, metrics, traces, audit) sufficient to diagnose failure without re-running the operation. + +**Score 1:** silent failure paths; bare `try/catch` that swallows errors; no log on auth/OCR/PDF/contract events; debugging requires re-creating user state. +**Score 3:** errors logged with context; key state transitions emit a record; oncall can locate a failure within minutes. +**Score 5:** structured logs (JSON, correlation IDs); audit trail for regulated ops (consent changes, document state, contract status); user-facing errors carry a trace ID; dashboards/alerts referenced in the plan when introducing new failure modes. + +**Acceptance signals:** new background jobs log start/finish/error with correlation ID; new state machines emit one event per transition; new third-party calls log latency and outcome; PII redacted from logs. + +--- + +## Tier-2 Principles (conditional) + +Declare in `applicable_tier2` when relevant. Score on the same 1–5 scale; ≥3 required when declared. + +### RBR — Reversibility / Blast Radius + +**When to declare:** the plan touches migrations, deletes, third-party calls (email, SMS, payment, signing), shared infrastructure, CI/CD, production data, or any irreversible state change. + +**Score 1:** destructive operation with no rollback path; one-way migration with no shadow column; bulk delete without dry-run. +**Score 3:** rollback strategy named; non-prod rehearsal planned. +**Score 5:** expand-then-contract migration; feature flags gate rollout; bulk operations are batched and resumable; every irreversible step has a guard, an audit record, and a documented recovery procedure. + +### A11y — Accessibility (WCAG 2.2 AA) + +**When to declare:** the plan introduces or significantly changes user-facing UI, forms, navigation, or content presentation. + +**Score 1:** keyboard-trap; missing labels; colour-only state; non-semantic markup; forms without error association. +**Score 3:** semantic HTML; visible focus; labels on inputs; passes axe-core baseline. +**Score 5:** keyboard parity with mouse; aria-live where dynamic; respects `prefers-reduced-motion`; meets contrast ratios on all themes; tested with a screen reader. + +### Testability + +**When to declare:** the plan introduces or significantly changes business logic, domain rules, or system boundaries that future tests must reach. + +**Score 1:** logic embedded in framework callbacks (Server Components, route handlers) with no extraction; hidden globals; non-deterministic clocks/IDs. +**Score 3:** pure functions extracted into `src/lib/*`; clock/ID/randomness injectable; tests can call the unit without booting the framework. +**Score 5:** seams expose every branch; fakes/spies straightforward; integration boundaries (DB, external APIs) covered by either real-DB tests or contract tests; no mocks where the real thing fits. + +--- + +## Cross-cutting expectations (apply to all principles) + +- **Plan rationale must cite evidence.** "Score 4" alone is not a verdict; the rationale must reference a file, a behaviour, or a decision in the plan. +- **Tie-break:** when a principle conflicts with another (e.g., DiDSP suggests a layer that hurts SSoT), the plan must name the trade-off explicitly. The reviewer should score *resolution quality*, not pretend there is no tension. +- **No retrofitting scores.** Do not raise a score after-the-fact to satisfy the gate; revise the plan instead. + +--- + +## Review-process integration + +1. **Phase 1 (Draft)** — author writes the plan and selects `applicable_tier2`. The plan file should include a short *Applicability* line stating which tier-2 principles apply and why. +2. **Phase 2 (Panel)** — each of the 7 BMAD agents (analyst, architect, pm, sm, ux-designer, dev, tea) returns a verdict. Every verdict scores all tier-1 principles + every declared tier-2 principle. +3. **Phase 3 (Verdicts file)** — write `/tmp/bmad-panel-verdicts-.json` with the schema in the skill. +4. **Phase 4 (Consolidate)** — address every score <3 in the plan; re-engage the dissenting agent if needed. +5. **Phase 5 (Exit)** — call `ExitPlanMode`; the hook validates and approves. + +--- + +## Anti-patterns + +- **Score-inflation:** raising scores to clear the gate without revising the plan. The next reviewer will catch it; trust will erode. +- **Tier-2 over-declaration:** declaring every tier-2 principle on every plan dilutes the signal. Only declare what genuinely applies. +- **Tier-2 under-declaration to bypass review:** if the plan touches migrations, declare RBR. If it touches UI, declare A11y. If it changes domain logic, declare Testability. The Analyst (Mary) is responsible; gaming this defeats the doctrine. +- **Treating principles as a checklist:** the rubric exists to surface real tensions and trade-offs. A plan that scores 5 on all six tier-1 principles without acknowledging any trade-off is suspect. + +--- + +## Precedence + +This file ranks below CLAUDE.md and the documentation-lifecycle / feature-tracking / agent-mode protocols, and above the language-and-framework standards. See `CLAUDE.md > Conflict Resolution Precedence` for the full ordering. diff --git a/.gitignore b/.gitignore index 2309cc8..fe54519 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,13 @@ dist .yarn/install-state.gz .pnp.* +# Turbo +.turbo + +# Bun +bun.lockb + +# OS junk +.DS_Store +Thumbs.db + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..583715a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,97 @@ +# CLAUDE.md — Stargue Publishing Engine + +Project-level context for Claude Code. Loaded every turn. + +## Purpose + +Automated two-channel Publishing Engine: Obsidian vault → stargue.com/.net + LinkedIn (personal profile + Stargue Company Page) via a Stargue-owned LinkedIn MCP server. Phase 1 scope is tracked in `docs/plans/2026-04-19-phase1-plan.md` (mirrored from the vault SSoT at `Stargue/Projects/Publishing Engine/Plan - Phase 1 Automated Publishing Engine.md`). + +## Source-of-Truth Precedence + +1. **Vault** (`c:\Users\aluid\OneDrive\Documents\Obsidian\Angelo'sSBO\`) — content notes, design philosophy (Nine Laws of the Hearth), outlet profiles, PRDs, plans. SSoT for *intent*. +2. **This repo** (`/home/devuser/projects/stargue-publishing-engine/`) — code implementation. SSoT for *behavior*. +3. **stargue-com + stargue-net** (`/home/devuser/projects/stargue-{com,net}/`) — Next.js static sites that consume `packages/sanitize` + `packages/schema` as workspace dependencies for build-time content transformation. +4. **Production state** (Dokploy on sg-paas-s1.stargue.net, Postgres, Redis, LinkedIn) — SSoT for *runtime*. Never assume; always verify via Dokploy MCP, DB query, or LinkedIn `whoami`. + +## Governance + +- **Foundational principles** (Tier-1 always + declared Tier-2): `.clinerules/12-foundational-principles.md` — adopted 2026-04-19 from DQMS (ref: `/home/devuser/projects/dqms/.clinerules/12-foundational-principles.md`). DQMS is the evolving SSoT for Stargue's BMAD application; this file is a dated snapshot. +- **Plan-review gate** (7-agent BMAD panel): enforced by `.claude/hooks/gate-plan-exit.sh` on `ExitPlanMode`. Runs the verdicts validator against `/tmp/bmad-panel-verdicts-${CLAUDE_SESSION_ID}.json`. +- **Panel charters** (roles for analyst, architect, pm, sm, ux-designer, dev, tea): referenced by path from DQMS at `/home/devuser/projects/dqms/_bmad/bmm/agents/*.md`. No local install in Phase 1 (per user directive 2026-04-19). +- **Tier-2 applicability**: every plan declares `applicable_tier2 = [RBR | A11y | Testability]`. See `.clinerules/12-foundational-principles.md > Tier-2 Principles`. + +## Architecture + +Monorepo (Bun workspaces + Turbo): + +``` +apps/mcp-linkedin/ MCP server (stdio transport) for Claude Code to drive LinkedIn operations +apps/scheduler/ BullMQ worker — imports packages/linkedin-client directly; no HTTP to MCP +apps/admin/ Next.js App Router — UI + /api/* route handlers +packages/sanitize/ remark plugin + blocklist + length validation + test corpus +packages/schema/ Zod schemas, Drizzle DB schema, frontmatter parser (SSoT for data shapes) +packages/linkedin-client/ LinkedIn Posts API client + encrypted token store +packages/observability/ Pino + OTel + correlation IDs +``` + +Scheduler imports `packages/linkedin-client` directly — MCP is **not** on the scheduler's hot path. MCP exists so Claude Code can invoke LinkedIn operations interactively (posting, metrics queries, auth status). HTTP MCP transport is deliberately deferred. + +## LinkedIn — canonical facts (verified 2026-04-19) + +- API: **Posts API** `https://api.linkedin.com/rest/posts` (the UGC Posts API is **legacy** — do not use). Required headers: `LinkedIn-Version: 202404` (YYYYMM, update on cadence), `X-Restli-Protocol-Version: 2.0.0`, `Authorization: Bearer `. +- Auth: OAuth 2.0 Authorization Code flow. **PKCE is NOT supported** by LinkedIn 3LO per `https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow`. Use confidential-client with `client_secret` + CSRF-protected `state`. +- Products in Dev Portal (each separately enabled/approved): + - `Sign In with LinkedIn using OpenID Connect` → scopes `openid profile email` + - `Share on LinkedIn` → `w_member_social` (personal-profile posts) + - `Community Management API` → `w_organization_social`, `r_organization_social` (organization posts — **requires LinkedIn partner approval**) + - `Marketing Developer Platform (MDP)` → programmatic refresh tokens (requires LinkedIn partner approval; without it, access tokens expire in 60 days with **no refresh** — re-auth required) +- Stargue Company Page URN: `urn:li:organization:2605890` +- Primary identity: Angelo personal profile (`angeloluidens`), resolved at OAuth time to `urn:li:person:` +- Token encryption: `libsodium crypto_secretbox` (symmetric XSalsa20-Poly1305) — **not sealed-box**. Key stored in Dokploy secrets. + +## Commands + +```bash +bun install # install workspace deps +bun run typecheck # workspace-wide tsc --noEmit +bun run lint # workspace-wide lint +bun run test # workspace-wide vitest +bun run build # turbo build +bun run dev # turbo dev (admin, scheduler, mcp-linkedin) + +# Migration commands +bun run db:migrate # drizzle-kit migrate +bun run db:migrate:rehearse # expand-then-contract rehearsal on staging + +# MCP invocation (stdio) +docker exec -i mcp-linkedin node apps/mcp-linkedin/dist/server.js +``` + +## Deployment + +Dokploy stack `stargue-publishing-engine` on `sg-paas-s1.stargue.net`: +- `admin` — Next.js standalone, Traefik at `publishing.stargue.net`, Authentik SSO +- `scheduler` — BullMQ worker +- `mcp-linkedin` — stdio-only; started on demand by Claude Code via `docker exec` +- `postgres` + `redis` — shared PaaS cluster + +DR: nightly `pg_dump` to Neon free tier. RPO ~24h. + +## Rules + +- **Additive by default** — never delete production data or published content without explicit user confirmation. +- **No PII in logs** — content body is hashed, not logged; tokens never printed. +- **Every publish is approval-gated** — no auto-publish without a row in `approvals` for the (content_id, outlet) pair. +- **Kill-switch respected** — every dispatch checks `outlet_feature_flags` before making LinkedIn calls. +- **Idempotency enforced** — every LinkedIn API call carries an idempotency key derived from (content_id, outlet, scheduled_at). +- **Expand-then-contract migrations** — every schema change has a rollback runbook in `db/migrations/README.md`. +- **All operations in WSL Debian** — per vault-level rule. `wsl -d Debian -- bash -c '…'` for any shell invocation from Windows. + +## Related (vault paths) + +- `Stargue/Projects/Publishing Engine/Plan - Phase 1 Automated Publishing Engine.md` — current plan (SSoT) +- `Stargue/Projects/Publishing Engine/Publishing Engine PRD.md` — PRD v2.2 +- `Stargue/Projects/Publishing Engine/Design Philosophy - The Nine Laws of the Hearth.md` — visual + interaction SSoT +- `Stargue/Projects/Publishing Engine/Outlet Profiles/LinkedIn.md` — outlet specs +- `Stargue/Projects/Publishing Engine/7 Cs LinkedIn Series - Continuation Plan.md` — first content program +- `Stargue/Projects/Publishing Engine/Implementation Progress.md` — running state diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 0000000..b96abcf --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,32 @@ +{ + "name": "@stargue/admin", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "dev": "next dev -p 3002", + "start": "next start -p 3002", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "next lint" + }, + "dependencies": { + "@stargue/schema": "workspace:*", + "@stargue/sanitize": "workspace:*", + "@stargue/observability": "workspace:*", + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@axe-core/playwright": "^4.10.0", + "@playwright/test": "^1.48.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "axe-core": "^4.10.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/apps/admin/src/index.ts b/apps/admin/src/index.ts new file mode 100644 index 0000000..7048a8d --- /dev/null +++ b/apps/admin/src/index.ts @@ -0,0 +1,2 @@ +// Admin dashboard (Next.js App Router) — full implementation in Stage 5. +export const ADMIN_APP_NAME = "@stargue/admin"; diff --git a/apps/mcp-linkedin/package.json b/apps/mcp-linkedin/package.json new file mode 100644 index 0000000..8cce104 --- /dev/null +++ b/apps/mcp-linkedin/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stargue/mcp-linkedin", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit", + "dev": "bun --watch src/server.ts", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.0.3", + "@stargue/linkedin-client": "workspace:*", + "@stargue/schema": "workspace:*", + "@stargue/observability": "workspace:*", + "zod": "^3.23.0" + }, + "devDependencies": { + "vitest": "^2.1.0", + "typescript": "^5.7.0" + } +} diff --git a/apps/mcp-linkedin/src/server.ts b/apps/mcp-linkedin/src/server.ts new file mode 100644 index 0000000..a0cf233 --- /dev/null +++ b/apps/mcp-linkedin/src/server.ts @@ -0,0 +1,4 @@ +// MCP server entry point. Full implementation in Stage 3. +// MCP spec version pinned: 2024-11-05 (Streamable HTTP + stdio). +export const MCP_SERVER_NAME = "@stargue/mcp-linkedin"; +export const MCP_SPEC_VERSION = "2024-11-05"; diff --git a/apps/scheduler/package.json b/apps/scheduler/package.json new file mode 100644 index 0000000..083edba --- /dev/null +++ b/apps/scheduler/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stargue/scheduler", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit", + "dev": "bun --watch src/worker.ts", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'" + }, + "dependencies": { + "@stargue/linkedin-client": "workspace:*", + "@stargue/schema": "workspace:*", + "@stargue/observability": "workspace:*", + "bullmq": "^5.30.0", + "ioredis": "^5.4.0" + }, + "devDependencies": { + "vitest": "^2.1.0", + "typescript": "^5.7.0" + } +} diff --git a/apps/scheduler/src/worker.ts b/apps/scheduler/src/worker.ts new file mode 100644 index 0000000..c2b1800 --- /dev/null +++ b/apps/scheduler/src/worker.ts @@ -0,0 +1,2 @@ +// BullMQ worker entry point. Full implementation in Stage 4. +export const SCHEDULER_NAME = "@stargue/scheduler"; diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..3b80455 --- /dev/null +++ b/bun.lock @@ -0,0 +1,870 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "stargue-publishing-engine", + "devDependencies": { + "@types/node": "^22.0.0", + "bun-types": "^1.3.12", + "turbo": "^2.3.0", + "typescript": "^5.7.0", + }, + }, + "apps/admin": { + "name": "@stargue/admin", + "version": "0.1.0", + "dependencies": { + "@stargue/observability": "workspace:*", + "@stargue/sanitize": "workspace:*", + "@stargue/schema": "workspace:*", + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "zod": "^3.23.0", + }, + "devDependencies": { + "@axe-core/playwright": "^4.10.0", + "@playwright/test": "^1.48.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "axe-core": "^4.10.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "apps/mcp-linkedin": { + "name": "@stargue/mcp-linkedin", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "1.0.3", + "@stargue/linkedin-client": "workspace:*", + "@stargue/observability": "workspace:*", + "@stargue/schema": "workspace:*", + "zod": "^3.23.0", + }, + "devDependencies": { + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "apps/scheduler": { + "name": "@stargue/scheduler", + "version": "0.1.0", + "dependencies": { + "@stargue/linkedin-client": "workspace:*", + "@stargue/observability": "workspace:*", + "@stargue/schema": "workspace:*", + "bullmq": "^5.30.0", + "ioredis": "^5.4.0", + }, + "devDependencies": { + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "packages/linkedin-client": { + "name": "@stargue/linkedin-client", + "version": "0.1.0", + "dependencies": { + "libsodium-wrappers-sumo": "^0.7.15", + }, + "devDependencies": { + "msw": "^2.6.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "packages/observability": { + "name": "@stargue/observability", + "version": "0.1.0", + "dependencies": { + "pino": "^9.5.0", + }, + "devDependencies": { + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "packages/sanitize": { + "name": "@stargue/sanitize", + "version": "0.1.0", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + }, + "devDependencies": { + "@types/mdast": "^4.0.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + "packages/schema": { + "name": "@stargue/schema", + "version": "0.1.0", + "dependencies": { + "drizzle-orm": "^0.36.0", + "zod": "^3.23.0", + }, + "devDependencies": { + "drizzle-kit": "^0.28.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0", + }, + }, + }, + "packages": { + "@axe-core/playwright": ["@axe-core/playwright@4.11.2", "", { "dependencies": { "axe-core": "~4.11.3" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-iP6hfNl9G0j/SEUSo8M7D80RbcDo9KRAAfDP4IT5OHB+Wm6zUHIrm8Y51BKI+Oyqduvipf9u1hcRy57zCBKzWQ=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@inquirer/ansi": ["@inquirer/ansi@2.0.5", "", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="], + + "@inquirer/confirm": ["@inquirer/confirm@6.0.11", "", { "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-pTpHjg0iEIRMYV/7oCZUMf27/383E6Wyhfc/MY+AVQGEoUobffIYWOK9YLP2XFRGz/9i6WlTQh1CkFVIo2Y7XA=="], + + "@inquirer/core": ["@inquirer/core@11.1.8", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", "@inquirer/type": "^4.0.5", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-/u+yJk2pOKNDOh1ZgdUH2RQaRx6OOH4I0uwL95qPvTFTIL38YBsuSC4r1yXBB3Q6JvNqFFc202gk0Ew79rrcjA=="], + + "@inquirer/figures": ["@inquirer/figures@2.0.5", "", {}, "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ=="], + + "@inquirer/type": ["@inquirer/type@4.0.5", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q=="], + + "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.0.3", "", { "dependencies": { "content-type": "^1.0.5", "raw-body": "^3.0.0", "zod": "^3.23.8" } }, "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@mswjs/interceptors": ["@mswjs/interceptors@0.41.4", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-3B9EinUkrdOUGYzHRzRWSXunQ4YFGboJnyLNRwEJWEde+j8fNhPUHvrN1E3g1DU/iS/s8JQrMNVe+S7AHHVs0w=="], + + "@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="], + + "@open-draft/deferred-promise": ["@open-draft/deferred-promise@3.0.0", "", {}, "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA=="], + + "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], + + "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@playwright/test": ["@playwright/test@1.59.1", "", { "dependencies": { "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" } }, "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.2", "", { "os": "none", "cpu": "arm64" }, "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="], + + "@stargue/admin": ["@stargue/admin@workspace:apps/admin"], + + "@stargue/linkedin-client": ["@stargue/linkedin-client@workspace:packages/linkedin-client"], + + "@stargue/mcp-linkedin": ["@stargue/mcp-linkedin@workspace:apps/mcp-linkedin"], + + "@stargue/observability": ["@stargue/observability@workspace:packages/observability"], + + "@stargue/sanitize": ["@stargue/sanitize@workspace:packages/sanitize"], + + "@stargue/scheduler": ["@stargue/scheduler@workspace:apps/scheduler"], + + "@stargue/schema": ["@stargue/schema@workspace:packages/schema"], + + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + + "@turbo/darwin-64": ["@turbo/darwin-64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg=="], + + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw=="], + + "@turbo/linux-64": ["@turbo/linux-64@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA=="], + + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g=="], + + "@turbo/windows-64": ["@turbo/windows-64@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g=="], + + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/set-cookie-parser": ["@types/set-cookie-parser@2.4.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw=="], + + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], + + "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="], + + "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="], + + "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="], + + "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="], + + "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "axe-core": ["axe-core@4.11.3", "", {}, "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bullmq": ["bullmq@5.74.1", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.10.1", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.4", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-GfJEos2zoOGM9xqkB7VZouwwFuejKFqm667cBcmbBekJXKqqXWk4QYP3Uy2pzgUwCbg1cR7GgGmGczM7fnhWSA=="], + + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "drizzle-kit": ["drizzle-kit@0.28.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ=="], + + "drizzle-orm": ["drizzle-orm@0.36.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + + "graphql": ["graphql@16.13.2", "", {}, "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig=="], + + "headers-polyfill": ["headers-polyfill@5.0.1", "", { "dependencies": { "@types/set-cookie-parser": "^2.4.10", "set-cookie-parser": "^3.0.1" } }, "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "libsodium-sumo": ["libsodium-sumo@0.7.16", "", {}, "sha512-x6atrz2AdXCJg6G709x9W9TTJRI6/0NcL5dD0l5GGVqNE48UJmDsjO4RUWYTeyXXUpg+NXZ2SHECaZnFRYzwGA=="], + + "libsodium-wrappers-sumo": ["libsodium-wrappers-sumo@0.7.16", "", { "dependencies": { "libsodium-sumo": "^0.7.16" } }, "sha512-gR0JEFPeN3831lB9+ogooQk0KH4K5LSMIO5Prd5Q5XYR2wHFtZfPg0eP7t1oJIWq+UIzlU4WVeBxZ97mt28tXw=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msgpackr": ["msgpackr@1.11.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "msw": ["msw@2.13.4", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.7", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-fPlKBeFe+8rpcyR3umUmmHuNwu6gc6T3STvkgEa9WDX/HEgal9wDeflpCUAIRtmvaLZM2igfI5y1bZ9G5J26KA=="], + + "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], + + "node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], + + "playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], + + "postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "rettime": ["rettime@0.11.7", "", {}, "sha512-DoAm1WjR1eH7z8sHPtvvUMIZh4/CSKkGCz6CxPqOrEAnOGtOuHSnSE9OC+razqxKuf4ub7pAYyl/vZV0vGs5tg=="], + + "rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], + + "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + + "tldts": ["tldts@7.0.28", "", { "dependencies": { "tldts-core": "^7.0.28" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw=="], + + "tldts-core": ["tldts-core@7.0.28", "", {}, "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "turbo": ["turbo@2.9.6", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.6", "@turbo/darwin-arm64": "2.9.6", "@turbo/linux-64": "2.9.6", "@turbo/linux-arm64": "2.9.6", "@turbo/windows-64": "2.9.6", "@turbo/windows-arm64": "2.9.6" }, "bin": { "turbo": "bin/turbo" } }, "sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg=="], + + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="], + + "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], + + "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "vite/postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + } +} diff --git a/db/migrations/README.md b/db/migrations/README.md new file mode 100644 index 0000000..768cafb --- /dev/null +++ b/db/migrations/README.md @@ -0,0 +1,38 @@ +# Database migrations — runbook + +**Authoring rule:** every schema change follows **expand-then-contract** sequencing. A breaking change is split into ≥2 migrations with a rollback point between them. + +## Pattern + +| Phase | Migration files | What it does | Rollback | +|---|---|---|---| +| 1. Expand | `NNNN_expand_.sql` | Add new nullable column / new table / new index. Old code keeps working. | Drop the new addition. | +| 2. Backfill | `NNNN_backfill_.sql` (if data migration needed) | Populate the new column from existing data. Idempotent. | Re-run or delete column. | +| 3. Constrain | `NNNN_constrain_.sql` | Add NOT NULL / UNIQUE / FK / CHECK now that data is populated. | Drop the constraint. | +| 4. Contract | `NNNN_contract_.sql` | Remove the old column / table once no code reads it. **Only after ≥1 deploy cycle of the new code.** | Restore from `pg_dump`. | + +## Mandatory for every migration + +- `migrations/NNNN_.sql` — the forward migration. +- `migrations/NNNN_.down.sql` — the rollback SQL. +- An entry below in the **Registry** with rationale, linked plan, and rollback cost estimate. + +## Registry + +| # | Date | Name | Plan | Forward cost | Rollback cost | Notes | +|---|---|---|---|---|---|---| +| — | — | — | — | — | — | No migrations yet — first migration lands in Stage 2.1 | + +## Rehearsal + +Stage 2.2 DoD: every schema change is rehearsed end-to-end on staging before production. Command: + +```bash +bun run db:migrate:rehearse +``` + +Rehearsal must cover: forward → backfill (if applicable) → constrain → contract, with a row-count + row-hash check at each step. + +## Incidents + +Any migration incident gets a post-mortem in `docs/incidents/YYYY-MM-DD-migration-.md`. diff --git a/docs/deferred-gates.md b/docs/deferred-gates.md new file mode 100644 index 0000000..f9ee9ea --- /dev/null +++ b/docs/deferred-gates.md @@ -0,0 +1,27 @@ +# Deferred gates + +Items that could not be completed in the Stage 0 execution window and must be resolved before the gate they block. + +## Gate 0.8 — Postgres + Redis on PaaS + +**Status:** Deferred 2026-04-19. +**Reason:** Dokploy MCP tools (`mcp__dokploy__*`) were not loaded in the Stage 0 session, and `DOKPLOY_API_KEY` was not in the WSL environment. Direct API query not possible in-session. +**Blocks:** Stage 2 (database + API) — the scheduler and admin cannot run without a Postgres + Redis pair. +**Resolution options:** +1. **Recommended:** Restart Claude Code with the vault's `.mcp.json` Dokploy MCP attached; run `mcp__dokploy__list_applications` / equivalent against `sg-paas-s1.stargue.net` to verify or provision. +2. Angelo confirms via Dokploy web UI and reports service names + connection hostnames. +3. Provision fresh Postgres + Redis services in the `stargue-publishing-engine` Dokploy stack (clean room; isolates blast radius from other PaaS apps). + +**Acceptance when resolved:** this file updated with service names, hostnames, and connection-string env-var locations (Dokploy secrets). + +## Gate 0.9 — LinkedIn Developer Portal apps + +**Status:** Pending Angelo's manual action. +**Reason:** Dev Portal registration cannot be automated — requires human auth at `linkedin.com/developers/`. +**Blocks:** Stage 3.1 (OAuth flow) live integration — structure + contract tests can proceed in parallel. +**Resolution:** follow the checklist in [`linkedin-apps.md`](./linkedin-apps.md). Expected time ~30 min for OIDC + Share; multi-week for Community Management API + MDP partner approvals. +**Acceptance when resolved:** `linkedin-apps.md` registry filled in with app IDs; client credentials stored in Dokploy secrets `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_TOKEN_ENCRYPTION_KEY`. + +## What proceeds in parallel + +Stages 1–2 (local DB), 3 structural (OAuth code + tool shapes + msw fixtures), 4 (scheduler), 5 (admin UI with Authentik stub), 6 (sanitize integration in stargue-com/.net), 7.1 (`/market audit` baseline), 7.2 (7 Cs post drafting) can all proceed without these gates. Live integration tests and first production publish require both gates resolved. diff --git a/docs/linkedin-apps.md b/docs/linkedin-apps.md new file mode 100644 index 0000000..5efcaa9 --- /dev/null +++ b/docs/linkedin-apps.md @@ -0,0 +1,46 @@ +# LinkedIn Developer Portal — Application Registry + +**Status tracker for Stargue's LinkedIn apps.** Every row must stay current — Claude Code verifies against this file during Stage 0 DoD 0.9. + +## Applications + +| App | Product | Purpose | Scopes granted | State | App ID | Approval date | Notes | +|---|---|---|---|---|---|---|---| +| Stargue Publishing Engine — Sign In | Sign In with LinkedIn using OpenID Connect | Identity / `whoami` | `openid profile email` | **TODO: register** | — | — | Standard — no approval required beyond Dev Portal self-service | +| Stargue Publishing Engine — Member Share | Share on LinkedIn | Personal-profile posts (Angelo) | `w_member_social` | **TODO: register** | — | — | Standard — attached to the same app as Sign In | +| Stargue Publishing Engine — Org Share | Community Management API | Company-page posts (`urn:li:organization:2605890`) | `w_organization_social`, `r_organization_social` | **TODO: apply** | — | — | Requires LinkedIn partner approval; submit via Dev Portal "Request access" | +| Stargue Publishing Engine — Refresh | Marketing Developer Platform (MDP) | Programmatic refresh tokens (eliminates 60-day re-auth) | (extends above) | **TODO: apply** | — | — | Requires LinkedIn partner approval; multi-week review | + +## OAuth redirect URIs + +- Production: `https://publishing.stargue.net/auth/linkedin/callback` +- Local dev: `http://localhost:3002/auth/linkedin/callback` + +## Client credentials + +Stored in Dokploy secrets on `sg-paas-s1.stargue.net`: +- `LINKEDIN_CLIENT_ID` +- `LINKEDIN_CLIENT_SECRET` +- `LINKEDIN_TOKEN_ENCRYPTION_KEY` (32-byte key for `crypto_secretbox`) + +## Manual setup checklist (Stage 0.9) + +1. Sign in to `https://www.linkedin.com/developers/` as Angelo (personal account). +2. Create app "Stargue Publishing Engine" linked to Stargue Company Page `urn:li:organization:2605890` (required for Community Management API eligibility). +3. Under **Products**, request: + - Sign In with LinkedIn using OpenID Connect (self-service; instant) + - Share on LinkedIn (self-service; instant) + - Community Management API (submit partner-access request — fill in use-case: "Programmatic posting of original content to Stargue Company Page for knowledge-management-as-a-service practice") + - Marketing Developer Platform (separate partner-access request — fill in use-case: "Automate posting and engagement-metric collection for owned content from a self-hosted publishing pipeline") +4. Under **Auth**, add redirect URIs above. +5. Copy Client ID and Client Secret to Dokploy secrets (never commit). +6. Update this file with the app ID. +7. Re-request Community Management API + MDP if denied initially, with more detailed use-case. + +## References + +- [LinkedIn Developer Portal](https://www.linkedin.com/developers/) +- [Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api) +- [3-Legged OAuth Flow](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow) +- [Programmatic Refresh Tokens](https://learn.microsoft.com/en-us/linkedin/shared/authentication/programmatic-refresh-tokens) +- [Community Management API overview](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/) diff --git a/docs/plans/2026-04-19-phase1-plan.md b/docs/plans/2026-04-19-phase1-plan.md new file mode 100755 index 0000000..2400f1d --- /dev/null +++ b/docs/plans/2026-04-19-phase1-plan.md @@ -0,0 +1,397 @@ +--- +created: 2026-04-19T09:45 +updated: 2026-04-19T10:45 +tags: +--- + +# Plan — Phase 1 Automated Publishing Engine (with LinkedIn MCP Server) + +**Revision:** v2 (consolidated from 7-agent BMAD panel review 2026-04-19) +**Verdicts file:** `/tmp/bmad-panel-verdicts-publishing-engine-phase1-20260419.json` (WSL) +**Panel result:** 1 APPROVE, 6 REVISE, 0 REJECT — all principles ≥3 (gate passes). Revisions below address each REVISE gap, citing originating agent. + +**Applicability:** tier-2 = [RBR, A11y, Testability] +**Precedence:** `CLAUDE.md > Conflict Resolution Precedence` + adopted doctrine at `/home/devuser/projects/dqms/.clinerules/12-foundational-principles.md` (user directive 2026-04-19) + +--- + +## 1. Context + +User directive 2026-04-19: **Full Phase 1 automation, now.** No manual posting, no postponing. LinkedIn programmatic refresh is gated to MDP partners per [Microsoft Learn > Programmatic Refresh Tokens (retrieved 2026-04-19)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/programmatic-refresh-tokens). Non-MDP apps must re-auth every ≤60 days; we accept this as the honest fallback and file MDP in parallel. + +Existing state (verified 2026-04-19): +- `stargue-com` and `stargue-net` both on Next.js 16 + React 19 + Bun + Tailwind v4 + remark (verified against `package.json` per **dev** agent). Note: **stargue-net/package.json is a copy of stargue-com** (same `name` field) — **name collision to resolve before workspace linking** (dev gap 5). +- Listmonk email capture + CTA + content protection already shipped on stargue.com. +- Dokploy on `sg-paas-s1.stargue.net`; Gitea at `git.stargue.net`; OpenObserve observability stack. +- Stargue LinkedIn Company Page: `urn:li:organization:2605890` (user-confirmed 2026-04-19). +- Authentik SSO: user-confirmed running; all Stargue services share the same instance. + +**Capacity reconciliation (PM/SM gap).** PRD §6.5 caps Angelo at 4h/week. The user has explicitly overridden this cap for this Phase 1 build — "no postponing, fully functional now." Plan is re-expressed below as **concentrated full-time build (14 calendar days)** with the user accepting timeline risk explicitly. If the user later reverts to the 4h/week cap, Stages 2–7 re-expand to ~15 weeks at 4h/week. + +--- + +## 2. Objectives + +1. **Automated two-channel publish pipeline** — Obsidian vault → stargue.com/.net (git push → Dokploy) + LinkedIn (personal profile `angeloluidens` + company page `urn:li:organization:2605890`) via a **Stargue LinkedIn MCP Server**. +2. **Optimized cadence** — bootstrap Tue + Thu 08:30 AM AST; adaptive refinement after data accrues (≥8 samples per (day,hour) slot), proposed-not-auto-applied. +3. **Fail-closed content sanitization** — wikilink/Obsidian strip + private-path blocklist + tag firewall + per-outlet length validation. +4. **Admin dashboard** — approval queue, side-by-side outlet preview, per-outlet status, scheduled-publish controls, metrics heatmap. +5. **PostgreSQL content registry** (PaaS) — SSoT for publication state, idempotency, audit; nightly pg_dump → Neon free tier for DR. +6. **OAuth 2.0 handled honestly** — 60-day re-auth fallback with proactive T-5-day notification + fail-safe halt; MDP app filed in parallel. +7. **Revenue conversion preserved** (PM gap). Phase 1 must not regress the already-shipped revenue surfaces on stargue.com (Listmonk email capture, CTAs, service page, Terms of Use). New inbound-inquiry tracking column on `publications.metadata_jsonb.inquiries` (counter incremented when a contact-page submission cites the post's slug as referrer). +8. **Kill-switch** — per-outlet feature flag (`OUTLET_ENABLED:linkedin=false`) readable at runtime without redeploy, toggled via admin dashboard. TEA gap 10. + +--- + +## 3. Architecture + +### 3.1 Repository topology (simplified per **architect** gap 1) + +Collapsed `apps/api` into `apps/admin` Next.js App Router route handlers. Scheduler imports `linkedin-client` directly as a library; MCP transport is **stdio only** (for Claude Code use); HTTP transport deferred until a real cross-service need emerges (architect gap 2). + +``` +stargue-publishing-engine/ +├── apps/ +│ ├── mcp-linkedin/ MCP server (stdio transport, TypeScript/Bun) +│ ├── scheduler/ BullMQ worker; imports packages/linkedin-client directly +│ └── admin/ Next.js App Router (UI + /api/* route handlers) +├── packages/ +│ ├── sanitize/ remark plugin + blocklist + length validation + test corpus +│ ├── schema/ Zod schemas, Drizzle schema, frontmatter parser (SSoT) +│ ├── linkedin-client/ LinkedIn Posts API client + encrypted token store +│ └── observability/ Pino + OTel + correlation IDs +├── infra/ +│ ├── docker-compose.yml Local dev (postgres + redis + openobserve) +│ └── dokploy/ Service definitions +├── db/migrations/ Drizzle expand-then-contract migrations +├── .clinerules/ Adopted from dqms +├── .claude/ +│ ├── hooks/gate-plan-exit.sh +│ └── skills/bmad-plan/SKILL.md +├── test/corpus/private/ 12-file private-vault fixture (sanitizer test SSoT) — SM gap 2 +├── docs/plans/ Filed plans +├── CLAUDE.md +├── package.json Bun workspaces +└── turbo.json +``` + +### 3.2 LinkedIn API — corrected per **dev** agent + +**Endpoint (dev gap 3):** Posts API `https://api.linkedin.com/rest/posts` — NOT legacy `/v2/ugcPosts`. Required headers: `LinkedIn-Version: 202404` (or current YYYYMM per release cadence), `X-Restli-Protocol-Version: 2.0.0`, `Authorization: Bearer `. Request shape uses flat `content` + `distribution` (not nested `specificContent/com.linkedin.ugc.ShareContent`). + +**Auth flow (dev gap 2):** OAuth 2.0 Authorization Code flow — **no PKCE** (LinkedIn 3LO does not document PKCE support per [Microsoft Learn > 3-Legged OAuth Flow](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow), retrieved 2026-04-19). We use confidential-client with `client_secret` + CSRF-protected `state` parameter. + +**Products required in LinkedIn Dev Portal (dev gap 1):** +| Product | Scopes granted | Purpose | Approval gate | +|---|---|---|---| +| **Sign In with LinkedIn using OpenID Connect** | `openid profile email` | Identity | Standard | +| **Share on LinkedIn** | `w_member_social` | Personal-profile posts | Standard | +| **Community Management API** | `w_organization_social`, `r_organization_social` | Company-page posts | **Requires LinkedIn partner approval — separate from MDP, non-trivial gate** | +| **Marketing Developer Platform (MDP)** — aspirational | Programmatic refresh tokens | Eliminate 60-day re-auth | Multi-week review | + +**Impact:** Company Page posting (Objective 1, personal + `org:2605890`) may be blocked pending Community Management API approval. Personal-profile posting unblocked immediately. We file Community Management API application in Stage 0.5 and build the tools so they're ready when approved. + +**Token store (architect gap 3):** Encryption primitive corrected to **libsodium `crypto_secretbox`** (symmetric XSalsa20-Poly1305) with a 32-byte key stored in Dokploy secrets. Sealed-box was wrong for at-rest DB encryption. Alternative considered: KMS envelope encryption via age-based encryption — deferred (no KMS in Stargue PaaS yet; secretbox+secret is adequate for Phase 1 blast radius). + +**Refresh concurrency (TEA gap 3):** Token rotation path acquires a Postgres advisory lock keyed on `hashtextextended(subject_urn, 0)` before reading/refreshing. Lock released after commit. Prevents double-rotation race. + +**Idempotency (dev gap 4, TEA gap 5):** `publications` table has `idempotency_key TEXT UNIQUE NOT NULL` populated as `sha256(content_id || outlet || scheduled_at)`. LinkedIn handler refuses to dispatch if a `publications` row with `external_id IS NOT NULL` already exists for the key. BullMQ jobs also carry an `idempotency_key` to dedupe retries before they reach the handler. + +**Rate limits (architect gap 4).** LinkedIn does not publicly document a fixed per-member posting cap. We impose **self-imposed conservative limits** (not LinkedIn quota): 20 posts/day/member, 50 posts/day/org. Stage 3 DoD asserts this via a test (TEA gap 6). + +**MCP tools exposed (stdio transport, no HTTP):** +| Tool | Purpose | +|---|---| +| `linkedin_whoami` | Returns authenticated subject + scopes + MDP-eligible flag | +| `linkedin_auth_status` | Access-expiry remaining, refresh-expiry remaining, kill-switch state | +| `linkedin_create_post` | Text post via Posts API (`author_urn` param: `urn:li:person:` or `urn:li:organization:2605890`) | +| `linkedin_create_article` | Long-form article (≤125k chars) | +| `linkedin_upload_media` | Image / PDF carousel; returns asset URN | +| `linkedin_create_post_with_media` | Combines upload + post | +| `linkedin_delete_post` | Delete post by URN (soft-reversible within 5-min edit window only; after that, delete-only) | +| `linkedin_get_post_metrics` | Impressions/reactions/comments/shares/clicks for a URN | +| `linkedin_get_profile_stats` | Aggregate profile stats for cadence optimizer | + +### 3.3 Sanitization pipeline + +`packages/sanitize` — Unified/remark plugin chain: +1. Strip wikilinks `[[Path/Note|Display]]` → `Display` or plain removal. +2. Strip embeds `![[file]]` → resolve (blog) or remove (LinkedIn). +3. Strip dataview blocks. +4. Strip callouts. +5. **Private-path blocklist** — `Family Matters/`, `Financial Matters/`, `Journal/`, `Clients/*[NDA]/`, extensible. Match triggers **build error**, not silent strip. +6. **Tag firewall** — `#private`, `#heal-internal`, `#confidential` trigger build error. +7. Per-outlet length validation (LinkedIn 3k / article 125k / Twitter 280 / etc.). +8. Idempotency hash output (`content_hash`) over sanitized body for dedupe. + +**Test corpus (SM gap 2, TEA gap 8):** `test/corpus/private/*` contains 12 fixture files — 4 blocklist-path, 4 tag-firewall, 4 length-overflow. Stage 1 DoD: **"CI passes when all 12 round-trip with build errors AND all reference clean files round-trip without errors."** Promoted from Risks to Stage 1 DoD. + +### 3.4 Content registry (Postgres) + DR + +Drizzle schema in `packages/schema/db.ts`: + +- `content(id, vault_path, slug, title, body_sanitized, frontmatter_jsonb, content_hash, version, created_at, updated_at)` +- `publications(id, content_id, outlet, status, scheduled_at, published_at, external_id, external_url, idempotency_key UNIQUE, error, metadata_jsonb)` +- `approvals(id, content_id, outlet, approved_by, approved_at, notes)` +- `metrics(id, publication_id, collected_at, impressions, reactions, comments, shares, clicks, raw_jsonb)` +- `linkedin_tokens(id, subject_type, subject_urn, access_token_ct, refresh_token_ct, access_expires_at, refresh_expires_at, scopes, created_at, updated_at)` +- `audit(id, ts, actor, action, subject_type, subject_id, correlation_id, payload_jsonb)` — append-only +- `outlet_feature_flags(outlet, enabled, reason, updated_at, updated_by)` — kill-switch (Objective 8) + +**Migrations (architect gap 7, TEA gap 4):** Every schema change follows **expand-then-contract**. Example for idempotency_key rollout: (1) add column nullable, (2) backfill via one-time script with SELECT … UPDATE … WHERE, (3) `NOT NULL` + `UNIQUE` constraint added in a second migration, (4) contract legacy column in a third migration once no code writes it. Each migration accompanied by a runbook in `db/migrations/README.md`. **Stage 2 DoD:** "Migration rehearsal on staging DB captures full expand→backfill→contract sequence with rollback point documented." + +**DR mirror:** nightly cron runs `pg_dump --format=c` streamed to Neon free tier via Neon connection string in Dokploy secrets. Retains only last 30 days of metrics on the mirror (respects Neon 0.5 GB free cap). RPO ~24h. + +### 3.5 Admin dashboard + A11y (UX gaps 1–8) + +Next.js App Router. Authentik SSO (user-confirmed instance). Routes also host `/api/*` handlers (architect gap 1 collapse). + +**Pages:** +- `/queue` — scheduled + pending items, side-by-side outlet preview, approval CTA +- `/publications/:id` — per-outlet status, metrics, errors +- `/auth/linkedin` — OAuth flow + re-auth link + kill-switch toggle +- `/metrics` — heatmap (day × hour × outlet) + +**Nine Laws temperature declaration (UX gap 5):** Admin dashboard is an **operator tool**, not a reader surface. It inhabits the **cool / stargue.net (forge) temperature**. Forest-green (#4E8211) + Citrus (#99CC00) accents. **Amber (#F59E0B-family) reserved for Hearth content only — never leaks into admin status badges** (Law 4). Status badges use Forest/slate/red variants. + +**A11y DoDs (UX gaps 1–4, 6–8; dev gap A11y; TEA gap 7):** +- Axe-core ruleset pinned to `axe-core@4.x`, severity gate: **zero `serious` or `critical` violations** on every route (SM gap 5). +- WCAG 2.2 AA contrast verification via script that reads design-token file, asserts each FG/BG pair ≥4.5:1 body and ≥3:1 large-text/non-text-UI on **both themes**. +- Screen-reader smoke test in CI: `@axe-core/playwright` + NVDA scripting via GitHub Actions on Windows runner for the approval flow; manual VoiceOver pass in release checklist. +- `aria-live="polite"` on queue status transitions; `aria-live="assertive"` on publish errors only. Announcement text specified per surface. +- Error association: every form input has `aria-describedby` → error-message container; WCAG 3.3.1/3.3.3/4.1.3 complied. +- Focus trap on modal approval confirmations; skip-link on dashboard; `prefers-reduced-motion` honored for section fade-in (Law 8.4). +- `/metrics` heatmap: **color + text redundancy** — each cell shows `HH:MM — N events` text on hover and in the cell body (not color-only). WCAG 1.4.1 satisfied. + +### 3.6 Cadence optimizer (Analyst gap 3) + +**Bootstrap:** Tue + Thu, 08:30 AM AST, user-confirmed. + +**Adaptive (spec'd precisely):** +- Model: Beta posterior over engagement rate (reactions+comments+shares ÷ impressions) per (day_of_week, hour_of_day) bucket. +- Prior: `Beta(1, 19)` — weakly informative, centered on 5% baseline engagement to avoid spurious early moves. +- Sample threshold: **≥8 posts per bucket AND ≥4 weeks of data** before the optimizer proposes a change. +- Change trigger: posterior mean of an alternate bucket exceeds current bucket's posterior mean by ≥20% AND the 95% credible intervals do not overlap. +- Output: **proposal in `/metrics` UI with "Approve", "Reject", "Snooze 4 weeks"** — never auto-applied. Human-in-the-loop (RBR). +- Seams: optimizer is a pure function `propose(history, config) → [Proposal]` in `packages/schema`; clock/RNG injected; unit-tested with synthetic history. + +### 3.7 Deployment + kill-switch + +Services on Dokploy stack `stargue-publishing-engine`: +- `admin` — Next.js standalone, port 3002, Traefik `publishing.stargue.net`, Authentik SSO +- `scheduler` — BullMQ worker, no ingress +- `mcp-linkedin` — MCP stdio, container runs only when Claude Code connects via `docker exec` +- `postgres` — **confirm via Dokploy MCP at Stage 0 DoD** (architect gap 5). If no existing cluster, provision a new stack service. +- `redis` — same confirmation path + +**Kill-switch (Objective 8, TEA gap 10):** `outlet_feature_flags` table read by scheduler + MCP server on every dispatch. Toggle via admin dashboard at `/admin/feature-flags`. Flip takes effect within 30s without redeploy. Audit-logged. + +**CSRF (architect gap 8):** Admin `/api/*` POST/DELETE handlers use double-submit cookie pattern via `@edge-csrf/nextjs` or equivalent. Authentik session cookie anchors identity; CSRF token anchors same-origin. + +--- + +## 4. Stages + measurable DoD (all "works" language tightened per SM/TEA gaps) + +### Stage 0 — Governance + gate resolution (day 1) + +| Step | Measurable DoD | +|---|---| +| 0.1 Create Gitea repo | `GET https://git.stargue.net/api/v1/repos/admin/stargue-publishing-engine` returns 200 with `empty=false` | +| 0.2 Install `.clinerules/` from dqms | `diff -q .clinerules/12-foundational-principles.md /home/devuser/projects/dqms/.clinerules/12-foundational-principles.md` reports no difference | +| 0.3 Install `.claude/hooks/gate-plan-exit.sh` | Hook file present with exec perms; `bash -n .claude/hooks/gate-plan-exit.sh` parses clean | +| 0.4 Install `.claude/skills/bmad-plan/SKILL.md` | File SHA256 matches dqms SKILL.md | +| 0.5 Project CLAUDE.md | File exists and names: vault-as-SSoT, precedence, commands, outlet URNs | +| 0.6 Bun workspaces + Turbo bootstrap | `bun install` exit 0; `bun run typecheck` exit 0 in empty state | +| **0.7 Gates answered** (SM gap 7) | §11 Open Questions each have "Answered: " row in `docs/plans/2026-04-19-phase1-plan.md` | +| 0.8 Verify Postgres + Redis cluster on PaaS | Dokploy MCP query returns running service names OR new services provisioned and listed | +| 0.9 File LinkedIn Dev Portal products | Apps registered: "Sign In + OIDC", "Share on LinkedIn", "Community Management API" (pending approval), "MDP" (pending approval). Screenshot URLs recorded in `docs/linkedin-apps.md` | +| 0.10 Resolve stargue-net/package.json name collision (dev gap 5) | `stargue-net/package.json` has `"name": "stargue-net"` (distinct from stargue-com) | + +### Stage 1 — Shared packages (days 2–3) + +| Step | Measurable DoD | +|---|---| +| 1.1 `packages/schema` | 100% line coverage via Vitest; Zod round-trip test for frontmatter with 6 valid + 6 invalid cases | +| 1.2 `packages/sanitize` | **CI passes when 12 `test/corpus/private/*` fixtures round-trip with build errors AND 6 clean-corpus fixtures round-trip without errors** (SM gap 2) | +| 1.3 `packages/observability` | Pino JSON output includes `correlation_id`, `subject`, `level`, `msg`; validated via JSON-schema check on 100 sample logs | +| 1.4 `packages/linkedin-client` | Fake client passes contract-test suite using [`msw`](https://mswjs.io/) (chosen recorder, dev gap 7) against recorded Posts API fixtures | + +### Stage 2 — Database + core API (days 3–4) + +| Step | Measurable DoD | +|---|---| +| 2.1 Drizzle expand-only migrations | `drizzle-kit migrate` applies all tables on empty DB; each migration has a rollback query recorded in `db/migrations/README.md` | +| 2.2 **Migration rehearsal** (TEA gap 4) | Idempotency_key rollout rehearsed end-to-end on staging: expand→backfill→constraint→contract; runbook committed | +| 2.3 Admin `/api/content`, `/api/approvals`, `/api/publications` handlers | Each route: Zod parse → Authentik session → Cerbos-equivalent authz check → DB write with correlation ID. Vitest integration tests assert 401/403/200/422 for each route | +| 2.4 Rate limit on `/api/content` POST | `express-rate-limit`-equivalent middleware; burst test asserts HTTP 429 on request #21/min (TEA gap 6) | +| 2.5 CSRF middleware | Double-submit cookie test: cross-origin POST returns 403 | + +### Stage 3 — LinkedIn MCP server (days 4–7) + +| Step | Measurable DoD | +|---|---| +| 3.1 OAuth auth-code flow (no PKCE, CSRF-state, personal) | Manual round-trip: auth URL → callback → token row persisted encrypted → `linkedin_whoami` returns subject | +| 3.2 OAuth for Company Page (pending Community Management API approval) | Same flow w/ `w_organization_social`; if API approval pending, test skipped with recorded pending-state row in `docs/linkedin-apps.md` | +| 3.3 Tools `linkedin_whoami`, `linkedin_auth_status` | Contract tests assert exact response shape against msw-recorded fixtures | +| 3.4 Tool `linkedin_create_post` (personal) | Live integration test against **a throwaway test post**. DoD: "Post URN persisted to `publications.external_id`; status=published; **test post deleted within 5-min edit window; deletion audited in `audit` table**" (SM gap 3) | +| 3.5 Tool `linkedin_upload_media` + `linkedin_create_post_with_media` | Image upload returns asset URN; PDF carousel post persisted; test posts deleted as 3.4 | +| 3.6 Tool `linkedin_create_article` (personal) | Article URN persisted; test article deleted | +| 3.7 Tool `linkedin_get_post_metrics` + `linkedin_get_profile_stats` | Metrics row written to `metrics` table; contract-tested | +| 3.8 Refresh path with advisory lock + concurrent-access test | **Parallel test fires 4 concurrent rotate attempts on an expired token; exactly 1 rotation succeeds, 3 retry on the new token** (TEA gap 3) | +| 3.9 Fail-safe halt on auth expiry | Forced-expiry test: `outlet_feature_flags.linkedin.enabled` auto-flips false; Telegram webhook receives notification; scheduler halts dispatch | +| 3.10 Kill-switch toggle via admin | Manual test: flip via UI → flag updated within 30s → next dispatch attempt refuses with audit row (TEA gap 10) | +| 3.11 Rate-limit self-cap | Burst test: 21 posts/min from same subject → 21st rejected with internal 429 + audit row | +| 3.12 MCP stdio transport wiring | Claude Code `docker exec mcp-linkedin node dist/server.js` responds to `initialize` + `tools/list` per MCP spec `2024-11-05` (pinned) | + +### Stage 4 — Scheduler (day 7–8) + +| Step | Measurable DoD | +|---|---| +| 4.1 BullMQ queue + Redis | `bun test` integration asserts: enqueue → worker-consume → DB row updated within 5s | +| 4.2 End-to-end publish loop | **Trace test: POST /api/content → approval → scheduled_at reached → git commit SHA recorded + LinkedIn URN recorded in `publications`, status=published, within 120s** (SM gap 4) | +| 4.3 DLQ + retry + idempotency | Chaos test: kill LinkedIn fake mid-publish → retry with same `idempotency_key` → no duplicate post (TEA gap 5) | +| 4.4 Cadence optimizer integration | Feature flag default OFF; unit tests assert proposals with ≥8 samples + ≥4 weeks threshold; no proposal below threshold | + +### Stage 5 — Admin dashboard (days 8–10) + +| Step | Measurable DoD | +|---|---| +| 5.1 Scaffold + Nine Laws tokens | Design tokens file matches Nine Laws doc via script diff; Lighthouse Performance ≥90, Accessibility ≥95 on `/queue` | +| 5.2 All routes authz-gated by Authentik | Playwright test: unauthenticated request → 302 to login; wrong role → 403 | +| 5.3 A11y gate | **Zero `serious` or `critical` axe-core violations on every route; contrast-check script exits 0 on both themes; @axe-core/playwright SR smoke passes the approval flow** (UX gaps 2, 3) | +| 5.4 Manual A11y checklist | Keyboard-only walkthrough passes; VoiceOver pass on approval flow recorded in `docs/a11y-checklist.md`; focus-trap on modals verified | +| 5.5 Heatmap pattern+text redundancy | Axe check + manual verification: heatmap cells render text + color, not color-only (UX gap 6) | + +### Stage 6 — stargue-com/.net integration (day 10) + +| Step | Measurable DoD | +|---|---| +| 6.1 Add `packages/sanitize` + `packages/schema` as workspace deps | `stargue-com` and `stargue-net` `bun run build` both succeed; rendered HTML contains zero wikilinks (assertion: `grep -c '\[\[' dist/**/*.html` = 0) | +| 6.2 Replace ad-hoc frontmatter parsing | Diff: no occurrences of ad-hoc parser; all frontmatter flows through `packages/schema` parser | + +### Stage 7 — Content + metrics bootstrap (days 11–14) + +| Step | Measurable DoD | +|---|---| +| 7.1 `/market audit` baseline | Baseline score captured in `docs/marketing-baseline-2026-04.json` | +| 7.2 Draft 7 Cs posts 2–8 (long + LinkedIn variants) | **14 files committed in `stargue-com/src/content/blog/7cs/` with schema-valid frontmatter; each passes autoresearch optimization loop with composite ≥8** | +| 7.3 File MDP + Community Management API apps | Application IDs recorded in `docs/linkedin-apps.md` | +| 7.4 First scheduled post lands on stargue.com + LinkedIn | **`publication.external_url` returns HTTP 200 from unauthenticated curl; stargue.com blog route returns HTTP 200; metrics row collected at T+48h±1h** (SM gap 6) | +| 7.5 DR test | pg_dump to Neon succeeds and restores to verifiable row count | + +--- + +## 5. Principle adherence — revised post-panel + +| Principle | v1 target | v2 target | Change rationale | +|---|---|---|---| +| SEBP | 4 | 5 | Simpler topology (apps/api collapsed into admin), clearer boundaries; composition over separation. | +| SSoT | 5 | 5 | Unchanged (panel consensus). Name-collision fix in 0.10. | +| FPT | 4 | 5 | **Fixed:** PKCE dropped, Posts API (not UGC), secretbox (not sealed-box), Community Management API gate named, stargue-net naming, rate-limit framed as self-imposed, msw as contract-test recorder. All per dev + architect verification. | +| DiDSP | 5 | 5 | Idempotency_key + advisory lock + CSRF + kill-switch added. | +| PbD | 4 | 5 | Added DPIA note (§9); token retention explicit; SAR path DoD-tested via erasure integration test in Stage 2. | +| OF | 5 | 5 | Unchanged (panel consensus 5 on OF). | +| RBR | 4 | 5 | Kill-switch + migration rehearsal DoD + 5-min-edit-window rollback acknowledgement + test-post deletion in Stage 3 DoDs. | +| A11y | 4 | 5 | SR protocol, aria-live levels specified, error association, dual-theme contrast script, heatmap pattern+text, focus-trap, temperature declared, operator persona question added. | +| Testability | 4 | 5 | All DoDs measurable, msw recorder named, concurrency+rate-limit+idempotency tests specified, optimizer pure-function seam, Vitest+Bun setup included in Stage 1. | + +--- + +## 6. Critical files / paths (unchanged from v1 + additions) + +[Existing list preserved] + +Additions: +- `test/corpus/private/` — sanitizer test SSoT +- `docs/linkedin-apps.md` — Dev Portal app IDs + approval states +- `docs/a11y-checklist.md` — manual A11y checklist +- `db/migrations/README.md` — migration runbook (expand-then-contract sequences) +- `docs/marketing-baseline-2026-04.json` — `/market audit` baseline + +--- + +## 7. Verification (expanded per TEA gaps) + +- **Unit:** Vitest + Bun workspace. 100% line coverage on pure cores. +- **Integration:** real Postgres (Docker), real Redis, `msw` fakes for LinkedIn. Frozen clock via `@sinonjs/fake-timers`; seeded RNG. +- **Contract:** msw-recorded LinkedIn API exchanges at `test/fixtures/linkedin/*.har`. **Re-record policy: rotate fixtures monthly or when LinkedIn-Version header bumps** (TEA gap 2). Stale-fixture detector: fixture file mtime > 45 days → CI warning. +- **E2E:** Playwright + `@axe-core/playwright` + NVDA scripting on Windows GH Actions runner. +- **Security:** `bun audit` in CI; gitleaks pre-commit; Dependabot. +- **Load:** k6 at Phase 1 exit — 50 queued publishes drained within SLA. +- **DR:** weekly pg_dump-to-Neon rehearsal with row-count verification. + +--- + +## 8. Authorization gates — ANSWERED + +| # | Question | Answer (2026-04-19) | +|---|---|---| +| 1 | Stargue LinkedIn Company Page URL | `https://www.linkedin.com/company/2605890/` — URN `urn:li:organization:2605890` | +| 2 | Cadence bootstrap | Confirmed: Tue + Thu ~08:30 AM AST | +| 3 | Admin auth | Shared Authentik instance (same for all Stargue services) | +| 4 | MDP signatory | Angelo as individual; Stargue entity later | +| 5 | Postgres + Neon | PaaS cluster + nightly pg_dump to Neon free tier (RPO ~24h) | +| 6 | 4h/week cap override | User override: concentrated full-time build, timeline risk accepted | +| 7 | BMAD install location | DQMS charters referenced by path; no install in Publishing Engine repo for this phase | +| 8 | Primary operator persona + A11y context (UX gap 8) | **Option C (2026-04-19):** primary = Angelo (VoiceOver-capable); secondary = Claude Code agents (programmatic). A11y target: human-first WCAG 2.2 AA; agents inherit semantic markup for free. | + +--- + +## 9. Risks (expanded per panel) + +| Risk | Mitigation | Raised by | +|---|---|---| +| LinkedIn denies MDP | 60-day re-auth operational baseline; proactive T-5d notification | plan v1 | +| **LinkedIn denies Community Management API** | Personal-profile posting unblocked; org-posting tools built but gated until approval | dev | +| **LinkedIn algorithm suppresses automated-posted content reach** | Monitor engagement delta between automated vs manual-posted baselines for first 4 weeks; if suppression detected, fallback to draft-and-prompt workflow via admin | **analyst** | +| LinkedIn suspends app (ToS) | Conservative rate limit; approval on every publish; no messaging; no engagement automation; own-content only | v1 | +| Refresh token revoked mid-series | Fail-safe halt + Telegram notification within minutes | v1 | +| Refresh rotation race | Advisory lock on refresh path (TEA gap 3) | tea | +| Duplicate post via retry | Idempotency key on publications + BullMQ jobs (dev gap 4) | dev, tea | +| Dokploy outage during scheduled post | DLQ + exponential backoff + alert | v1 | +| Private vault leak via sanitizer gap | 3-layer fail-closed + CI corpus (sanitize gap 8) | v1, TEA | +| OneDrive sync conflict | Scheduler reads from Postgres, not OneDrive; checksum on ingest | v1 | +| Admin PII leak | No PII in dashboard; Authentik SSO; audit view | v1 | +| Bad LinkedIn post past 5-min edit window | Runbook: delete via `linkedin_delete_post` tool; audit; post-incident review documented in `docs/incidents/` (architect, SM) | architect, sm | +| Scope creep | Stages gated by measurable DoD; Phase 2 = separate plan | v1 | +| **4h/week → full-time override creates personal-capacity risk** | User-owned timeline risk; checkpoints at Stage 3, 5, 7 to re-evaluate (PM gap 1) | pm | +| **MDP + Community Management API approval timeline unknown** | Plan works without either; track application IDs in `docs/linkedin-apps.md` | pm | +| DPIA for OAuth-token processing of natural person (Angelo) | **DPIA recorded in `docs/dpia/linkedin-oauth.md`** — purpose (publication automation), data (encrypted tokens only), retention (lifetime-of-authorization), lawful basis (contract-performance + legitimate interest), user rights (erasure via token revoke + hard-delete row) (TEA gap PbD) | tea, sm | + +--- + +## 10. Out of scope (deferred to Phase 2 with separate plan) + +- Multilingual routing + hreflang (PAP/NL/ES) — F-021. **Tradeoff explicit:** 7 Cs is EN-only at launch; PAP-speaking stakeholders see EN during bootstrap. Phase 2 adds language-aware routing (Analyst + PM gaps). +- Medium, Substack, ResearchGate, X, Reddit, Telegram, TikTok, Facebook, WhatsApp, Newsletter-email outlets — F-013, F-033 +- Inlet Engine — F-030, F-031 (separate PRD) +- Cross-platform analytics beyond LinkedIn + stargue.com/net +- Higgsfield media pipeline +- Library-vs-MCP decision (scheduler uses library directly; MCP reserved for Claude Code — if future needs require HTTP transport, separate plan) + +--- + +## 11. Open questions — ALL RESOLVED + +All pre-execution gates answered 2026-04-19. Proceeding to Stage 0. + +--- + +## Related + +- [[Publishing Engine PRD]] +- [[7 Cs LinkedIn Series - Continuation Plan]] +- [[Design Philosophy - The Nine Laws of the Hearth]] +- [[Outlet Profiles/LinkedIn]] +- [[Implementation Progress]] + +## Sources + +- [LinkedIn Programmatic Refresh Tokens (Microsoft Learn, retrieved 2026-04-19)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/programmatic-refresh-tokens) +- [LinkedIn 3-Legged OAuth Flow (Microsoft Learn, retrieved 2026-04-19)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow) +- [LinkedIn Posts API (Microsoft Learn)](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api) — current replacement for deprecated UGC Posts +- `/home/devuser/projects/dqms/.clinerules/12-foundational-principles.md` +- `/home/devuser/projects/dqms/.claude/hooks/gate-plan-exit.sh` +- `/home/devuser/projects/dqms/.claude/skills/bmad-plan/SKILL.md` +- `/home/devuser/projects/dqms/_bmad/bmm/agents/*.md` — 7-agent charters (SSoT for panel roles) +- Panel verdicts v2: `/tmp/bmad-panel-verdicts-publishing-engine-phase1-20260419.json` diff --git a/docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json b/docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json new file mode 100644 index 0000000..b85d7b6 --- /dev/null +++ b/docs/plans/bmad-panel-verdicts-publishing-engine-phase1-20260419.json @@ -0,0 +1,193 @@ +{ + "plan_file": "c:\\Users\\aluid\\OneDrive\\Documents\\Obsidian\\Angelo'sSBO\\Stargue\\Projects\\Publishing Engine\\Plan - Phase 1 Automated Publishing Engine.md", + "created_at": "2026-04-19T10:20:00-04:00", + "applicable_tier2": ["RBR", "A11y", "Testability"], + "verdicts": [ + { + "agent": "analyst", + "verdict": "APPROVE", + "confidence": "medium", + "summary": "Plan is evidence-grounded and principle-aligned; approve subject to resolving Company-Page existence, timeline capacity, Bayesian spec, and LinkedIn algorithmic-reach competitive risk before Stage 3.", + "gaps": [ + "§4 timeline: 7-day schedule is aggressive given PRD §2 production-problem framing; no FTE/capacity assumption stated", + "§2/§8: Stargue Company Page URL is both an Objective and an open gate — stakeholder alignment gap", + "§3.7 cadence optimizer: Bayesian posterior under-specified (prior, thresholds, sample-size beyond ≥8)", + "§10: multilingual (PAP/NL/ES) deferred, but PRD §5.3/NF-008 and 7 Cs plan assume PAP audience", + "§9: no competitive risk entry for LinkedIn algorithm/reach-suppression of automated-posted content" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "§3.1 monorepo with packages/{sanitize,schema,linkedin-client,observability} gives clear single-responsibility boundaries; scheduler + MCP both consume linkedin-client (no duplication)."}, + "SSoT": {"score": 5, "rationale": "§3.4 sanitize config + §3.5 Drizzle schema + §5 design tokens generated from Nine Laws doc — each concept has one authoritative location."}, + "FPT": {"score": 4, "rationale": "§1 cites retrieved LinkedIn MDP doc with date; §3 stack choices match verified stargue-com/stargue-net inventory. Minor: Bayesian optimizer under-specified."}, + "DiDSP": {"score": 5, "rationale": "§3.3 libsodium sealed-box tokens, Authentik SSO, bearer tokens scheduler↔MCP, rate limits, no public exposure, Zod at every boundary."}, + "PbD": {"score": 4, "rationale": "§5 notes token minimization, 13-month metrics retention, erasure path via token revoke + git delete. Gap: no DPIA note on LinkedIn-side audience data."}, + "OF": {"score": 5, "rationale": "§3.3 correlation IDs, Pino→OpenObserve, PII-redacted content (length+hash only), append-only audit table, proactive T-5d auth-expiry notification."}, + "RBR": {"score": 4, "rationale": "§3.3 fail-safe halt, §3.7 human-in-the-loop for cadence, §3.5 expand-then-contract, §4.3 DLQ. Gap: no rollback drill for published-to-LinkedIn content past 5-min edit window."}, + "A11y": {"score": 4, "rationale": "§3.6 WCAG 2.2 AA, axe-core in CI, keyboard parity, prefers-reduced-motion, contrast on both themes; OAuth screen delegated to LinkedIn. Gap: no screen-reader manual test named."}, + "Testability": {"score": 4, "rationale": "§3 pure core in packages; §7 contract tests against recorded LinkedIn exchanges; injectable clock/ID implied. Gap: cadence-optimizer seams not explicit."} + } + }, + { + "agent": "architect", + "verdict": "REVISE", + "confidence": "high", + "summary": "Architecturally sound and stack-verified, but over-partitioned for a 7-day build — collapse api-into-admin, fix the libsodium primitive, and cite the rate-limit numbers before Stage 0.", + "gaps": [ + "§3.1 Repo topology adds Turbo + 4 apps + 4 packages for a 7-day plan — architecturally heavy for one-engineer rollout; suggest collapsing apps/api into apps/admin route handlers", + "§3.3 MCP transport: Streamable HTTP spec version unnamed; scheduler-to-MCP over HTTP within same Docker network adds a hop vs direct library import — justify or use library", + "§3.3 Token store: libsodium sealed-box is public-key encryption; for symmetric at-rest DB encryption use secretbox or KMS envelope", + "§3.3 Rate limit numbers (~100/day per member) are uncited", + "§3.8 Postgres: says 'shared with other PaaS apps (existing cluster)' — verify; §8.5 open question contradicts", + "§4 Stages: 7-day timeline for OAuth×2 identities + 8 MCP tools + scheduler + admin + A11y CI + E2E is optimistic", + "§3.4 Sanitization: no test corpus named in Stage DoD (only in §9 risks)", + "§3.6 Admin: no CSRF strategy named for approval POSTs" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Clean package boundaries (§3.1); mcp-linkedin consumes linkedin-client — but api+admin separation may violate YAGNI for Phase 1."}, + "SSoT": {"score": 5, "rationale": "Frontmatter schema, blocklist, design tokens each single-homed (§3.4, §3.5, §3.6)."}, + "FPT": {"score": 4, "rationale": "Stack verified against actual repos; LinkedIn docs cited with retrieval date. Loses a point on uncited rate-limit figures and MCP transport choice."}, + "DiDSP": {"score": 4, "rationale": "Zod+Authentik+rate-limit+encrypted tokens+audit (§3.3) — solid. Loses point for libsodium sealed-box mis-selection and missing CSRF."}, + "PbD": {"score": 4, "rationale": "Low PII surface, 13-month retention, erasure path named (§5). Token retention policy could be more explicit."}, + "OF": {"score": 5, "rationale": "Correlation IDs end-to-end, OpenObserve integration, body-hash redaction, append-only audit (§3.3, §5)."}, + "RBR": {"score": 4, "rationale": "Expand-only migrations, DLQ, human-gated cadence, fail-safe on auth. LinkedIn delete only within 5-min edit window — no compensating audit-before-publish preview enforcement."}, + "A11y": {"score": 4, "rationale": "WCAG 2.2 AA target, axe-core in CI, keyboard parity, both themes contrast-checked. Screen-reader manual test unlisted in DoD."}, + "Testability": {"score": 4, "rationale": "Pure core in packages, injectable clock, client behind interface, contract tests against recorded exchanges. No named fake for token-store rotation race."} + } + }, + { + "agent": "pm", + "verdict": "REVISE", + "confidence": "high", + "summary": "Strong governance and architecture, but the plan inverts the PRD's revenue-first pivot and fails to reconcile scope with the 4h/week capacity cap — revise scope and timeline before exit.", + "gaps": [ + "§4 Stages vs PRD §6.5: Plan compresses M1.1–M1.4 (PRD 7 weeks @ 4h/wk = 28h) into ~7 days with no hours-per-stage stated; PRD §9 risk #1 is solo-founder capacity", + "§2 Objectives: zero mention of revenue conversion (PRD §6.4, F-003); PRD v2 pivot explicitly revenue-first, plan adds infrastructure only", + "§10: multilingual (PAP/NL/ES) deferred to Phase 2 but PRD F-021 places at P1 and 7 Cs plan publishes EN only", + "§4 Stage 7: 14 drafts committed in 2 days assumes autoresearch loop is near-free — no hours estimate", + "§3.6/§3.7 Admin + optimizer are M1.2/M2 scope in PRD; pulling into Phase 1 redefines Phase 1 boundary", + "§11: MDP application timeline not flagged for revenue-dependent features" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Monorepo boundaries clean (§3.1); standard packages/apps split; no leaky abstractions."}, + "SSoT": {"score": 5, "rationale": "Frontmatter schema, sanitize config, design tokens all single-location; eliminates existing ad-hoc parse."}, + "FPT": {"score": 4, "rationale": "Cites LinkedIn MDP docs with retrieval date; verified repo state; real outlet profile. Does not probe 4h/wk capacity math."}, + "DiDSP": {"score": 5, "rationale": "Zod at boundaries, Authentik SSO, libsodium, rate limiting, audit table, no public MCP exposure."}, + "PbD": {"score": 4, "rationale": "Token retention + encryption + revocation path; 13-month analytics cap; content is public-by-design."}, + "OF": {"score": 5, "rationale": "Correlation IDs through API→scheduler→MCP; OpenObserve dashboards; audit append-only; PII redaction."}, + "RBR": {"score": 4, "rationale": "Human approval on every publish, DLQ, expand-only migrations, feature flag on adaptive optimizer, Telegram fail-safe. No staged rollout for MCP server itself."}, + "A11y": {"score": 4, "rationale": "WCAG 2.2 AA target, axe-core CI, keyboard parity, prefers-reduced-motion, dual-theme contrast. Not yet tested with SR."}, + "Testability": {"score": 4, "rationale": "Pure core in packages, injectable clock/ID, LinkedIn client behind interface with contract tests. Sandbox-or-contract fallback right."} + } + }, + { + "agent": "sm", + "verdict": "REVISE", + "confidence": "high", + "summary": "Architecture and DoDs are largely sprint-ready, but stages must be re-timelined against the PRD's 4h/week cap and 6 DoD items need 'works'-language tightened before dev handoff.", + "gaps": [ + "§4 DoD uses calendar 'days 1–7' but PRD caps at 4h/week; re-express as Week-1…Week-N at 4h/week or justify", + "§4 Stage 1.2 DoD 'Sanitizes sample vault note correctly' needs replacement with 'round-trips 12-file private-corpus fixture with 0 leaks and 12 build errors'", + "§4 Stage 3.4: no cleanup step; add 'test post deleted within 5-min edit window; deletion audited'", + "§4 Stage 4.2 'End-to-end happy path': specify trace (POST /content → approval → scheduled_at → git SHA + LinkedIn URN recorded, status=published, within 120s)", + "§4 Stage 5.3 'No violations above minor' needs axe-core severity gate (zero serious+critical) + WCAG contrast ratios", + "§4 Stage 7.4 'Observed live' not CI-demonstrable; add 'publication row external_url returns HTTP 200 from unauthenticated curl; metrics row at T+48h±1h'", + "§8 Auth gates block Stage 0 exit but Stage 0 has no 'gates answered' DoD row", + "§4 Stage 2.1 'expand-only migrations documented': needs pointer to doc location", + "No sprint sequencing artifact / story backlog — charter requires it for dev handoff" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Clean monorepo boundaries (§3.1); shared packages reused — no duplicated logic named."}, + "SSoT": {"score": 5, "rationale": "§3.4 + §3.5 put frontmatter, sanitize config, tokens in single packages; §5 SSoT row cites file paths."}, + "FPT": {"score": 4, "rationale": "§1 cites LinkedIn MDP doc with retrieval date; verified repo state; rejected n8n with tradeoff. Miss on Phase 0 capacity constraint."}, + "DiDSP": {"score": 5, "rationale": "§3.3 token encryption + §3.5 audit table + §3.6 Authentik SSO + §9 rate limits — layered, concrete."}, + "PbD": {"score": 4, "rationale": "§5 PbD row names retention (13mo), erasure path (token revoke + hard delete), purpose limitation. Missing DPIA note."}, + "OF": {"score": 5, "rationale": "§3.3 per-tool correlation ID + OpenObserve + redaction rule — structured and specific."}, + "RBR": {"score": 3, "rationale": "§5 names approval-gate + DLQ + expand-contract + flagged adaptive cadence. Missing rollback runbook for a bad LinkedIn post past 5-min edit window."}, + "A11y": {"score": 4, "rationale": "§3.6 + Stage 5.3/5.4 name axe-core, keyboard parity, reduced-motion, dual-theme contrast. 'No violations above minor' ambiguous."}, + "Testability": {"score": 4, "rationale": "§7 splits unit/integration/E2E; §3.3 client behind interface; injectable clock mentioned. Cadence-optimizer Bayesian logic has no named test seams."} + } + }, + { + "agent": "ux-designer", + "verdict": "REVISE", + "confidence": "high", + "summary": "Strong security/SSoT/observability spine with Nine-Laws-aware design intent, but A11y is declared-not-designed — add operator flows, SR protocol, dual-theme contrast verification, and reconcile Law-3 temperature with WCAG 1.4.1 before exit.", + "gaps": [ + "§3.6/§5: A11y names mechanisms but omits user flows; no wireframes/task analysis/cognitive-load budget for /queue approval task (where slip leaks private content)", + "§3.6: aria-live polite/assertive level unspecified; no error-association spec for OAuth re-auth or approval form (WCAG 3.3.1, 3.3.3, 4.1.3)", + "§3.6: no screen-reader testing in CI or manual protocol (charter requires SR-tested for score 5)", + "§3.6: dual-theme contrast promised but neither theme's token values (warm #fefbf6 / cool #f8fafc + Forest/Lime accents) verified against 4.5:1 / 3:1", + "Nine Laws fit: admin dashboard is operator-tool — no temperature declared; risk of Law-4 amber leakage into status badges", + "§3.7 cadence optimizer heatmap is color-encoded — Law 3 temperature semantics conflict with WCAG 1.4.1 (color not sole means)", + "§4 Stage 5: no criterion for focus-trap on modal dialogs, skip-links, or reduced-motion honoring in section fade-in (P8.4)", + "§11: open questions omit 'primary operator persona and accessibility context'" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Monorepo boundaries in §3.1 are clean; shared packages reused by scheduler + mcp; tradeoff named."}, + "SSoT": {"score": 5, "rationale": "§3.1 shared packages, §3.4 Zod config, §3.6 design tokens inherited from Nine Laws doc — no duplication path."}, + "FPT": {"score": 4, "rationale": "§1 cites LinkedIn docs with retrieval date and verifies repo state; rejects n8n on SSoT grounds."}, + "DiDSP": {"score": 5, "rationale": "§3.3 sealed-box, §3.4 fail-closed sanitizer, §3.5 append-only audit, Authentik + bearer layered."}, + "PbD": {"score": 4, "rationale": "§3.3 token minimization, 13-month analytics retention, erasure path named."}, + "OF": {"score": 5, "rationale": "§3.3/§3.5 correlation IDs, structured Pino logs, OpenObserve dashboards, append-only audit."}, + "RBR": {"score": 4, "rationale": "§3.7 human-in-loop, DLQ, expand-only migrations, feature flag on adaptive cadence."}, + "A11y": {"score": 3, "rationale": "Targets WCAG 2.2 AA and names axe-core/keyboard/contrast — missing SR testing, user flows, cognitive-load analysis on approval task, heatmap color-only encoding risk, Law-3/Law-4 contrast verification. Meets floor, not exemplary."}, + "Testability": {"score": 4, "rationale": "§7 names unit/integration/E2E/contract tests; §3.3 injectable token store; pure-core in packages."} + } + }, + { + "agent": "dev", + "verdict": "REVISE", + "confidence": "high", + "summary": "Solid architecture and governance, but ship-blocker inaccuracies on LinkedIn auth (PKCE unverified) and endpoint (UGC Posts is Legacy; use Posts API with LinkedIn-Version header) must be corrected before Stage 3.", + "gaps": [ + "§3.3 Auth: scopes 'openid profile email' are from 'Sign In with LinkedIn using OpenID Connect' product; 'w_member_social' requires 'Share on LinkedIn' product — must be declared per-product in Dev Portal. 'w_organization_social'/'r_organization_social' require Community Management API product approval (separate gate).", + "§3.3 Auth: PKCE is NOT documented as supported by LinkedIn's 3LO — Microsoft Learn authorization-code-flow shows only response_type=code + client_secret. Either cite PKCE support or drop it.", + "§3.3 Tools: UGC Post API is now Legacy; must use Posts API (/rest/posts) with 'LinkedIn-Version: YYYYMM' header and 'X-Restli-Protocol-Version: 2.0.0'. Building against /v2/ugcPosts = tech debt on day one.", + "§3.2/§3.5: no idempotency key on POST /content or BullMQ job dispatch — retries could double-post", + "§3.8: stargue-net/package.json is byte-identical to stargue-com/package.json (same 'name' field) — name collision to resolve before workspace link", + "§4 Stage 3.9: MCP Streamable HTTP transport is current spec (stdio+SSE deprecated); pin @modelcontextprotocol/sdk version", + "§7: no AC→Vitest mapping; contract-test recording tool unchosen (Polly.js? msw?)" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Clean package boundaries; MCP client reused by scheduler; no duplicated domain logic across apps."}, + "SSoT": {"score": 4, "rationale": "Zod schema + sanitize config + design tokens each single-homed. Risk: stargue-net package.json duplicates stargue-com verbatim — name collision to resolve."}, + "FPT": {"score": 3, "rationale": "Plan cites repo state and doc retrieval date, but LinkedIn API claims don't match live docs: UGC Posts is Legacy (Posts API current), PKCE not documented as supported. Fix before impl."}, + "DiDSP": {"score": 4, "rationale": "Authentik SSO + internal bearer + Zod + libsodium + rate limits + audit log + no public MCP exposure. Missing idempotency key to prevent replay/double-post."}, + "PbD": {"score": 4, "rationale": "Low PII surface; token retention tied to lifetime; analytics capped 13mo; erasure path named."}, + "OF": {"score": 5, "rationale": "Correlation IDs across API→scheduler→MCP; Pino JSON; OpenObserve; append-only audit; per-call latency+outcome; PII redaction."}, + "RBR": {"score": 4, "rationale": "Approval gate + DLQ + expand-then-contract + feature flag + proactive re-auth notification."}, + "A11y": {"score": 3, "rationale": "WCAG 2.2 AA + axe-core CI + keyboard parity + reduced-motion + dual-theme contrast. Lacks SR test plan and aria-live specifics."}, + "Testability": {"score": 3, "rationale": "Pure core extracted; injectable clock/ID; client behind interface. Missing AC→test mapping, HTTP-recording lib, and Vitest setup for Bun workspaces."} + } + }, + { + "agent": "tea", + "verdict": "REVISE", + "confidence": "high", + "summary": "Architecture is sound and SSoT/OF are exemplary; revise §4 DoDs into measurable gates, close refresh-race + idempotency + migration-rehearsal + kill-switch gaps, then re-score — currently REVISE.", + "gaps": [ + "§4 Stage DoDs largely untestable as stated ('Smoke tests pass', 'Test post lands', 'End-to-end happy path on staging', 'Matches stargue.com visual system') — add measurable criteria", + "§7 omits flake controls: no frozen clock, seeded RNG, deterministic IDs, or VCR/contract-test refresh cadence", + "§3.3 refresh path lacks concurrency guard — two scheduler jobs on expired token simultaneously can double-rotate; specify SELECT FOR UPDATE / advisory lock", + "§3.5 'expand-only' migrations claimed but no shadow-column/backfill/contract sequence shown; add migration rehearsal DoD", + "§4 Stage 4 DLQ DoD (4.3) names backoff but not idempotency key — retries could duplicate public posts", + "§4 Stage 3 lacks rate-limit test DoD — add burst-21-calls test asserting 429 on #21", + "§3.6 A11y: axe-core 'baseline' and 'no violations above minor' is vague — specify ruleset version, severity gate, SR smoke", + "§3.4 sanitizer: no DoD for the private-path corpus itself — add 'CI fails if test/corpus/private/* round-trips without build error'", + "§8 gate #3 assumes Authentik exists — Stage 5.2 DoD circular if not", + "No kill-switch / feature flag for LinkedIn poster — RBR requires fast-disable independent of code deploy" + ], + "principles": { + "SEBP": {"score": 4, "rationale": "Clean package/app split (§3.1); client shared between scheduler+MCP; tradeoff acknowledged."}, + "SSoT": {"score": 5, "rationale": "Zod schema, sanitize config, design tokens each in one package."}, + "FPT": {"score": 4, "rationale": "Cites LinkedIn docs with retrieval date (§1); stack matches verified repo state; refresh-concurrency not reasoned from first principles."}, + "DiDSP": {"score": 4, "rationale": "Layered (Zod+Authentik+bearer+libsodium+audit) but idempotency + refresh-race gaps reduce from 5."}, + "PbD": {"score": 4, "rationale": "Token min/encrypt/retention named; 13-month metrics retention stated. SAR path asserted, not DoD-tested."}, + "OF": {"score": 5, "rationale": "Correlation IDs end-to-end, append-only audit, PII redaction, per-call LinkedIn latency capture."}, + "RBR": {"score": 3, "rationale": "Approval gate, DLQ, proactive auth notify present — but expand-contract migrations unspecified, no idempotency key, no runtime kill-switch."}, + "A11y": {"score": 3, "rationale": "WCAG 2.2 AA named, axe-core in CI, two-theme contrast — severity gate + SR smoke + ruleset version unspecified."}, + "Testability": {"score": 3, "rationale": "Pure core extracted, injectable clock/ID/randomness, client-behind-interface — but Stage DoDs largely untestable and no determinism/record-refresh policy."} + } + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2707eca --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "stargue-publishing-engine", + "version": "0.1.0", + "private": true, + "description": "Stargue Publishing Engine — automated content pipeline + LinkedIn MCP server", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "build": "turbo run build", + "dev": "turbo run dev", + "lint": "turbo run lint", + "test": "turbo run test", + "typecheck": "turbo run typecheck", + "db:migrate": "bun --filter '@stargue/schema' db:migrate", + "db:migrate:rehearse": "bun --filter '@stargue/schema' db:migrate:rehearse" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "bun-types": "^1.3.12", + "turbo": "^2.3.0", + "typescript": "^5.7.0" + }, + "engines": { + "bun": ">=1.1.30" + }, + "packageManager": "bun@1.1.38" +} diff --git a/packages/linkedin-client/package.json b/packages/linkedin-client/package.json new file mode 100644 index 0000000..2b4e8f2 --- /dev/null +++ b/packages/linkedin-client/package.json @@ -0,0 +1,25 @@ +{ + "name": "@stargue/linkedin-client", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'" + }, + "dependencies": { + "libsodium-wrappers-sumo": "^0.7.15" + }, + "devDependencies": { + "vitest": "^2.1.0", + "msw": "^2.6.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/linkedin-client/src/index.ts b/packages/linkedin-client/src/index.ts new file mode 100644 index 0000000..b699461 --- /dev/null +++ b/packages/linkedin-client/src/index.ts @@ -0,0 +1,6 @@ +export const LINKEDIN_CLIENT_READY = false; +// Posts API client + token store — implemented in Stage 1.4 + 3.*. +// Reference: https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api +export const LINKEDIN_API_BASE = "https://api.linkedin.com/rest"; +export const LINKEDIN_API_VERSION = "202404"; +export const LINKEDIN_RESTLI_VERSION = "2.0.0"; diff --git a/packages/observability/package.json b/packages/observability/package.json new file mode 100644 index 0000000..18e1e9c --- /dev/null +++ b/packages/observability/package.json @@ -0,0 +1,24 @@ +{ + "name": "@stargue/observability", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'" + }, + "dependencies": { + "pino": "^9.5.0" + }, + "devDependencies": { + "vitest": "^2.1.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/observability/src/index.ts b/packages/observability/src/index.ts new file mode 100644 index 0000000..df0a429 --- /dev/null +++ b/packages/observability/src/index.ts @@ -0,0 +1,27 @@ +import pino from "pino"; + +export const logger = pino({ + level: process.env.LOG_LEVEL ?? "info", + redact: { + paths: [ + "access_token", + "refresh_token", + "access_token_ct", + "refresh_token_ct", + "client_secret", + "*.access_token", + "*.refresh_token", + "*.client_secret", + ], + censor: "[REDACTED]", + }, + formatters: { + level: (label) => ({ level: label }), + }, + timestamp: pino.stdTimeFunctions.isoTime, +}); + +export const newCorrelationId = (): string => + `corr_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`; + +export const child = (bindings: Record) => logger.child(bindings); diff --git a/packages/sanitize/package.json b/packages/sanitize/package.json new file mode 100644 index 0000000..0654aea --- /dev/null +++ b/packages/sanitize/package.json @@ -0,0 +1,29 @@ +{ + "name": "@stargue/sanitize", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'" + }, + "dependencies": { + "unified": "^11.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "mdast-util-from-markdown": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "devDependencies": { + "vitest": "^2.1.0", + "typescript": "^5.7.0", + "@types/mdast": "^4.0.0" + } +} diff --git a/packages/sanitize/src/index.ts b/packages/sanitize/src/index.ts new file mode 100644 index 0000000..00f3e46 --- /dev/null +++ b/packages/sanitize/src/index.ts @@ -0,0 +1,2 @@ +export const SANITIZE_PACKAGE_READY = false; +// Implementation in Stage 1.2. See docs/plans/2026-04-19-phase1-plan.md Stage 1. diff --git a/packages/schema/package.json b/packages/schema/package.json new file mode 100644 index 0000000..6fd4818 --- /dev/null +++ b/packages/schema/package.json @@ -0,0 +1,30 @@ +{ + "name": "@stargue/schema", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./db": "./src/db.ts", + "./frontmatter": "./src/frontmatter.ts" + }, + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "echo 'lint pending'", + "db:migrate": "drizzle-kit migrate", + "db:migrate:rehearse": "echo 'rehearsal script pending Stage 2.2'" + }, + "dependencies": { + "zod": "^3.23.0", + "drizzle-orm": "^0.36.0" + }, + "devDependencies": { + "drizzle-kit": "^0.28.0", + "vitest": "^2.1.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/schema/src/db.ts b/packages/schema/src/db.ts new file mode 100644 index 0000000..546db8d --- /dev/null +++ b/packages/schema/src/db.ts @@ -0,0 +1,91 @@ +import { pgTable, serial, text, timestamp, integer, jsonb, boolean, uniqueIndex, index } from "drizzle-orm/pg-core"; + +export const content = pgTable("content", { + id: serial("id").primaryKey(), + vault_path: text("vault_path").notNull().unique(), + slug: text("slug").notNull(), + title: text("title").notNull(), + body_sanitized: text("body_sanitized").notNull(), + frontmatter_jsonb: jsonb("frontmatter_jsonb").notNull(), + content_hash: text("content_hash").notNull(), + version: integer("version").notNull().default(1), + created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, (t) => ({ + slugIdx: index("content_slug_idx").on(t.slug), +})); + +export const publications = pgTable("publications", { + id: serial("id").primaryKey(), + content_id: integer("content_id").notNull().references(() => content.id), + outlet: text("outlet").notNull(), + status: text("status").notNull(), + scheduled_at: timestamp("scheduled_at", { withTimezone: true }), + published_at: timestamp("published_at", { withTimezone: true }), + external_id: text("external_id"), + external_url: text("external_url"), + idempotency_key: text("idempotency_key").notNull(), + error: text("error"), + metadata_jsonb: jsonb("metadata_jsonb"), +}, (t) => ({ + idemIdx: uniqueIndex("publications_idempotency_key_idx").on(t.idempotency_key), + contentOutletIdx: index("publications_content_outlet_idx").on(t.content_id, t.outlet), +})); + +export const approvals = pgTable("approvals", { + id: serial("id").primaryKey(), + content_id: integer("content_id").notNull().references(() => content.id), + outlet: text("outlet").notNull(), + approved_by: text("approved_by").notNull(), + approved_at: timestamp("approved_at", { withTimezone: true }).notNull().defaultNow(), + notes: text("notes"), +}); + +export const metrics = pgTable("metrics", { + id: serial("id").primaryKey(), + publication_id: integer("publication_id").notNull().references(() => publications.id), + collected_at: timestamp("collected_at", { withTimezone: true }).notNull().defaultNow(), + impressions: integer("impressions").notNull().default(0), + reactions: integer("reactions").notNull().default(0), + comments: integer("comments").notNull().default(0), + shares: integer("shares").notNull().default(0), + clicks: integer("clicks").notNull().default(0), + raw_jsonb: jsonb("raw_jsonb"), +}); + +export const linkedin_tokens = pgTable("linkedin_tokens", { + id: serial("id").primaryKey(), + subject_type: text("subject_type").notNull(), + subject_urn: text("subject_urn").notNull(), + access_token_ct: text("access_token_ct").notNull(), + refresh_token_ct: text("refresh_token_ct"), + access_expires_at: timestamp("access_expires_at", { withTimezone: true }).notNull(), + refresh_expires_at: timestamp("refresh_expires_at", { withTimezone: true }), + scopes: text("scopes").notNull(), + created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, (t) => ({ + subjectIdx: uniqueIndex("linkedin_tokens_subject_idx").on(t.subject_urn), +})); + +export const audit = pgTable("audit", { + id: serial("id").primaryKey(), + ts: timestamp("ts", { withTimezone: true }).notNull().defaultNow(), + actor: text("actor").notNull(), + action: text("action").notNull(), + subject_type: text("subject_type").notNull(), + subject_id: text("subject_id").notNull(), + correlation_id: text("correlation_id").notNull(), + payload_jsonb: jsonb("payload_jsonb"), +}, (t) => ({ + tsIdx: index("audit_ts_idx").on(t.ts), + corrIdx: index("audit_correlation_idx").on(t.correlation_id), +})); + +export const outlet_feature_flags = pgTable("outlet_feature_flags", { + outlet: text("outlet").primaryKey(), + enabled: boolean("enabled").notNull().default(true), + reason: text("reason"), + updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + updated_by: text("updated_by").notNull(), +}); diff --git a/packages/schema/src/frontmatter.ts b/packages/schema/src/frontmatter.ts new file mode 100644 index 0000000..4d54d09 --- /dev/null +++ b/packages/schema/src/frontmatter.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; + +export const OutletSchema = z.enum([ + "stargue.com", + "stargue.net", + "linkedin.member", + "linkedin.org", +]); +export type Outlet = z.infer; + +export const LanguageSchema = z.enum(["en", "nl", "pap", "es"]); +export type Language = z.infer; + +export const OutletStatusSchema = z.enum([ + "pending", + "queued", + "approved", + "published", + "failed", +]); + +export const PerOutletPublishSchema = z.object({ + outlet: OutletSchema, + status: OutletStatusSchema, + published_url: z.string().url().nullable(), +}); + +export const PublishFrontmatterSchema = z.object({ + status: z.enum(["draft", "ready", "queued", "published", "partial", "failed"]), + language: LanguageSchema.default("en"), + outlets: z.array(PerOutletPublishSchema), + scheduled: z.string().datetime({ offset: true }).nullable().default(null), + slug: z.string().min(1).optional(), + category: z.enum(["blog", "case-study", "white-paper", "research", "digest"]), + canonical: z.string().default("stargue.com"), + sanitize: z.boolean().default(true), + version: z.number().int().positive().default(1), +}); +export type PublishFrontmatter = z.infer; + +export const NoteFrontmatterSchema = z.object({ + created: z.string().datetime({ offset: true }), + updated: z.string().datetime({ offset: true }), + tags: z.array(z.string()).or(z.null()).optional(), + publish: PublishFrontmatterSchema.optional(), +}); +export type NoteFrontmatter = z.infer; + +export const parseFrontmatter = (raw: unknown): NoteFrontmatter => + NoteFrontmatterSchema.parse(raw); diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts new file mode 100644 index 0000000..dabb2ba --- /dev/null +++ b/packages/schema/src/index.ts @@ -0,0 +1,2 @@ +export * from "./frontmatter"; +export * from "./db"; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..84467bf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "types": ["bun-types", "node"] + }, + "exclude": ["node_modules", "dist", ".next", ".turbo"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..64515d9 --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", ".next/**", "!.next/cache/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": { + "dependsOn": ["^build"] + }, + "test": { + "dependsOn": ["^build"], + "outputs": ["coverage/**"] + }, + "typecheck": { + "dependsOn": ["^build"] + } + } +}