From e34974ea8e4a257455594b2a9ec1e1c0132903dd Mon Sep 17 00:00:00 2001 From: vict0rsch Date: Sat, 30 May 2026 00:38:23 +0200 Subject: [PATCH 1/2] feat: initial agentic infra constitution + rules + grill-with-docs skill --- .../rules/grill-with-docs-for-new-features.md | 10 +++ .../keep-system-architecture-up-to-date.md | 18 ++++ .claude/rules/mirror-providers.md | 23 +++++ .claude/skills/grill-with-docs/ADR-FORMAT.md | 47 ++++++++++ .../skills/grill-with-docs/CONTEXT-FORMAT.md | 77 ++++++++++++++++ .claude/skills/grill-with-docs/SKILL.md | 88 +++++++++++++++++++ .../grill-with-docs-for-new-features.mdc | 15 ++++ .../keep-system-architecture-up-to-date.mdc | 18 ++++ .cursor/rules/mirror-providers.mdc | 23 +++++ .cursor/skills/grill-with-docs/ADR-FORMAT.md | 47 ++++++++++ .../skills/grill-with-docs/CONTEXT-FORMAT.md | 77 ++++++++++++++++ .cursor/skills/grill-with-docs/SKILL.md | 88 +++++++++++++++++++ AGENTS.md | 83 +++++++++++++++++ 13 files changed, 614 insertions(+) create mode 100644 .claude/rules/grill-with-docs-for-new-features.md create mode 100644 .claude/rules/keep-system-architecture-up-to-date.md create mode 100644 .claude/rules/mirror-providers.md create mode 100644 .claude/skills/grill-with-docs/ADR-FORMAT.md create mode 100644 .claude/skills/grill-with-docs/CONTEXT-FORMAT.md create mode 100644 .claude/skills/grill-with-docs/SKILL.md create mode 100644 .cursor/rules/grill-with-docs-for-new-features.mdc create mode 100644 .cursor/rules/keep-system-architecture-up-to-date.mdc create mode 100644 .cursor/rules/mirror-providers.mdc create mode 100644 .cursor/skills/grill-with-docs/ADR-FORMAT.md create mode 100644 .cursor/skills/grill-with-docs/CONTEXT-FORMAT.md create mode 100644 .cursor/skills/grill-with-docs/SKILL.md create mode 100644 AGENTS.md diff --git a/.claude/rules/grill-with-docs-for-new-features.md b/.claude/rules/grill-with-docs-for-new-features.md new file mode 100644 index 00000000..8b9775c9 --- /dev/null +++ b/.claude/rules/grill-with-docs-for-new-features.md @@ -0,0 +1,10 @@ +# Use /grill-with-docs For Development + +When a user asks to develop this codebase, use the `/grill-with-docs` skill before implementation. + +## Required behavior + +- Start a grilling session to resolve terminology, scope, and trade-offs. +- Ask one question at a time and wait for user feedback before continuing. +- If a question can be answered from the codebase or docs, check there first. +- Proceed to implementation only after the development intent is clear. diff --git a/.claude/rules/keep-system-architecture-up-to-date.md b/.claude/rules/keep-system-architecture-up-to-date.md new file mode 100644 index 00000000..4244659f --- /dev/null +++ b/.claude/rules/keep-system-architecture-up-to-date.md @@ -0,0 +1,18 @@ +# Keep System Architecture Doc Up To Date + +## Policy + +`system-architecture.md` is the source of truth for the repository architecture. + +## Required behavior + +When a task changes the system architecture (components, boundaries, data flow, key abstractions, integrations, or deployment topology), update `system-architecture.md` in the same task. + +If no architecture impact exists, explicitly confirm that no update is needed. + +## Verification checklist + +- Architecture-impacting code changes include corresponding `system-architecture.md` updates. +- New or changed architectural terms are reflected consistently. +- Mermaid diagrams remain accurate when affected. +- No secrets are introduced in documentation. diff --git a/.claude/rules/mirror-providers.md b/.claude/rules/mirror-providers.md new file mode 100644 index 00000000..a289117e --- /dev/null +++ b/.claude/rules/mirror-providers.md @@ -0,0 +1,23 @@ +# Mirror Providers Rule + +## Policy + +Keep both provider folders aligned: + +- skills are mirrored between `.claude/skills/` and `.cursor/skills/` +- rules are mirrored between `.claude/rules/` and `.cursor/rules/` + +## Required behavior + +When creating, updating, renaming, or deleting a skill or rule in one provider folder, apply the equivalent change to the other provider folder in the same task. + +## Allowed differences + +Only vendor-required differences are allowed (format or loader specifics). Core guidance and intent must remain equivalent. + +## Verification checklist + +- Both provider folders contain equivalent skill directories and rule files. +- Names map consistently across providers. +- Referenced local docs/scripts resolve in each provider folder. +- No secrets are introduced. diff --git a/.claude/skills/grill-with-docs/ADR-FORMAT.md b/.claude/skills/grill-with-docs/ADR-FORMAT.md new file mode 100644 index 00000000..da7e78ec --- /dev/null +++ b/.claude/skills/grill-with-docs/ADR-FORMAT.md @@ -0,0 +1,47 @@ +# ADR Format + +ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. + +Create the `docs/adr/` directory lazily — only when the first ADR is needed. + +## Template + +```md +# {Short title of the decision} + +{1-3 sentences: what's the context, what did we decide, and why.} +``` + +That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most ADRs won't need them. + +- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited +- **Considered Options** — only when the rejected alternatives are worth remembering +- **Consequences** — only when non-obvious downstream effects need to be called out + +## Numbering + +Scan `docs/adr/` for the highest existing number and increment by one. + +## When to offer an ADR + +All three of these must be true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." + +### What qualifies + +- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." +- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." +- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. +- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. +- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. +- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." +- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md b/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md new file mode 100644 index 00000000..ddfa247c --- /dev/null +++ b/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md @@ -0,0 +1,77 @@ +# CONTEXT.md Format + +## Structure + +```md +# {Context Name} + +{One or two sentence description of what this context is and why it exists.} + +## Language + +**Order**: +{A concise description of the term} +_Avoid_: Purchase, transaction + +**Invoice**: +A request for payment sent to a customer after delivery. +_Avoid_: Bill, payment request + +**Customer**: +A person or organization that places orders. +_Avoid_: Client, buyer, account + +## Relationships + +- An **Order** produces one or more **Invoices** +- An **Invoice** belongs to exactly one **Customer** + +## Example dialogue + +> **Dev:** "When a **Customer** places an **Order**, do we create the **Invoice** immediately?" +> **Domain expert:** "No — an **Invoice** is only generated once a **Fulfillment** is confirmed." + +## Flagged ambiguities + +- "account" was used to mean both **Customer** and **User** — resolved: these are distinct concepts. +``` + +## Rules + +- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid. +- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution. +- **Keep definitions tight.** One sentence max. Define what it IS, not what it does. +- **Show relationships.** Use bold term names and express cardinality where obvious. +- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. +- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. +- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts. + +## Single vs multi-context repos + +**Single context (most repos):** One `CONTEXT.md` at the repo root. + +**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other: + +```md +# Context Map + +## Contexts + +- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders +- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments +- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping + +## Relationships + +- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking +- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices +- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money` +``` + +The skill infers which structure applies: + +- If `CONTEXT-MAP.md` exists, read it to find contexts +- If only a root `CONTEXT.md` exists, single context +- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved + +When multiple contexts exist, infer which one the current topic relates to. If unclear, ask. diff --git a/.claude/skills/grill-with-docs/SKILL.md b/.claude/skills/grill-with-docs/SKILL.md new file mode 100644 index 00000000..5ea0aa91 --- /dev/null +++ b/.claude/skills/grill-with-docs/SKILL.md @@ -0,0 +1,88 @@ +--- +name: grill-with-docs +description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions. +--- + + + +Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. + +Ask the questions one at a time, waiting for feedback on each question before continuing. + +If a question can be answered by exploring the codebase, explore the codebase instead. + + + + + +## Domain awareness + +During codebase exploration, also look for existing documentation: + +### File structure + +Most repos have a single context: + +``` +/ +├── CONTEXT.md +├── docs/ +│ └── adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives: + +``` +/ +├── CONTEXT-MAP.md +├── docs/ +│ └── adr/ ← system-wide decisions +├── src/ +│ ├── ordering/ +│ │ ├── CONTEXT.md +│ │ └── docs/adr/ ← context-specific decisions +│ └── billing/ +│ ├── CONTEXT.md +│ └── docs/adr/ +``` + +Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed. + +## During the session + +### Challenge against the glossary + +When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" + +### Sharpen fuzzy language + +When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." + +### Discuss concrete scenarios + +When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. + +### Cross-reference with code + +When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" + +### Update CONTEXT.md inline + +When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). + +`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. + +### Offer ADRs sparingly + +Only offer to create an ADR when all three are true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will wonder "why did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). + + diff --git a/.cursor/rules/grill-with-docs-for-new-features.mdc b/.cursor/rules/grill-with-docs-for-new-features.mdc new file mode 100644 index 00000000..c6d92e36 --- /dev/null +++ b/.cursor/rules/grill-with-docs-for-new-features.mdc @@ -0,0 +1,15 @@ +--- +description: Use grill-with-docs for development work +alwaysApply: true +--- + +# Use /grill-with-docs For Development + +When a user asks to develop this codebase, use the `/grill-with-docs` skill before implementation. + +## Required behavior + +- Start a grilling session to resolve terminology, scope, and trade-offs. +- Ask one question at a time and wait for user feedback before continuing. +- If a question can be answered from the codebase or docs, check there first. +- Proceed to implementation only after the development intent is clear. diff --git a/.cursor/rules/keep-system-architecture-up-to-date.mdc b/.cursor/rules/keep-system-architecture-up-to-date.mdc new file mode 100644 index 00000000..4244659f --- /dev/null +++ b/.cursor/rules/keep-system-architecture-up-to-date.mdc @@ -0,0 +1,18 @@ +# Keep System Architecture Doc Up To Date + +## Policy + +`system-architecture.md` is the source of truth for the repository architecture. + +## Required behavior + +When a task changes the system architecture (components, boundaries, data flow, key abstractions, integrations, or deployment topology), update `system-architecture.md` in the same task. + +If no architecture impact exists, explicitly confirm that no update is needed. + +## Verification checklist + +- Architecture-impacting code changes include corresponding `system-architecture.md` updates. +- New or changed architectural terms are reflected consistently. +- Mermaid diagrams remain accurate when affected. +- No secrets are introduced in documentation. diff --git a/.cursor/rules/mirror-providers.mdc b/.cursor/rules/mirror-providers.mdc new file mode 100644 index 00000000..3b67f533 --- /dev/null +++ b/.cursor/rules/mirror-providers.mdc @@ -0,0 +1,23 @@ +# Mirror Providers Rule + +## Policy + +Keep both provider folders aligned: + +- skills are mirrored between `.cursor/skills/` and `.claude/skills/` +- rules are mirrored between `.cursor/rules/` and `.claude/rules/` + +## Required behavior + +When creating, updating, renaming, or deleting a skill or rule in one provider folder, apply the equivalent change to the other provider folder in the same task. + +## Allowed differences + +Only vendor-required differences are allowed (format or loader specifics). Core guidance and intent must remain equivalent. + +## Verification checklist + +- Both provider folders contain equivalent skill directories and rule files. +- Names map consistently across providers. +- Referenced local docs/scripts resolve in each provider folder. +- No secrets are introduced. diff --git a/.cursor/skills/grill-with-docs/ADR-FORMAT.md b/.cursor/skills/grill-with-docs/ADR-FORMAT.md new file mode 100644 index 00000000..da7e78ec --- /dev/null +++ b/.cursor/skills/grill-with-docs/ADR-FORMAT.md @@ -0,0 +1,47 @@ +# ADR Format + +ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. + +Create the `docs/adr/` directory lazily — only when the first ADR is needed. + +## Template + +```md +# {Short title of the decision} + +{1-3 sentences: what's the context, what did we decide, and why.} +``` + +That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most ADRs won't need them. + +- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited +- **Considered Options** — only when the rejected alternatives are worth remembering +- **Consequences** — only when non-obvious downstream effects need to be called out + +## Numbering + +Scan `docs/adr/` for the highest existing number and increment by one. + +## When to offer an ADR + +All three of these must be true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." + +### What qualifies + +- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." +- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." +- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. +- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. +- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. +- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." +- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md b/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md new file mode 100644 index 00000000..ddfa247c --- /dev/null +++ b/.cursor/skills/grill-with-docs/CONTEXT-FORMAT.md @@ -0,0 +1,77 @@ +# CONTEXT.md Format + +## Structure + +```md +# {Context Name} + +{One or two sentence description of what this context is and why it exists.} + +## Language + +**Order**: +{A concise description of the term} +_Avoid_: Purchase, transaction + +**Invoice**: +A request for payment sent to a customer after delivery. +_Avoid_: Bill, payment request + +**Customer**: +A person or organization that places orders. +_Avoid_: Client, buyer, account + +## Relationships + +- An **Order** produces one or more **Invoices** +- An **Invoice** belongs to exactly one **Customer** + +## Example dialogue + +> **Dev:** "When a **Customer** places an **Order**, do we create the **Invoice** immediately?" +> **Domain expert:** "No — an **Invoice** is only generated once a **Fulfillment** is confirmed." + +## Flagged ambiguities + +- "account" was used to mean both **Customer** and **User** — resolved: these are distinct concepts. +``` + +## Rules + +- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid. +- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution. +- **Keep definitions tight.** One sentence max. Define what it IS, not what it does. +- **Show relationships.** Use bold term names and express cardinality where obvious. +- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. +- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. +- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts. + +## Single vs multi-context repos + +**Single context (most repos):** One `CONTEXT.md` at the repo root. + +**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other: + +```md +# Context Map + +## Contexts + +- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders +- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments +- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping + +## Relationships + +- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking +- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices +- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money` +``` + +The skill infers which structure applies: + +- If `CONTEXT-MAP.md` exists, read it to find contexts +- If only a root `CONTEXT.md` exists, single context +- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved + +When multiple contexts exist, infer which one the current topic relates to. If unclear, ask. diff --git a/.cursor/skills/grill-with-docs/SKILL.md b/.cursor/skills/grill-with-docs/SKILL.md new file mode 100644 index 00000000..5ea0aa91 --- /dev/null +++ b/.cursor/skills/grill-with-docs/SKILL.md @@ -0,0 +1,88 @@ +--- +name: grill-with-docs +description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions. +--- + + + +Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. + +Ask the questions one at a time, waiting for feedback on each question before continuing. + +If a question can be answered by exploring the codebase, explore the codebase instead. + + + + + +## Domain awareness + +During codebase exploration, also look for existing documentation: + +### File structure + +Most repos have a single context: + +``` +/ +├── CONTEXT.md +├── docs/ +│ └── adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives: + +``` +/ +├── CONTEXT-MAP.md +├── docs/ +│ └── adr/ ← system-wide decisions +├── src/ +│ ├── ordering/ +│ │ ├── CONTEXT.md +│ │ └── docs/adr/ ← context-specific decisions +│ └── billing/ +│ ├── CONTEXT.md +│ └── docs/adr/ +``` + +Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed. + +## During the session + +### Challenge against the glossary + +When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" + +### Sharpen fuzzy language + +When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." + +### Discuss concrete scenarios + +When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. + +### Cross-reference with code + +When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" + +### Update CONTEXT.md inline + +When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). + +`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. + +### Offer ADRs sparingly + +Only offer to create an ADR when all three are true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will wonder "why did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). + + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..fba70d90 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,83 @@ +# 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. +- Read `system-architecture.md` first to understand repository structure and boundaries before broad grepping/searching across the entire codebase. +- If the user's request is too vague or short or unclear, use the "grill-with-docs" skill to clarify the request. +- Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +# 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +# 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +# 4. Plans Must Be Executable By Small Models + +**Every plan must be clear, exhaustive, imperative, and verifiable.** + +When writing a plan: +- Use numbered imperative steps (`Do X`, `Then do Y`), never vague goals. +- Make the plan exhaustive for the requested scope: no hidden steps, no implicit work. +- State assumptions and prerequisites explicitly before execution steps. +- For each step, define an objective verification check (test, command, observable output, or diff condition). +- Define concrete completion criteria so an agent can truthfully decide "done" vs "not done." +- Use language that a small model can follow literally without guessing intent. + +Hard rule: +- If a small-capability agent could not implement the plan truthfully from the plan text alone, the plan is invalid and must be rewritten. + +# 5. Agent Entry Point (Shared Across Providers) + +Provider-specific rule files are the source of truth for cross-provider mirroring: + +- `.cursor/rules/mirror-providers.mdc` +- `.claude/rules/mirror-providers.md` + +# 6. Be safe, be consistent + +**Security is a hard gate. A task is not done until this section is satisfied.** + +Always perform a security pass while reading and writing code: +- Treat secrets exposure as critical (API keys, tokens, passwords, credentials, private keys, webhook URLs, `.env` contents). +- Never hardcode secrets in code, tests, docs, examples, commits, PR text, logs, or terminal snippets. +- Do not move secrets into "safe-looking" files (fixtures, sample configs, scripts). If a value is sensitive, keep it out. +- If you detect a leak or high-risk pattern, stop and warn clearly. Do this even when it is outside the original request. + +Before declaring completion, run a critical consistency review of your own work: +- Verify the result is consistent with sections 1-4: explicit assumptions, minimal scope, and surgical edits only. +- Confirm every changed line traces directly to the user's request (no speculative features, no side-quest refactors). +- Check that your naming, structure, and behavior align with existing project language and conventions. +- Check consistency with documentation produced by the `/grill-with-docs` process (`CONTEXT.md`, `CONTEXT-MAP.md`, and relevant ADRs). If code and docs disagree, flag it explicitly. +- Challenge your own decisions: "Where did I overreach?" and "What part is inconsistent with the stated principles?" +- If inconsistencies remain, report them explicitly and propose the smallest concrete fix. + +Completion rule: +- Do not present the task as complete until all checks are done: (1) secret/leak/security scan, (2) critical consistency self-review, (3) consistency check against `/grill-with-docs` artifacts (`CONTEXT.md`, `CONTEXT-MAP.md`, relevant ADRs). \ No newline at end of file From 201dd82614dfe90ea571ec6f9115358c63fc185a Mon Sep 17 00:00:00 2001 From: vict0rsch Date: Sat, 30 May 2026 02:25:52 +0200 Subject: [PATCH 2/2] feat: add system-architecture --- contributing.md | 42 ++--- system-architecture.md | 366 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 387 insertions(+), 21 deletions(-) create mode 100644 system-architecture.md diff --git a/contributing.md b/contributing.md index d43650a6..9921818d 100644 --- a/contributing.md +++ b/contributing.md @@ -8,6 +8,10 @@ The only external deps are [`tom-select`](https://tom-select.js.org/) (tag input The project uses modern ES modules and [WXT](https://wxt.dev/) for bundling, dev server, manifest generation, and cross-browser packaging. +### Related documentation + +**This guide covers how to set up, build, and contribute** (commands, conventions, step-by-step how-tos, tests, release). For **how the system works** — the mental model, data flow, core abstractions, component responsibilities, and design rationale — see [`system-architecture.md`](system-architecture.md) (e.g. its [Technical Stack](system-architecture.md#technical-stack) for the full dependency and tooling list). + ## Set-up 1. [Install `npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) (Node.js 22+ recommended) @@ -25,7 +29,7 @@ The project uses modern ES modules and [WXT](https://wxt.dev/) for bundling, dev - **Dev server**: Hot module reloading with `npm run dev` - **Zip packaging**: `npm run zip` produces ready-to-submit archives for both browsers -You do **not** need to know WXT internals to contribute. The key thing to know is that WXT discovers entry points from `src/entrypoints/` and builds them into `dist/`. +You do **not** need to know WXT internals to contribute. The key thing to know is that WXT discovers entry points from `src/entrypoints/` and builds them into `dist/`. For how WXT (and these entry points) fit the overall architecture, see [`system-architecture.md` → System Architecture](system-architecture.md#system-architecture). ## Build commands @@ -217,26 +221,17 @@ Files in `public/` are copied as-is to the build output. Use this for: ### Key source files for contributors -**Core Logic** (`src/shared/js/utils/`): - -- `config.js` — Global state and settings. Start here to understand data structures. Re-exports `knownPaperPages` / `preprintSources` from `sources/index.js` for backward compatibility. -- `functions.js` — Utility functions used everywhere. -- `data.js` — How papers are stored, validated, and migrated between versions. -- `sources/` — **Add new paper sources here** (`sources/.js` + register in `sources/index.js`). See below. -- `parsers.js` — Shared helpers (fetchDom, fetchText, BibTeX glue, DC meta extraction, …) used by source modules. -- `preprintMatching.js` — Cross-source enrichment in `addOrUpdatePaper` (CrossRef, DBLP, Semantic Scholar, …). -- `paper.js` — Paper object operations: `makePaper`, updates, linking, `paperToAbs` / `paperToPDF`. - -**User Interface** (`src/popup/js/`): +The tree above shows **where** each file lives. For **what each component does and +how they relate** (responsibilities, data flow, the parse → enrich → store +pipeline), see [`system-architecture.md` → Component Deep-Dive](system-architecture.md#component-deep-dive). +A few starting points for common contributions: -- `popup.js` — Main popup logic. How the extension popup initializes and displays papers. -- `memory.js` — Memory table functionality. Search, sorting, and display of saved papers. -- `handlers.js` — User interactions. Button clicks, keyboard shortcuts, form handling. - -**Background Processing**: - -- `src/background/background.js` — Handles sync, notifications, and browser API calls. -- `src/content_scripts/content_script.js` — Automatically detects papers when browsing websites. +- **Understand the data structures** → `src/shared/js/utils/config.js` (the `state` + shape) and `data.js` (paper schema in `validatePaper`). +- **Add a paper source** → `src/shared/js/utils/sources/` ([Adding a paper source](#adding-a-paper-source)). +- **Add a paper attribute** → `src/shared/js/utils/data.js` ([Creating a new paper attribute](#creating-a-new-paper-attribute)). +- **Popup / UI work** → `src/popup/js/` (`popup.js`, `memory.js`, `handlers.js`, `templates.js`). +- **Background / content-script behavior** → `src/background/background.js`, `src/content_scripts/content_script.js`. ### Prettier @@ -256,6 +251,11 @@ Most editors can auto-format on save with the Prettier extension. For VS Code, i ## Adding a paper source +> For the conceptual model behind sources (the registry, the `is` map dispatch, and +> the **load-order/TDZ constraint** you must respect — never call `functions.js` +> exports at a source module's top level), see +> [`system-architecture.md` → Core Concepts & Abstractions](system-architecture.md#core-concepts--abstractions). + 1. **Create** `src/shared/js/utils/sources/.js` exporting a class that extends `BasePaperSource` from `sources/base.js`. Implement at minimum: - `name` (lowercase key, e.g. `"arxiv"`) - `displayName` (human label) @@ -270,7 +270,7 @@ Most editors can auto-format on save with the Prettier extension. For VS Code, i ### Why stateless handlers, not class instances -Saved papers are **plain objects** (JSON in `chrome.storage`, messages, Gist sync). MV3 service workers also terminate often, so there is no long-lived "paper with methods" to hydrate. Each source module exports a **stateless** subclass of `BasePaperSource`: the registry does a name lookup and calls static-ish methods on the class (`ArxivSource.parse`, …). That matches one hash lookup per dispatch, same cost order as the old large `switch` statements, without prototype loss across storage boundaries. +Sources are **stateless** `BasePaperSource` subclasses (the registry does a name lookup and calls static methods like `ArxivSource.parse`), not instances. For the rationale — MV3 worker lifetime, plain-object serialization across storage/messages/Gist, and dispatch cost — see [`system-architecture.md` → Key Design Decisions](system-architecture.md#key-design-decisions). The load-order/TDZ constraint that sources must respect is documented in the same file under [Core Concepts & Abstractions](system-architecture.md#core-concepts--abstractions). ## Creating a new paper attribute diff --git a/system-architecture.md b/system-architecture.md new file mode 100644 index 00000000..e9ae501c --- /dev/null +++ b/system-architecture.md @@ -0,0 +1,366 @@ +# PaperMemory — System Architecture + +> Source of truth for the repository's architecture. Read this before broad +> grepping. Keep it current when components, boundaries, data flow, key +> abstractions, or integrations change (see `.claude/rules/keep-system-architecture-up-to-date.md`). +> +> **This doc covers how the system works** — mental model, data flow, +> abstractions, design rationale, invariants. For how to set up, build, and +> contribute (commands, conventions, step-by-step how-tos, tests, release), see +> [`contributing.md`](contributing.md). +> +> **This doc covers how the system works** — mental model, data flow, +> abstractions, design rationale, invariants. For how to set up, build, and +> contribute (commands, conventions, step-by-step how-tos, tests, release), see +> [`contributing.md`](contributing.md). + +## Overview + +**PaperMemory** is a cross-browser (Chrome/Brave/Edge + Firefox) WebExtension that +acts as an automated, minimalist reference manager for research papers. As the user +browses, it silently detects when a tab is a known paper page (arXiv, OpenReview, +NeurIPS, Nature, IEEE, and ~30 other academic sources), parses the paper's metadata, +and records it in a local library ("the Memory"). On top of recording, it enriches +papers: it discovers code repositories, matches preprints to their published +versions across several bibliographic databases, generates BibTeX and Markdown +links, rewrites PDF tab titles to human-readable names, and can sync the whole +library to a private GitHub Gist. + +The system is a **client-side-only** application: there is no backend owned by the +project. All persistence is in `chrome.storage.local`; all enrichment happens by +calling third-party public APIs directly from the extension. The "server" role is +played entirely by the **background service worker**, which brokers +cross-origin fetches and privileged browser APIs on behalf of UI surfaces and +content scripts. + +The target reader of this document is an engineer who needs to reason about how a +URL becomes a stored, enriched paper — and how to add a new paper source or UI +surface without breaking the (deliberately load-order-sensitive) module graph. + +## System Architecture + +PaperMemory is built with **[WXT](https://wxt.dev/)** (`wxt.config.js`), which +compiles the `src/` tree into a Manifest V3 extension. WXT's role is purely +build-time: it wires `src/entrypoints/*` into manifest-declared contexts (background +worker, content script, and four HTML pages), applies the `@pm` / `@pmu` path +aliases, and produces per-browser bundles. There is no runtime framework — the code +is plain ES modules plus jQuery and Tom Select for DOM work. + +```mermaid +graph TB + subgraph Browser["Browser (per-tab + extension contexts)"] + subgraph CS["Content Script — every page (<all_urls>)"] + CScript["content_script.js
detect paper page,
inject feedback UI"] + end + subgraph UI["UI Surfaces (HTML pages)"] + Popup["popup
memory browser + actions"] + FullMem["fullMemory
full-tab library view"] + Options["options
preferences + import/export"] + BibM["bibMatcher
batch BibTeX enrichment"] + end + subgraph BG["Background Service Worker"] + BGScript["background.js
message router,
privileged-API broker"] + end + end + + subgraph Shared["src/shared/js/utils — shared core (imported by every context)"] + Paper["paper.js
addOrUpdatePaper orchestrator"] + State["state.js + config.js
in-memory state, prefs"] + Data["data.js
storage, migration, validation"] + Sources["sources/*
plugin registry (BasePaperSource)"] + Sync["sync.js
GitHub Gist sync"] + Preprint["preprintMatching.js
CrossRef / DBLP / S2 / Unpaywall"] + Urls["urls.js · parsers.js · files.js · functions.js"] + end + + subgraph External["Third-party APIs (called directly)"] + Arxiv[(arXiv API)] + OR[(OpenReview API)] + Gist[(GitHub Gist)] + Match[(CrossRef / DBLP /
Semantic Scholar /
Unpaywall)] + end + + CScript -->|addOrUpdatePaper| Paper + Popup --> Paper + BibM --> Preprint + Paper --> Sources + Paper --> Data + Paper --> Preprint + Paper --> State + Paper -->|pushToRemote| Sync + CScript -.->|chrome.runtime message| BGScript + Popup -.->|chrome.runtime message| BGScript + BGScript -->|brokered fetch| Arxiv + BGScript -->|brokered fetch| OR + BGScript --> Match + Sync --> Gist + Data --> Storage[("chrome.storage.local")] + State --> Storage +``` + +### Communication patterns + +- **Shared-core imports.** The bulk of logic lives in `src/shared/js/utils/` and is + imported directly into whichever context needs it (popup, content script, worker). + There is one logical codebase executed in several JS realms; `state`/`config.js` + is a per-realm singleton, **not** shared memory across contexts. +- **Message passing (`chrome.runtime`).** Used for two things: (1) the content + script and popup ask the background worker to perform privileged or + cross-origin operations they cannot do themselves; (2) the worker pushes + commands (keyboard shortcuts, tab-URL changes) into content scripts. The router is + the single `chrome.runtime.onMessage` listener in `background.js` (a `type`-keyed + `if/else` chain). It rejects messages whose `sender.id !== chrome.runtime.id`. +- **Storage as the integration bus.** All contexts read/write the same + `chrome.storage.local` `papers` object. `addOrUpdatePaper` re-reads `papers` + immediately before every write and merges, because multiple tabs may parse the + same paper concurrently (see *Concurrency* below). + +### Why the background worker brokers fetches + +Several source parsers (`arxiv.js` via `fetch-arxiv-xml`, `openreview` via +`OpenReview*JSON`) and all preprint-matching calls are routed through the worker +even though `fetch` exists in content scripts. The worker holds `host_permissions: +["*://*/*"]`, so it can issue cross-origin requests without being subject to the +visited page's CSP or CORS posture. Content scripts delegate via +`sendMessageToBackground({ type: ... })`. + +## Core Concepts & Abstractions + +### Paper sources — the plugin registry + +The defining abstraction is the **paper source**: a class extending +`BasePaperSource` ([`sources/base.js`](src/shared/js/utils/sources/base.js)) that +knows how to recognise and parse one family of URLs. Sources are **stateless** — +the class is never instantiated; only static members are used, and papers stay +plain objects. Each source declares: + +- `name` — internal key, matches `paper.source` and the `is.{name}` flag. +- `displayName`, `isPreprint`, and `patterns` (string substrings or + `(url) => boolean` predicates). +- `matches(url)`, `urlToId(url, ctx)`, `parse(url, tab, ctx)`, plus URL + transforms `toAbs` / `toPDF` and venue helpers. + +All ~32 concrete sources are registered **once** in +[`sources/index.js`](src/shared/js/utils/sources/index.js)'s `ALL_SOURCES` array. +From that single list the module derives every lookup the rest of the code needs: +`getSource(name)`, `knownPaperPages` (consumed by `isPaper`), `sourceFromIs`, +`matchUrl`, `preprintSources`, and `SOURCE_DISPATCH_ORDER`. **Adding a source = create +the module + add one import/array entry.** Patterns must be mutually exclusive — this +is enforced by the "Source Pattern Mutual Exclusion" test in `test/test-meta.js`. +For the step-by-step procedure, see +[`contributing.md` → Adding a paper source](contributing.md#adding-a-paper-source). + +`WebsiteSource` is special: it is the universal fallback for arbitrary pages and is +**excluded** from `DISPATCH_SOURCES`. It is invoked explicitly in `paper.js` +(`getSource("website")`) when the user manually parses a non-academic page, never via +pattern dispatch. + +> **Load-order constraint (documented in `base.js`):** `functions.js` imports +> `getSource` from `sources/index.js`, which imports every concrete source — some of +> which import back from `functions.js`. This cycle is safe **only** because source +> modules consume `functions.js` bindings inside method bodies (runtime), never at +> module top level. Evaluating a `functions.js` export inside a static initialiser +> (e.g. `static patterns = [...]`) hits a Temporal Dead Zone and crashes the +> extension at load. + +### The "is" map and source dispatch + +[`isPaper(url)`](src/shared/js/utils/paper.js) returns an object `is` mapping every +source name → boolean (plus `localFile`, `stored`, `parsedWebsite`). This map is the +currency of dispatch: `sourceFromIs(is)` walks `DISPATCH_SOURCES` and returns the +first source whose flag is set, while `parseIdFromUrl` walks `SOURCE_DISPATCH_ORDER` +to resolve a stable id. Because patterns are mutually exclusive, dispatch order is +not semantically meaningful. + +### Stable IDs and de-duplication + +A paper's identity is its `id` (e.g. `Arxiv-2103.12345`, `Website_`). Two +fuzzy de-dup mechanisms keep the library clean: + +- **`urlHashToId`** — `miniHash(url) → id`, a fast cache so a revisited URL + resolves without re-parsing. +- **`titleHashToIds`** — `miniHash(title) → [ids]`, used by `findFuzzyPaperMatch` to + detect that a freshly parsed paper is the same work as an existing one under a + different source (e.g. an arXiv preprint and its NeurIPS published version). + `preprintSources` is consulted so a non-preprint match is preferred as the + canonical record. + +## Data Models + +### The `paper` object + +The single domain entity. It is a plain object validated (and defaulted) by +[`validatePaper`](src/shared/js/utils/data.js) against a schema of expected keys: + +| Field | Type | Notes | +|---|---|---| +| `id` | string | Unique PaperMemory id; primary key in `papers` | +| `source` | string | A registered source `name` | +| `title`, `author`, `year`, `venue` | string | Core metadata (`author` is ` and `-separated) | +| `bibtex`, `key`, `doi` | string | Citation data; `key` is the BibTeX key | +| `pdfLink`, `md` | string | PDF URL and a pre-rendered Markdown link | +| `codeLink`, `code` | string/object | Repo URL + raw PapersWithCode payload | +| `note` | string | Free-text + auto-generated "Accepted @ …" venue notes | +| `tags` | string[] | User + auto-tags (`autoTagPaper`) | +| `favorite`, `favoriteDate` | bool/string | Starring | +| `count`, `addDate`, `lastOpenDate` | number/string | Visit tracking + sort keys | +| `extras` | object | Optional per-source overflow | + +### Storage layout (`chrome.storage.local`) + +The `papers` key holds an `{ id → paper }` map plus a `__dataVersion` sentinel. +Other top-level keys: `prefs`/menu checks, `autoTags`, `ignoreSources`, +`urlHashToId`, sync state, and periodic backups. `unlimitedStorage` is requested so +the library can grow without quota errors. + +### Migration + +[`migrateData`](src/shared/js/utils/data.js) runs on every `initState`. It compares +the stored `__dataVersion` against a hardcoded `latestDataVersion` and applies an +ordered chain of `if (currentVersion < N)` blocks that mutate papers in place +(backfilling defaults, renaming sources, fixing links), recording a per-paper +`migrationSummaries` audit trail. A backup is taken before migrating. + +### In-memory state + +`config.js` exports a per-realm `state` singleton (current papers, sorted lists, +tags, prefs, the title-rendering function, hash caches, UI flags). `state.js` +`initState` is the canonical hydrator: load papers → backup → migrate → load prefs & +caches → (UI contexts only) match local files, sort, build tag set. It is invoked in +the worker, popup, content script, and full-memory page. + +## Component Deep-Dive + +### `paper.js` — the parse/enrich/store orchestrator +**Location:** [`src/shared/js/utils/paper.js`](src/shared/js/utils/paper.js) +The heart of the system. `addOrUpdatePaper({url, is, prefs, tab, ...})` is the single +funnel through which papers enter the Memory. Its pipeline: resolve id +(`parseIdFromUrl`) → update-visit-or-`makePaper` → fuzzy-dedup merge +(`mergePapers`) → PapersWithCode code discovery (`tryPWCMatch`) → write to storage → +`pushToRemote` → asynchronous preprint matching (`tryPreprintMatch`) to backfill +venue/bibtex → second write. Caller-supplied `contentScriptCallbacks` +(`update`/`preprints`/`done`/`feedback`) let the content script render progressive +feedback. `makePaper` dispatches to `src.parse()` and stamps `paper.source`. + +### `sources/*` — source plugins +**Location:** [`src/shared/js/utils/sources/`](src/shared/js/utils/sources/) +~32 `BasePaperSource` subclasses + the `index.js` registry + `WebsiteSource` +fallback. See *Core Concepts*. Source-specific scraping helpers were factored out of +`functions.js` into per-source modules (recent refactor, commits `1b18d72`/`ebf02e1`). + +### `background.js` — service worker +**Location:** [`src/background/background.js`](src/background/background.js) +(entrypoint wrapper at `src/entrypoints/background.js`). Hosts the message router, +brokered fetches (arXiv XML, OpenReview notes/forums, Google Scholar, the +preprint-matching APIs), the toolbar badge state machine +(`badgeOk/Wait/Error/Clear`), keyboard-command handlers (`manualParsing`, +`downloadPdf`, `defaultAction`), the PDF-title rewriter (`chrome.tabs.onUpdated` + +`chrome.scripting.executeScript`), and Gist push/pull. On popup disconnect it pushes +sync. + +### `content_script.js` — page integration +**Location:** [`src/content_scripts/content_script.js`](src/content_scripts/content_script.js) +Injected at `document_start` on ``. On load it runs `initSyncAndState`, +pings the worker (`hello`), then calls `isPaper(url)`; on a hit it invokes +`addOrUpdatePaper` and renders the floating feedback notification. Also listens for +worker-pushed commands and provides in-page actions (copy BibTeX/MD link, open +abstract/PDF/ar5iv/HuggingFace, etc., shared with the popup's `handlers.js`). + +### UI surfaces — `popup`, `fullMemory`, `options`, `bibMatcher` +**Location:** `src/popup/`, `src/fullMemory/`, `src/options/`, `src/bibMatcher/` +(thin `src/entrypoints/*/main.js` wrappers import the real logic + `debug.js`). +- **popup** ([`popup/js/popup.js`](src/popup/js/popup.js), `memory.js`, `handlers.js`, + `templates.js`) — primary surface: searchable/sortable memory list, per-paper + actions, the preferences menu, and on-open parsing of the active tab. HTML is + assembled from `popup/html/**` fragments via a custom `` Vite + plugin (`htmlIncludePlugin` in `wxt.config.js`). +- **fullMemory** — the popup's memory view in a full browser tab. +- **options** — preferences, custom PDF-title function, auto-tag rules, JSON/BibTeX + import & export, sync configuration. +- **bibMatcher** — a standalone tool: paste a `.bib` file, batch-match each entry + against DBLP/Semantic Scholar/CrossRef/Unpaywall/Google Scholar to fill in + published venues. Built directly on `preprintMatching.js`. + +### `sync.js` — GitHub Gist synchronisation +**Location:** [`src/shared/js/utils/sync.js`](src/shared/js/utils/sync.js) +Optional cross-device sync via a private GitHub Gist using a user-supplied Personal +Access Token (`@octokit/request`). `getIdentifier` stamps writes with a per-device +`__syncId` so a device ignores its own echoes on pull. `pushToRemote` is fired +after every storage write; pull/merge happens on init and popup connect. + +### Supporting utilities +`data.js` (storage/migration/validation/prefs), `urls.js` (id resolution), +`parsers.js` (DOM/JSON/BibTeX fetch helpers + DC meta-tag extraction), +`bibtexParser.js`, `files.js` (matching downloaded PDFs in `PaperMemoryStore/` via +the `downloads` API), `functions.js` (49 misc helpers incl. `miniHash`, +`sendMessageToBackground`), `miniquery.js` (a tiny jQuery-ish DOM helper). + +## Key Workflow — automatic capture of a paper + +1. User opens e.g. `https://arxiv.org/abs/2103.12345`. +2. WXT-injected **content script** runs `initSyncAndState`, pings the worker, + computes `is = isPaper(url)`. +3. `is.arxiv` is true → `addOrUpdatePaper({url, is, prefs})`. +4. `parseIdFromUrl` → `ArxivSource.urlToId` → `Arxiv-2103.12345` (de-dup against + `titleHashToIds` for an existing published version). +5. New id → `makePaper` → `ArxivSource.parse`, which asks the **worker** to fetch + `export.arxiv.org/api/query` (`fetch-arxiv-xml`), parses the Atom XML into a + `paper`, builds a BibTeX entry. +6. `initPaper` defaults fields, runs `autoTagPaper`, `validatePaper`. +7. PapersWithCode code discovery → write `papers` to storage → `pushToRemote`. +8. Async `tryPreprintMatch` queries CrossRef/DBLP/S2/Unpaywall to backfill the + published venue/bibtex; a second storage write persists it. +9. Content-script callbacks render "Saved ✓ (+ repo …)" feedback; the worker may + rewrite the PDF tab title. + +## Concurrency & Invariants + +- **Concurrent writers.** Multiple tabs can parse the same paper simultaneously. + `addOrUpdatePaper` re-reads `papers` from storage immediately before each + `chrome.storage.local.set` and merges (`mergePapers`, `incrementCount`) rather + than blindly overwriting. Treat `state.papers` as a stale snapshot, never the + source of truth at write time. +- **Pattern mutual exclusion** across sources is a hard invariant (test-enforced). +- **No top-level use of `functions.js` exports inside source modules** (TDZ — see + the `base.js` constraint above). +- **`state` is per-realm.** Do not assume the popup's `state` reflects the content + script's; reconcile through storage. + +## Technical Stack + +- **Build:** WXT 0.20 (Vite under the hood), Manifest V3, dual Chrome/Firefox output. +- **Runtime libs:** jQuery 4, Tom Select 2 (tag inputs), `@octokit/request` (Gist). +- **Language:** plain ES modules (`"type": "module"`), JSDoc-typed, no TypeScript; + `jsconfig.json` + `@pm`/`@pmu` path aliases. +- **Tooling:** Prettier; Mocha + jsdom + Puppeteer (stealth) for the + `test/test-*.js` suite (unit + headless-browser integration). `npm test` builds + Chrome first (`pretest`) then runs Mocha. +- **External APIs (no project backend):** arXiv, OpenReview, GitHub Gist, CrossRef, + DBLP, Semantic Scholar, Unpaywall, Google Scholar, (historically PapersWithCode — + its API is currently stubbed out in `background.js`). +- **Permissions:** `activeTab`, `storage`, `unlimitedStorage`, `downloads`(+`.open`), + `scripting`, and `host_permissions: *://*/*`. +- **Docs site:** MkDocs Material (`mkdocs.yml`, `docs/`) published to papermemory.org. + +## Key Design Decisions + +- **Stateless source classes over instances (Option A, per `base.js`).** Saved + papers are plain objects (JSON in `chrome.storage`, runtime messages, and Gist + sync), and MV3 service workers terminate frequently — so there is no long-lived + "paper with methods" to hydrate, and prototype identity would be lost across + every storage/message boundary anyway. Each source is therefore a stateless + `BasePaperSource` subclass: dispatch is a name lookup in the registry followed by + a static method call (`ArxivSource.parse`, …). That is one hash lookup per + dispatch — the same cost order as the large `switch` statements it replaced — + without the serialisation hazards of class instances. +- **Single registry array as the only edit point** for sources, with all lookups + derived from it — minimises the surface area and footguns of adding a source. +- **Background worker as a fetch/permission broker** rather than a stateful server — + keeps the architecture backend-free while sidestepping page CSP/CORS. +- **Storage-as-bus with read-before-write merges** instead of a lock — pragmatic + concurrency control for an extension with no shared memory across realms. +- **Gist-based sync with device `__syncId` tagging** — zero project infrastructure, + user owns their data and credentials. +- **Two-phase enrichment** (fast parse + store, then async venue/code backfill) so + the user gets immediate feedback while slow third-party lookups complete in the + background.