Overview

38 min read

Product: Open Speckit

Domain: openspeckit.com

Document type: Functional specification (agent-ready, spec-driven development)

Version: 1.0

Status: Ready for implementation

Target stack: Angular (standalone, SSR) + Firebase (Auth, Firestore, Storage, Functions, Hosting)


0. How to use this document

This is the source specification for an AI coding agent. Implement in the order of the milestones in §16. Every functional requirement has an ID (FR-x.y) and acceptance criteria (AC-x.y.z). Do not invent features outside this document; if something is ambiguous, choose the simplest implementation that satisfies the acceptance criteria and record the decision in /docs/decisions.md.

Before coding, generate CONSTITUTION.md in the repository root from §2 and a technical plan from §12–§15. Do not begin implementation until the technical plan is approved.


1. Product vision

1.1 Problem

Software is increasingly written by AI coding agents. The quality of the resulting system depends almost entirely on the quality of the specification fed to the agent. Today those specifications live in private repositories, in chat histories, or nowhere at all. There is no common place to find, reuse, fork and improve high-quality specifications.

1.2 Solution

Open Speckit is an open repository of specifications for IT solutions — "GitHub for specifications of agent-coded software". Anyone can browse and download specifications for free. Registered users can publish, version, fork and improve them. All content is released under the MIT licence.

1.3 Core value propositions

Audience Value
Developer using an AI agent Download a ready, structured specification and feed it directly to their agent
Author / architect Publish a specification, gain reputation, receive forks and improvements
Team / consultancy Standardise how specifications are written; reuse CONSTITUTION templates
Educator Use published specifications as teaching material

1.4 Product principles

  1. Free and open by default. Every public specification is MIT-licensed. No paywall, no ads on content pages.
  2. Anonymous read. No registration required to browse, read or download public specifications.
  3. Agent-first output. The download format is directly consumable by a coding agent without post-processing.
  4. Markdown only. No proprietary formats. What you see in the editor is what you download.
  5. Versioned truth. A published version is immutable. Work in progress never leaks into a published version.

1.5 Explicit non-goals (v1)

  • No code hosting, no CI, no issue tracker.
  • No in-app AI generation of specifications (planned post-v1, see §17).
  • No paid plans, no billing.
  • No real-time collaborative editing (single-writer with optimistic locking is sufficient).
  • No comments/discussions (post-v1).

2. CONSTITUTION — cross-cutting requirements

These requirements apply to every module and every screen. They are non-negotiable and must be reflected in CONSTITUTION.md.

2.1 Internationalisation

  • The UI supports Polish (pl) and English (en). English is the default and fallback.
  • All UI strings live in translation files; no hardcoded user-facing text in templates or TypeScript.
  • Language is resolved: URL param ?lang= → user profile preference → navigator.languageen. Persisted in the user profile for signed-in users, in localStorage otherwise.
  • Specification content is not translated by the system. Each specification declares its own content language (contentLanguage, ISO 639-1) which is used for filtering and hreflang.
  • Dates, numbers and relative times use the active locale.

2.2 Accessibility

  • WCAG 2.2 level AA across all pages.
  • Full keyboard operability; visible focus indicators; logical tab order; skip-to-content link.
  • Correct landmark roles and a single <h1> per page.
  • Colour contrast ≥ 4.5:1 for text, ≥ 3:1 for UI components and graphics.
  • All images require alt text; screenshot upload requires an alt text field before saving.
  • Live regions announce async results (save, publish, copy, download).
  • Respect prefers-reduced-motion and prefers-color-scheme.
  • No functionality depends on hover or pointer only.

2.3 Performance

  • Public specification pages are server-side rendered and indexable.
  • Lighthouse targets on public pages: Performance ≥ 90, Accessibility ≥ 95, Best Practices ≥ 95, SEO ≥ 95 (mobile).
  • Largest Contentful Paint ≤ 2.5 s on a 4G connection.
  • Editor and diff viewer are lazy-loaded route chunks.
  • Initial JS payload of the landing page ≤ 200 kB gzipped.

2.4 Security & privacy

  • Authentication only through Firebase Auth with Google provider.
  • Authorisation enforced in Firestore Security Rules and Storage Rules, not only in the UI. The UI hides what the rules forbid; the rules are the boundary.
  • Never trust client-supplied counters, ownership fields or timestamps — they are set by Cloud Functions or rules.
  • Personal data stored: Google uid, display name, avatar URL, e-mail, chosen handle, locale. Nothing else.
  • GDPR: account deletion removes the user profile and anonymises authorship on published public specifications (Deleted user), or deletes them if the user requests removal of content. Data export available on request via contact form.
  • Cookies: only essential (auth session, language). No third-party analytics cookies. If analytics is enabled it must be cookieless and IP-anonymised.
  • All Cloud Function endpoints validate input with a schema and enforce rate limits.

2.5 Code quality

  • Angular standalone components, signals for local state, OnPush change detection everywhere.
  • Strict TypeScript (strict: true, noUncheckedIndexedAccess: true), no any in application code.
  • Feature-first folder structure (§12.2). Data access only through typed repository services — components never touch Firestore SDK directly.
  • Unit tests for services, guards, pipes and diff logic. E2E tests for the critical paths in §15.
  • Every route has a title and meta description set through Angular's Title/Meta services.

3. Glossary

Term Definition
Specification The top-level content unit. Owned by a user, has a slug, metadata, articles and versions.
Article One markdown document inside a specification.
README Mandatory article of type README, the landing content of a specification, contains screenshots.
CONSTITUTION Optional article of type CONSTITUTION holding general/cross-cutting requirements.
Structure mode Whether articles are arranged as steps (implementation order) or modules (functional decomposition).
Draft (working copy) The mutable current content of a specification, visible only to the owner and collaborators.
Version An immutable snapshot of all articles, published with a semantic version number.
Visibility public or private.
Stage draft or production. Indicates maturity of the specification, independent of visibility.
Fork (duplicate) A copy of a specification into another user's account, keeping attribution to the source.
Bundle The downloadable ZIP archive of a specification version.
Handle Unique, URL-safe user identifier, e.g. @mzmigrodzki.

4. Roles and permissions

Role Description
anonymous Any visitor. Not signed in.
user Signed in via Google.
owner The user who created a specification.
collaborator A user granted edit rights on a specification by its owner.
admin Platform administrator (flag on the user document, set manually).

4.1 Permission matrix

Action anonymous user collaborator owner admin
Browse catalogue, search public specs
Read public specification (any published version)
Download public specification bundle
Read private specification
Create specification
Edit articles of a specification
Publish a new version
Change visibility / stage
Manage collaborators
Fork a public specification
Fork own private specification
Delete specification
Unpublish a version
Moderate / hide any specification

Rule: A published public version can never be silently altered. An owner may unpublish it (removing it from public listings) but the version document itself is retained for integrity of forks.


5. Information architecture and routes

Route Page Auth Rendering
/ Landing page public SSR + prerender
/catalogue Catalogue & search public SSR
/catalogue?q=&tags=&category=&lang=&sort= Filtered catalogue public SSR
/s/:handle/:slug Specification overview (README of latest published version) public/private SSR
/s/:handle/:slug/a/:articleSlug Article view public/private SSR
/s/:handle/:slug/versions Version list public/private SSR
/s/:handle/:slug/v/:version Specific version overview public/private SSR
/s/:handle/:slug/v/:version/a/:articleSlug Article at a version public/private SSR
/s/:handle/:slug/compare/:from...:to Version diff public/private CSR (lazy)
/s/:handle/:slug/edit Editor shell (article list + editor) collaborator+ CSR (lazy)
/s/:handle/:slug/edit/:articleSlug Editor for one article collaborator+ CSR (lazy)
/s/:handle/:slug/settings Specification settings owner+ CSR (lazy)
/s/:handle/:slug/stats Specification statistics owner+ CSR (lazy)
/new Create specification wizard user CSR
/u/:handle Public user profile with their public specs public SSR
/dashboard My specifications user CSR
/settings Account settings user CSR
/about About / purpose & content public prerender
/privacy Privacy policy public prerender
/terms Terms of service public prerender
/contact Contact form public prerender
/licence MIT licence explanation public prerender
/404 Not found public

Legacy/short links: /@:handle/:slug redirects (301) to /s/:handle/:slug.


6. FR-1 — Authentication and accounts

FR-1.1 Google sign-in

  • Sign-in exclusively via Firebase Auth Google provider (signInWithPopup, with signInWithRedirect fallback on browsers blocking popups).
  • Sign-in entry points: header button, any gated action (create, edit, fork), and /dashboard guard.
  • After sign-in the user returns to the page they attempted to reach (returnUrl query param, validated to be a same-origin relative path).

FR-1.2 First-time onboarding

On first sign-in the system creates a users/{uid} document and shows an onboarding dialog requiring:

  • Handle — 3–30 chars, ^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$, unique, case-insensitive, checked live against a handles/{handle} reservation document. Pre-filled from the Google display name, slugified.
  • Display name — pre-filled, editable, 2–60 chars.
  • Optional: bio (≤ 280 chars), website URL, preferred UI language.
  • Acceptance of Terms and Privacy Policy (checkbox, required, timestamp stored).

Reserved handles that cannot be claimed: admin, api, about, catalogue, contact, dashboard, licence, license, new, privacy, s, settings, terms, u, www, support, help, openspeckit.

FR-1.3 Account settings

/settings allows editing display name, bio, website, UI language, avatar source (Google avatar or initials), and e-mail notification preferences. Handle can be changed once every 30 days; the old handle is kept as a redirect alias for 12 months.

FR-1.4 Account deletion

/settings → "Delete account" requires typing the handle to confirm and offers two options:

  1. Keep my published public specifications — they remain, authorship replaced by "Deleted user", ownership transferred to a system account.
  2. Delete everything — all specifications, versions, files and statistics are deleted; existing forks are unaffected but their forkedFrom reference is marked as deleted.

Deletion runs as a Cloud Function (deleteAccount), is idempotent and completes within 24 h.

Acceptance criteria

  • AC-1.1 Anonymous visitor can read and download any public specification without ever seeing a sign-in prompt.
  • AC-1.2 A handle cannot be duplicated even under concurrent claims (enforced by a transaction on handles/{handle}).
  • AC-1.3 Attempting a gated action while signed out shows the sign-in dialog and, after success, completes the original action.
  • AC-1.4 Rules deny writes to users/{uid} where uid != request.auth.uid.

7. FR-2 — Specifications

FR-2.1 Creation wizard (/new)

Three steps, all fields validated client- and rule-side:

  1. Basics — title (3–120 chars), slug (auto-generated from title, editable, unique per owner, ^[a-z0-9][a-z0-9-]{1,58}[a-z0-9]$), short description (≤ 300 chars, used in catalogue cards and meta description).
  2. Classification — category (single, from fixed list), tags (1–10, free text, normalised to lowercase-kebab, max 30 chars each), content language, target stack (free tags, e.g. angular, firebase, python), complexity (starter | standard | advanced).
  3. Structure & access — structure mode (steps | modules), visibility (private default), stage (draft default), optional "start from template".

On completion the system creates the specification with a generated README article and, if a template was chosen, the template's articles.

Fixed category list: web-app, mobile-app, api-backend, data-analytics, ai-agent, automation, integration, devtool, game, e-commerce, internal-tool, other.

FR-2.2 Templates

The system ships with at least three built-in templates seeded as specifications owned by the system account and flagged isTemplate: true:

  • Minimal — README + CONSTITUTION + 3 step articles.
  • Web application (Angular + Firebase) — README, CONSTITUTION, and modules: Auth, Data model, UI, Deployment.
  • AI agent / automation — README, CONSTITUTION, and steps: Goal, Tools, Prompts, Guardrails, Evaluation.

FR-2.3 Visibility and stage

  • visibility: private (only owner + collaborators + admin) or public (everyone).
  • stage: draft (work in progress, shown with a badge, excluded from "production" catalogue filter by default) or production (author declares it ready to use).
  • Both are independently switchable from /settings. Switching from private to public requires: a README with ≥ 200 characters of content, at least one non-README article, and explicit confirmation of MIT licensing (dialog with checkbox).
  • Making a specification private again removes it from the catalogue and search index immediately; previously downloaded bundles are unaffected (state this in the confirmation dialog).

FR-2.4 Settings page

/s/:handle/:slug/settings sections:

  • General — title, slug (with warning that old URLs will 301 for 12 months), description, category, tags, stack, complexity, content language.
  • Access — visibility, stage, collaborators (add by handle or e-mail; role editor; owner can remove; max 20).
  • Danger zone — transfer ownership (by handle, requires acceptance by the recipient), archive (read-only, still visible), delete (typed confirmation of the slug).

FR-2.5 Deletion

Deleting a specification soft-deletes it (deletedAt set, removed from all listings and rules deny reads) and hard-deletes after 30 days via a scheduled function. The owner can restore within that window from /dashboard → "Trash".

Acceptance criteria

  • AC-2.1 Slug uniqueness is enforced per owner; a collision surfaces an inline error before submit and is rejected by rules.
  • AC-2.2 A private specification returns 404 (not 403) to unauthorised readers, both in SSR and in the client.
  • AC-2.3 Publishing checks in FR-2.3 are enforced by the publishSpecification Cloud Function, not only in the UI.
  • AC-2.4 Changing a slug leaves working links at the previous URL for 12 months.

8. FR-3 — Articles and structure

FR-3.1 Article types

Type Cardinality Purpose
README exactly 1, mandatory, cannot be deleted Overview, purpose, screenshots, how to use, credits
CONSTITUTION 0 or 1 Cross-cutting/general requirements applying to the whole solution
ARTICLE 0..n A step or a module, depending on structure mode

README is always rendered first, CONSTITUTION always second, ARTICLEs follow in their explicit order.

FR-3.2 Article fields

  • title (3–120 chars)
  • slug (unique within the specification, auto from title, editable)
  • type (README | CONSTITUTION | ARTICLE)
  • order (integer, gap-numbered by 10 for cheap reordering)
  • summary (optional, ≤ 200 chars, shown in the table of contents and in the bundle index)
  • body (markdown, max 200 000 characters)
  • updatedAt, updatedBy, wordCount, readingTimeMinutes (derived, written by function/service)

FR-3.3 Structure management

  • Left sidebar of the editor lists articles grouped by type, with drag-and-drop reordering (keyboard-accessible alternative required: "Move up"/"Move down" buttons and Alt+↑/↓).
  • Structure mode (steps vs modules) changes labels only: "Step 1, Step 2…" vs "Module: …". It does not change data.
  • Add article, rename, duplicate within specification, delete (with confirmation; README cannot be deleted; deleting CONSTITUTION warns that references may break).
  • Maximum 100 articles per specification.

FR-3.4 Screenshots and assets

  • Screenshots are uploaded from the editor into Firebase Storage at specs/{specId}/assets/{assetId}.{ext}.
  • Allowed: png, jpg, webp, gif, svg (SVG sanitised server-side); max 5 MB per file, max 50 files per specification, max 100 MB total.
  • Each upload requires alt text (WCAG). Optional caption.
  • On upload the editor inserts ![alt](assets/{assetId}.png "caption") at the cursor. Relative assets/ paths are resolved to Storage download URLs at render time and are rewritten to real relative files in the bundle.
  • The README's first image is used as the specification cover in catalogue cards and the OG image; the owner can override the cover in settings.

Acceptance criteria

  • AC-3.1 A specification always has exactly one README; the system prevents its deletion and its type change.
  • AC-3.2 Reordering is fully operable by keyboard and announced to screen readers.
  • AC-3.3 An image cannot be inserted without alt text.
  • AC-3.4 Relative asset paths render correctly in the web view, in the diff view and in the downloaded bundle.

9. FR-4 — Markdown editor (wiki-style)

The editor follows the "Verbalu wiki" model: a single-pane markdown editing surface with a live preview, no WYSIWYG abstraction over the source.

FR-4.1 Layout

  • Desktop: three columns — article tree (left, collapsible), markdown source (centre), live preview (right, toggleable, synchronised scroll). Split ratio persisted per user.
  • Mobile/narrow: tabbed — Edit / Preview / Structure.
  • Sticky toolbar; sticky save/status bar showing "Saved", "Saving…", "Unsaved changes", last saved time.

FR-4.2 Toolbar and shortcuts

Bold Ctrl/Cmd+B, Italic Ctrl/Cmd+I, Heading levels, Link Ctrl/Cmd+K, Inline code, Code block, Bullet list, Numbered list, Task list, Table insert (with size picker), Blockquote, Horizontal rule, Image upload, Internal link, Mermaid diagram, Front-matter block. Ctrl/Cmd+S saves. Ctrl/Cmd+/ opens the shortcut help dialog.

FR-4.3 Supported markdown

GitHub Flavored Markdown: headings, emphasis, lists, task lists, tables, fenced code with language hints and syntax highlighting, blockquotes, footnotes, autolinks, strikethrough, inline HTML sanitised (allow-list: formatting tags, <details>, <summary>, <kbd>, <sub>, <sup>; strip all scripts, iframes, event handlers and javascript: URLs).

Extensions:

  • Mermaid fenced blocks (```mermaid) rendered as diagrams in preview, in the web view and exported as-is in the bundle. Failure to parse renders the source in a code block with a warning, never breaks the page.
  • Internal links [[article-slug]] or [[article-slug|Label]] resolving to another article in the same specification. Unresolved links render with a distinct "broken link" style and are listed in the structure health panel.
  • Callouts > [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION].
  • Front matter (optional YAML at the top of an article) with keys title, summary, tags — parsed and used to override article metadata on save.

FR-4.4 Saving

  • Autosave of the working copy 2 seconds after typing stops and on blur, on route change and on tab hide.
  • Optimistic locking: each article carries rev (integer). A save with a stale rev is rejected and the UI shows a conflict dialog offering "Keep mine", "Keep theirs", "Open side-by-side diff".
  • Local recovery: unsaved buffer is mirrored to IndexedDB; if the tab crashes, the editor offers recovery on next open.
  • Saving never touches published versions.

FR-4.5 Editor helpers

  • Live word count, character count and estimated reading time.
  • Outline panel generated from headings, clickable.
  • Structure health panel listing: broken internal links, images missing alt text, articles with empty bodies, headings skipping levels, README shorter than 200 chars.
  • Full-text find & replace within the current article (Ctrl/Cmd+F, Ctrl/Cmd+H).
  • "Paste as markdown": pasting HTML converts it to markdown.
  • "Copy article as markdown" and "Copy whole specification as markdown" buttons.

Acceptance criteria

  • AC-4.1 Typing in a 50 000-character article stays responsive (no visible input lag; preview debounced ≥ 150 ms).
  • AC-4.2 No <script> or event-handler attribute from markdown ever reaches the DOM (unit-tested with a sanitiser fixture set).
  • AC-4.3 A concurrent edit by a collaborator triggers the conflict dialog rather than silently overwriting.
  • AC-4.4 The editor is fully usable with keyboard only, including image insertion and reordering.

10. FR-5 — Versioning, comparison and forking

FR-10.1 Model

  • The working copy is the live set of articles. It is mutable and private to owner/collaborators.
  • A version is an immutable snapshot: versions/{versionId} with the complete article set embedded, plus metadata.
  • Versions use semantic versioning MAJOR.MINOR.PATCH. On publish the UI proposes the next patch, minor and major and lets the user pick or type a value. Versions must be strictly increasing.

FR-10.2 Publishing

Publish dialog collects:

  • Version number (validated, must be greater than the latest).
  • Changelog entry (markdown, ≤ 5 000 chars, required, min 10 chars).
  • Optional "This is a breaking change" flag (forces MAJOR).

publishSpecification Cloud Function then, in a single transaction:

  1. Validates permission, structure (README present and ≥ 200 chars, ≥ 1 non-README article for public specs), and version ordering.
  2. Copies all articles into the version snapshot with a content hash per article and an overall contentHash.
  3. Sets latestVersion, publishedAt, increments versionCount, appends the changelog.
  4. Updates the search index document and the daily stats bucket (updates counter).

FR-10.3 Version list

/s/:handle/:slug/versions shows a reverse-chronological list: version, date, author, changelog, article count, size, "Download", "View", "Compare with…". Owner sees additionally "Unpublish".

FR-10.4 Comparison

/s/:handle/:slug/compare/:from...:to where from/to are version numbers or the literal working (owner/collaborator only).

  • Summary header: articles added / removed / renamed / modified / reordered, total lines added/removed.
  • Per-article accordion showing a line-level unified or side-by-side diff (user-switchable, preference persisted), with word-level highlighting inside changed lines.
  • Filter: "Only changed articles" (default on), "Show whitespace changes" (default off).
  • Renames detected by slug match first, then by content similarity ≥ 60%.
  • Deep-linkable to a specific article: ?a=article-slug.
  • Diff is computed client-side; the diff route is a lazy chunk.

FR-10.5 Fork / duplicate

Two operations, both available to any signed-in user on a readable specification:

  • Duplicate (own specification) — creates a copy in the same account with slug {slug}-copy, visibility private, stage draft, no version history, statistics reset to zero.
  • Fork (any readable specification) — creates a copy in the current user's account from a chosen version (default: latest published). Sets forkedFrom: { specId, handle, slug, version }, visibility private by default, stage draft, statistics reset, and increments forkCount on the source.

Forked and duplicated specifications carry an attribution block automatically appended to the README:

---
Forked from [handle/slug](https://openspeckit.com/s/handle/slug) v1.2.0 — MIT licence.

The source specification page shows "Forks (n)" linking to a list of public forks.

FR-10.6 Restore

An owner can restore the working copy from any version ("Restore this version"), which overwrites the working articles after a typed confirmation. Restoring never deletes versions.

Acceptance criteria

  • AC-5.1 A published version is byte-identical on every subsequent read; no write path can modify it.
  • AC-5.2 Comparing a version against itself reports zero changes.
  • AC-5.3 Comparing 100 articles totalling 500 kB renders in under 2 seconds on a mid-range laptop.
  • AC-5.4 A fork's content equals the source version exactly, plus the attribution block.
  • AC-5.5 Publishing is atomic: a failure mid-way leaves no partial version.

11. FR-6 — Download and export

FR-6.1 Download options

From the specification page, version page and catalogue card:

Format Contents
ZIP bundle (default) Full folder structure, all articles as files, assets, LICENSE, speckit.json
Single markdown All articles concatenated in order with --- separators and a generated table of contents
JSON speckit.json with full content inline (for programmatic use)
Copy to clipboard Single-markdown form, copied directly — the fastest path into an agent

Anonymous users can use all four.

FR-6.2 Bundle layout

{handle}-{slug}-{version}/
├── README.md
├── CONSTITUTION.md            # if present
├── LICENSE                    # MIT, with author and year
├── speckit.json               # machine-readable manifest
├── articles/
│   ├── 01-discovery.md
│   ├── 02-data-model.md
│   └── 03-ui.md
└── assets/
    ├── screenshot-dashboard.png
    └── diagram-flow.svg
  • Article file names: zero-padded order + slug. In modules mode the numbering is still applied to keep deterministic ordering.
  • Every markdown file begins with YAML front matter:
---
title: Data model
type: ARTICLE
order: 20
summary: Firestore collections and their relationships
spec: openspeckit.com/s/mzmigrodzki/task-manager
version: 1.2.0
licence: MIT
---

FR-6.3 speckit.json manifest

{
  "schemaVersion": "1.0",
  "id": "spec_abc123",
  "title": "AI Task Manager",
  "slug": "ai-task-manager",
  "description": "…",
  "url": "https://openspeckit.com/s/mzmigrodzki/ai-task-manager",
  "version": "1.2.0",
  "publishedAt": "2026-08-14T10:00:00Z",
  "licence": "MIT",
  "author": { "handle": "mzmigrodzki", "displayName": "Marcin" },
  "forkedFrom": null,
  "structureMode": "steps",
  "category": "web-app",
  "tags": ["angular", "firebase", "kanban"],
  "stack": ["angular", "firebase"],
  "contentLanguage": "en",
  "complexity": "standard",
  "articles": [
    { "type": "README", "slug": "readme", "title": "Overview", "order": 0, "file": "README.md", "hash": "sha256:…" },
    { "type": "CONSTITUTION", "slug": "constitution", "title": "Constitution", "order": 1, "file": "CONSTITUTION.md", "hash": "sha256:…" },
    { "type": "ARTICLE", "slug": "data-model", "title": "Data model", "order": 20, "file": "articles/02-data-model.md", "hash": "sha256:…" }
  ],
  "assets": [
    { "id": "screenshot-dashboard", "file": "assets/screenshot-dashboard.png", "alt": "Dashboard with three columns" }
  ],
  "contentHash": "sha256:…"
}

FR-6.4 Generation and counting

  • ZIP is generated client-side (JSZip) from the version snapshot for speed; assets are fetched from Storage via their download URLs.
  • The download is registered by calling the registerDownload callable function (or a sendBeacon to an HTTPS function) with { specId, versionId, format }.
  • If the client-side path fails, fall back to the downloadBundle HTTPS function which streams a server-generated ZIP.
  • Deduplication: one counted download per (specId, hashedClientId, day), where hashedClientId is a salted SHA-256 of IP + user agent, or the uid when signed in. The raw IP is never stored.

FR-6.5 Licence

  • Every public specification is MIT. The licence text is generated with the author's display name and the year of first publication.
  • The specification page shows a persistent "MIT licence" badge linking to /licence.
  • Publishing publicly requires explicit acceptance (§FR-2.3), stored as licenceAcceptedAt and licenceAcceptedBy.

Acceptance criteria

  • AC-6.1 An anonymous visitor downloads a bundle in ≤ 2 clicks from the specification page.
  • AC-6.2 The bundle unzips into the exact structure of §11.2 and every article file has valid front matter.
  • AC-6.3 Reloading the download page ten times in a minute increments downloadCount by exactly 1.
  • AC-6.4 speckit.json validates against /schemas/speckit-1.0.schema.json shipped in the repo.
  • AC-6.5 Relative asset links inside markdown resolve to files present in the ZIP.

FR-7.1 Catalogue page

/catalogue lists public specifications as cards showing: cover image (or generated gradient with initials), title, author handle + avatar, short description (2 lines), category, up to 4 tags, stage badge (draft shown in amber), latest version, download count, updated date, "Download" quick action.

Layout: responsive grid (3 / 2 / 1 columns). 24 items per page, infinite scroll on client with an accessible "Load more" button as the primary control.

FR-7.2 Filters

  • Text query — matches title, description, tags, author handle.
  • Category — single select.
  • Tags — multi-select, faceted with counts.
  • Stack — multi-select.
  • Content language — multi-select.
  • Complexity — multi-select.
  • Stageproduction only (default), include drafts.
  • Sortrelevance (only with a query), most downloaded, recently updated, newest, most forked, title A–Z.

All filters are reflected in the URL query string so results are shareable and SSR-renderable.

FR-7.3 Search implementation

  • Maintain a denormalised searchIndex/{specId} document written by Cloud Functions on publish/settings change/visibility change, containing: titleLower, keywords (array of normalised tokens from title + description + tags + stack, deduplicated, max 100), plus all filter fields and sort counters.
  • Query strategy: array-contains-any on keywords (max 10 tokens from the user query) + equality filters + orderBy on the chosen sort field. Client-side re-ranking by token coverage and title-prefix match for relevance sort.
  • Empty query + filters uses plain equality + orderBy.
  • Required composite indexes are declared in firestore.indexes.json.
  • Debounce the query input at 300 ms; show skeleton cards while loading; show a clear empty state with suggested tags.
  • The abstraction is a SearchService interface so an external engine (Typesense/Algolia) can replace it later without touching the UI.

FR-7.4 Discovery on the landing page

The landing page pulls from the same index: 6 most downloaded, 6 most recently updated, and the full tag cloud (top 30 tags).

Acceptance criteria

  • AC-7.1 A private specification never appears in any catalogue or search result, including for its owner's anonymous session.
  • AC-7.2 Filter state survives page reload and can be shared as a URL.
  • AC-7.3 Search returns results in under 500 ms for an index of 1 000 specifications.
  • AC-7.4 Empty results show actionable suggestions, never a bare "no results".

13. FR-8 — Statistics

FR-8.1 Counted events

Event Counter Trigger Dedup window
View viewCount Specification overview or article page loaded 30 min per client
Download downloadCount Any of the four download formats (§11.1) 24 h per client per spec
Update updateCount A version is published none
Fork forkCount A fork is created none
Copy copyCount "Copy to clipboard" used counted also as a download

FR-8.2 Storage

  • Aggregate counters live on the specification document (stats map), written only by Cloud Functions using FieldValue.increment.
  • Daily buckets: specs/{specId}/stats/{yyyy-MM-dd} with { views, downloads, forks, updates } for time series.
  • Platform-wide totals in platform/stats (specifications count, public count, users, total downloads), updated by a scheduled function every hour and shown on the landing page.
  • User-level totals on users/{uid}.stats (total downloads across their public specs, spec count, fork count received).

FR-8.3 Owner statistics page

/s/:handle/:slug/stats (owner + collaborators + admin):

  • KPI tiles: total views, total downloads, forks, versions published, last update.
  • Line chart of views and downloads over the last 30 / 90 / 365 days (range selector).
  • Table of downloads by format and by version.
  • Simple, accessible charts: every chart has an equivalent data table toggle (WCAG).

FR-8.4 Public display

Specification pages and catalogue cards show download count, fork count and last-updated date. View counts are shown only to the owner.

Acceptance criteria

  • AC-8.1 No client can write a counter directly (rules deny stats field writes from clients).
  • AC-8.2 Repeated page reloads within 30 minutes increment viewCount by 1.
  • AC-8.3 The stats page renders correctly for a specification with zero events.
  • AC-8.4 Every chart has a keyboard-accessible data-table equivalent.

14. FR-9 — Static and support pages

FR-9.1 Landing page (/)

Sections in order:

  1. Hero — headline ("Open specifications for software your agent will write"), one-sentence subhead, primary CTA "Browse the catalogue", secondary CTA "Publish a specification". Live platform counters (specs / downloads / authors).
  2. Why — three cards: Specifications are the new source code, Reuse instead of re-writing prompts, Free and MIT for everyone.
  3. What is inside a specification — visual of the structure: README → CONSTITUTION → steps/modules, with a short explanation of each.
  4. How it works — 4 steps: Browse → Download → Feed your agent → Fork and improve.
  5. Featured specifications — 6 cards from the index.
  6. For authors — versioning, diffs, forks, statistics; CTA to sign in with Google.
  7. Licence — MIT explained in two sentences, link to /licence.
  8. FAQ — 6 questions (Is it really free? Who owns what I publish? Can I keep a spec private? What format do I get? How is this different from GitHub? Do I need an account to download?).
  9. Final CTA + footer.

Footer on every page: About, Catalogue, Privacy, Terms, Contact, Licence, language switcher, "Made in Poland", GitHub link (if the platform repository is public).

FR-9.2 About (/about)

Purpose, what belongs on the platform and what does not, quality expectations for a good specification, content guidelines, moderation policy summary.

FR-9.3 Privacy (/privacy)

GDPR-compliant policy: controller identity and contact, data collected (Google profile: e-mail, name, avatar, uid; handle; content; technical logs), purposes and legal bases, Firebase/Google as processor and international transfers, retention periods, user rights (access, rectification, erasure, portability, objection), cookies (essential only), contact for data requests, last-updated date.

FR-9.4 Terms (/terms)

Free service "as is", account rules, content licensing: by publishing publicly the author irrevocably licenses the content under MIT, warranty that the author has the right to publish, prohibited content (illegal, infringing, malicious, spam, personal data of third parties), moderation and takedown procedure, DMCA-style complaint route, liability limitation, termination, governing law (Poland), changes to terms with notice.

FR-9.5 Contact (/contact)

Form: name, e-mail, subject (select: general, bug, content takedown, privacy request, partnership), message (≤ 3 000 chars), consent checkbox, invisible reCAPTCHA v3. Submits to submitContactForm Cloud Function which validates, rate-limits (3 per hour per IP hash), stores in contactMessages and sends an e-mail to the platform address. Success and error states announced in a live region. Also displays the direct e-mail address as a fallback.

FR-9.6 Licence page (/licence)

Plain-language explanation of MIT for specifications, the full MIT text, what it means for authors and for consumers, attribution expectations on forks.

Acceptance criteria

  • AC-9.1 All static pages are prerendered and reachable from the footer of every page.
  • AC-9.2 The contact form is unusable without consent and fails gracefully when reCAPTCHA is unavailable.
  • AC-9.3 All static pages exist in both pl and en.

15. Data model (Firestore)

15.1 Collections

users/{uid}
handles/{handle}                    # reservation, { uid, createdAt }
specs/{specId}
specs/{specId}/articles/{articleId} # working copy
specs/{specId}/versions/{versionId} # immutable snapshots
specs/{specId}/assets/{assetId}
specs/{specId}/stats/{yyyy-MM-dd}
searchIndex/{specId}                # public, denormalised
platform/stats                      # single doc
contactMessages/{messageId}         # server-write only
reports/{reportId}                  # abuse reports, server-write only

15.2 users/{uid}

interface UserDoc {
  uid: string;
  handle: string;                 // lowercase, unique
  handleLower: string;
  displayName: string;
  email: string;                  // never exposed publicly
  photoURL: string | null;
  bio: string | null;             // <= 280
  website: string | null;
  locale: 'pl' | 'en';
  isAdmin: boolean;               // set manually, never client-writable
  termsAcceptedAt: Timestamp;
  handleChangedAt: Timestamp | null;
  stats: { specCount: number; publicSpecCount: number; totalDownloads: number; totalForks: number };
  createdAt: Timestamp;
  updatedAt: Timestamp;
  deletedAt: Timestamp | null;
}

15.3 specs/{specId}

interface SpecDoc {
  id: string;
  ownerUid: string;
  ownerHandle: string;            // denormalised for URLs and rules
  collaboratorUids: string[];     // max 20
  title: string;
  slug: string;                   // unique per owner
  previousSlugs: { slug: string; until: Timestamp }[];
  description: string;            // <= 300
  category: Category;
  tags: string[];                 // 1..10
  stack: string[];
  contentLanguage: string;        // ISO 639-1
  complexity: 'starter' | 'standard' | 'advanced';
  structureMode: 'steps' | 'modules';
  visibility: 'private' | 'public';
  stage: 'draft' | 'production';
  isTemplate: boolean;
  coverAssetId: string | null;
  latestVersion: string | null;   // e.g. "1.2.0"
  latestVersionId: string | null;
  versionCount: number;
  articleCount: number;
  forkedFrom: { specId: string; handle: string; slug: string; version: string } | null;
  licence: 'MIT';
  licenceAcceptedAt: Timestamp | null;
  stats: { views: number; downloads: number; forks: number; updates: number };
  publishedAt: Timestamp | null;
  createdAt: Timestamp;
  updatedAt: Timestamp;
  deletedAt: Timestamp | null;
  hiddenByAdmin: boolean;
}

15.4 specs/{specId}/articles/{articleId}

interface ArticleDoc {
  id: string;
  specId: string;
  type: 'README' | 'CONSTITUTION' | 'ARTICLE';
  title: string;
  slug: string;
  summary: string | null;
  body: string;                   // markdown, <= 200_000 chars
  order: number;                  // gaps of 10
  rev: number;                    // optimistic locking
  wordCount: number;
  updatedBy: string;
  createdAt: Timestamp;
  updatedAt: Timestamp;
}

15.5 specs/{specId}/versions/{versionId}

versionId = the version string with dots replaced by underscores, zero-padded for ordering (e.g. v0001_0002_0000).

interface VersionDoc {
  id: string;
  specId: string;
  version: string;                // "1.2.0"
  major: number; minor: number; patch: number;
  changelog: string;              // markdown
  breaking: boolean;
  publishedBy: string;
  publishedAt: Timestamp;
  unpublishedAt: Timestamp | null;
  articleCount: number;
  totalChars: number;
  contentHash: string;            // sha256 of the ordered article hashes
  articles: {
    type: 'README' | 'CONSTITUTION' | 'ARTICLE';
    title: string; slug: string; summary: string | null;
    body: string; order: number; hash: string;
  }[];
  assets: { id: string; path: string; alt: string; caption: string | null }[];
  snapshotMeta: { title: string; description: string; tags: string[]; stack: string[]; category: string; structureMode: string };
}

A version document may approach Firestore's 1 MiB limit for very large specifications. If totalChars > 700_000, the publish function stores article bodies in Storage at specs/{specId}/versions/{versionId}/{slug}.md and leaves body: null with bodyPath set. The version reader must transparently handle both shapes.

15.6 searchIndex/{specId}

interface SearchIndexDoc {
  specId: string; ownerHandle: string; ownerDisplayName: string; ownerPhotoURL: string | null;
  title: string; titleLower: string; slug: string; description: string;
  keywords: string[];             // <= 100 normalised tokens
  category: string; tags: string[]; stack: string[]; contentLanguage: string;
  complexity: string; stage: string;
  coverUrl: string | null;
  latestVersion: string;
  downloads: number; forks: number; views: number;
  publishedAt: Timestamp; updatedAt: Timestamp;
}

Only public, non-deleted, non-hidden specifications have an index document. Deleting the index document is the mechanism for removing a spec from discovery.


16. Security rules (specification, not final code)

// Read
- users/{uid}: public fields readable by anyone; email readable only by self and admin.
  (Implement by keeping email in a separate `usersPrivate/{uid}` doc.)
- searchIndex/*: readable by anyone.
- specs/{id}: readable if (visibility == 'public' && !deletedAt && !hiddenByAdmin)
              || ownerUid == auth.uid
              || auth.uid in collaboratorUids
              || isAdmin();
- specs/{id}/articles/*, versions/*, assets/*: same condition as the parent spec.
- specs/{id}/stats/*: owner, collaborators, admin only.

// Write
- users/{uid}: create/update only by self; `isAdmin`, `stats`, `handle` immutable from client
               (handle changes go through the `changeHandle` function).
- handles/{handle}: create only via transaction from `claimHandle` function.
- specs/{id}: create by authenticated user with ownerUid == auth.uid, stats zeroed,
              latestVersion null, visibility 'private', deletedAt null;
              update by owner/collaborator but NEVER on fields:
                ownerUid, stats, versionCount, latestVersion*, publishedAt, licenceAcceptedAt, hiddenByAdmin;
              visibility/stage/collaboratorUids updatable by owner only;
              delete: denied (soft delete via function only).
- articles/*: create/update/delete by owner/collaborator; enforce body size,
              type README uniqueness (checked in function on create), rev increment by exactly 1.
- versions/*: client writes DENIED entirely (functions only).
- stats/*, searchIndex/*, platform/*, contactMessages/*, reports/*: client writes DENIED.

// Storage rules
- specs/{specId}/assets/{file}: read follows the spec's visibility (use a mirrored metadata check
  or generate signed URLs from a function for private specs);
  write only by owner/collaborator, contentType in the allow-list, size <= 5 MB.

Rules must be covered by tests using the Firebase emulator (@firebase/rules-unit-testing), at minimum: anonymous read of public, anonymous read denied on private, collaborator write allowed, non-collaborator write denied, counter tampering denied, version write denied.


17. Cloud Functions

Function Type Purpose
onUserCreate Auth trigger Create users/{uid} skeleton, usersPrivate/{uid}
claimHandle Callable Transactional handle reservation + profile completion
changeHandle Callable Handle change with 30-day cooldown and alias creation
createSpecification Callable Create spec + README (+ template articles) atomically
publishSpecification Callable Validate, snapshot, bump version, update index and stats
unpublishVersion Callable Mark version unpublished, recompute latestVersion
forkSpecification Callable Copy version into caller's account, attribution, increment forkCount
duplicateSpecification Callable Copy working copy within the same account
setVisibility Callable Validate publish preconditions, write/delete searchIndex doc
deleteSpecification Callable Soft delete, remove index doc
registerDownload Callable / HTTPS Deduplicated download counting
registerView HTTPS (beacon) Deduplicated view counting
downloadBundle HTTPS Server-side ZIP fallback, streams the archive
submitContactForm Callable reCAPTCHA verify, rate limit, store, e-mail
reportContent Callable Abuse report intake
deleteAccount Callable GDPR deletion / anonymisation
rollupStats Scheduled hourly Platform totals, user totals
purgeDeleted Scheduled daily Hard-delete soft-deleted items older than 30 days
sanitizeUpload Storage trigger Validate MIME, strip SVG scripts, generate thumbnails and OG images

All callables: authenticated where required, Zod-validated input, structured error codes (invalid-argument, permission-denied, failed-precondition, resource-exhausted), and per-uid rate limiting via a rateLimits/{uid} document.


18. Angular application design

18.1 Stack

  • Angular 20+ standalone components, signals, provideRouter with lazy loadComponent/loadChildren.
  • Angular SSR (@angular/ssr) with hydration; prerender static pages at build time.
  • AngularFire (@angular/fire) for Auth, Firestore, Storage, Functions.
  • Markdown: markdown-it + plugins (footnote, task-lists, attrs, container for callouts) + Prism/Shiki for highlighting + DOMPurify for sanitisation + mermaid (lazy).
  • Diff: diff (jsdiff) for line diff, custom word-level refinement.
  • ZIP: jszip.
  • UI: Angular Material 3 with a custom theme (design tokens in SCSS), or a hand-rolled component set — pick one and stay consistent. Prefer Material for accessible primitives (dialog, menu, tabs, tooltip).
  • i18n: @ngx-translate/core with JSON dictionaries in /public/i18n/{lang}.json.
  • State: signal-based services; no NgRx.

18.2 Folder structure

src/app/
├── core/
│   ├── auth/            (auth.service, auth.guard, owner.guard, collaborator.guard)
│   ├── data/            (spec.repository, article.repository, version.repository,
│   │                     user.repository, search.repository, stats.repository)
│   ├── models/          (all interfaces from §15)
│   ├── i18n/            (translate setup, language.service)
│   ├── seo/             (seo.service — title, meta, canonical, OG, JSON-LD)
│   └── util/            (slugify, semver, hash, debounce, clipboard)
├── shared/
│   ├── ui/              (spec-card, tag-chip, stage-badge, empty-state, skeleton,
│   │                     confirm-dialog, copy-button, avatar, licence-badge)
│   ├── markdown/        (markdown-renderer, markdown-editor, toolbar, mermaid-block,
│   │                     internal-link.directive, sanitiser.config)
│   └── pipes/           (relative-time, number-compact, safe-html)
└── features/
    ├── landing/
    ├── catalogue/
    ├── spec-view/       (overview, article, versions, version-view)
    ├── spec-edit/       (editor shell, article-tree, article-editor, structure-health,
    │                     publish-dialog, conflict-dialog)
    ├── spec-compare/
    ├── spec-settings/
    ├── spec-stats/
    ├── spec-create/
    ├── dashboard/
    ├── profile/
    ├── account/
    └── static/          (about, privacy, terms, contact, licence, not-found)

18.3 SEO

  • SeoService sets title, description, canonical, hreflang alternates, Open Graph and Twitter cards per route.
  • JSON-LD on specification pages: TechArticle / SoftwareSourceCode with license: https://opensource.org/licenses/MIT, author, datePublished, version.
  • Dynamic sitemap.xml generated by a scheduled function listing all public specifications; robots.txt disallowing /edit, /settings, /stats, /dashboard.
  • OG images generated per specification (cover screenshot or generated card with title + author).

18.4 Design direction

Clean, documentation-like, high legibility. Content column max 78ch. A monospace-accented visual identity (the product is about specifications, not marketing). Light and dark themes following prefers-color-scheme with a manual override. Generous whitespace, restrained accent colour, no decorative imagery on content pages.


19. Deployment

  • Firebase Hosting with SSR (Cloud Run / Firebase App Hosting), custom domain openspeckit.com with www → apex redirect, forced HTTPS, HSTS.
  • Environments: dev (emulators + a openspeckit-dev project) and prod.
  • CI/CD (GitHub Actions): on PR — lint, unit tests, rules tests against emulators, build; on merge to main — deploy hosting, functions, rules and indexes.
  • Firebase Emulator Suite for local development, with a seed script (npm run seed) creating: 3 users, 3 templates, 12 public specifications with 2–4 versions each and realistic statistics.
  • Security headers: CSP (allowing only self, Firebase endpoints and Google auth domains), X-Content-Type-Options, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy.
  • Error tracking via a lightweight, cookieless solution; log Cloud Function errors to Cloud Logging with structured fields.

20. Milestones

# Milestone Contents Done when
M0 Foundations Repo, Angular + SSR skeleton, Firebase projects, emulators, CI, design tokens, i18n, layout shell, static pages /, /about, /privacy, /terms, /contact live and prerendered
M1 Identity Google auth, onboarding, handles, profile, account settings, guards, rules + rules tests AC-1.1 … AC-1.4
M2 Authoring core Spec CRUD, articles, structure, markdown editor with preview, autosave, asset upload AC-3.x, AC-4.x
M3 Versions Publish, version list, version view, restore, immutability rules AC-5.1, AC-5.2, AC-5.5
M4 Public surface Visibility, catalogue, search index, specification public pages, SEO, sitemap AC-2.2, AC-7.x
M5 Distribution ZIP/markdown/JSON/clipboard export, speckit.json, licence flow, download counting AC-6.x
M6 Compare & fork Diff engine, compare UI, fork, duplicate, attribution, fork lists AC-5.3, AC-5.4
M7 Insight Statistics pipeline, owner stats page, platform counters on landing AC-8.x
M8 Hardening Accessibility audit, Lighthouse pass, moderation tools, abuse reports, GDPR deletion, seed content, launch §2 targets met, 20 seeded specifications published

21. Definition of done (global)

A feature is done when:

  1. Its acceptance criteria pass.
  2. Firestore/Storage rules enforce its authorisation and have tests.
  3. It works keyboard-only and passes an axe scan with no serious/critical issues.
  4. Both pl and en strings exist.
  5. Loading, empty and error states are implemented — never a blank screen or an unhandled rejection.
  6. Public pages render server-side without client-only errors.
  7. No console.log or any in the shipped code.

22. Post-v1 backlog (do not implement now)

  • AI-assisted specification drafting and review inside the editor.
  • Comments and suggestions on articles; pull-request-style change proposals between forks.
  • Stars/bookmarks and personal collections.
  • Organisations/teams with shared ownership.
  • Public REST API (GET /api/v1/specs/:handle/:slug/:version) and a CLI (npx openspeckit pull handle/slug).
  • Quality score and badges (structure completeness, article depth, screenshot presence).
  • Embeddable "Open in agent" buttons for external sites.
  • Webhooks on new versions.
  • Multi-language variants of a single specification.

Discussion

0 comments

No comments yet. Start the discussion.