qapture2 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/init.cjs CHANGED
@@ -24,15 +24,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/bin/init.ts
27
- var path7 = __toESM(require("path"), 1);
28
- var fs7 = __toESM(require("fs"), 1);
27
+ var path6 = __toESM(require("path"), 1);
28
+ var fs6 = __toESM(require("fs"), 1);
29
29
  var process2 = __toESM(require("process"), 1);
30
30
 
31
31
  // src/artifacts/SKILL.md
32
- var SKILL_default = "---\nname: qapture\ndescription: >\n Activated when the user provides a `qa-notes-*.zip` file exported from\n Qapture. Reads the preamble block in `notes.md` (project context, stack, run\n commands, theme tokens, dev/test login credentials, red-zone coverage report,\n and invariants), flags any uncovered RED risk zones before acting, then works\n through each `## Point N` annotation (page, element selector, screenshot \u2192\n locate code \u2192 make the change \u2192 verify by running the app). Finally grades\n coverage against the red zones and reports.\n\n **No AI is bundled in Qapture \u2014 YOU are the AI reading these artifacts.**\n Qapture is a 100% client-side, keyless, network-free capture widget.\ntriggers:\n - qa-notes-*.zip\n---\n\n# Qapture \u2014 Agent Skill\n\n> **Core principle:** Qapture ships zero AI. No model, no API keys, no network\n> calls. The CLI is a plain deterministic scaffolder. **You** \u2014 the coding agent\n> reading this skill \u2014 are the AI. The developer used Qapture to capture\n> annotated screenshots + notes from their live app; your job is to act on them.\n\n---\n\n## What Is Qapture?\n\nQapture is a drop-in in-browser widget (Shadow DOM, keyless, no telemetry).\nTesters annotate the live app: click an element or draw a region, add a note,\nand the widget captures a screenshot automatically. When done, they export a\n`qa-notes-*.zip`. That ZIP is the hand-off to you.\n\n---\n\n## ZIP Layout\n\n```\nqa-notes-<timestamp>.zip\n\u251C\u2500\u2500 notes.md \u2190 ALWAYS read this first (see Step 1)\n\u2514\u2500\u2500 screenshots/\n \u251C\u2500\u2500 point-1.png\n \u251C\u2500\u2500 point-2.png\n \u2514\u2500\u2500 ...\n```\n\n### `notes.md` structure\n\n```\n[PREAMBLE BLOCK]\n Project name, one-liner, stack, run commands, theme tokens,\n Login Context (dev/test credentials \u2014 see security note below),\n Coverage Report (red/amber/green zone checklist),\n Invariants, Additional Context.\n\n---NOTES---\n\n## Point 1\nPage: /some/path\nSelector: #some-element (or [data-testid=\"foo\"] etc.)\nNote: the tester's free-text description of the issue / request\n\n## Point 2\n...\n```\n\n---\n\n## Step 1 \u2014 Read the Preamble First\n\nBefore touching any code, open `notes.md` and parse everything **above** the\n`---NOTES---` separator. Extract and internalize:\n\n| Section | What to do |\n| ------------------ | --------------------------------------------------------------------- |\n| **Project / Stack** | Understand the framework, router, ORM, and any unusual constraints. |\n| **Run Commands** | Know how to start the dev server and seed the database. |\n| **Theme Tokens** | Understand the colour palette so you don't introduce style regressions.|\n| **Login Context** | DEV/TEST/SEED credentials only. Use these to log in during verification. **Never log, forward, or commit these values.** |\n| **Coverage Report**| List of RED / AMBER / GREEN zones and whether they are covered. |\n| **Invariants** | Absolute rules you must never violate (e.g. \"prices \u2265 0\", \"checkout requires auth\"). |\n| **Conventions** | Codebase naming, file organisation, import rules, validation approach. |\n\n---\n\n## Step 2 \u2014 Flag Uncovered RED Zones Before Acting\n\nAfter reading the preamble, check the Coverage Report for any RED zones that\nare **not yet covered** by an annotation in this ZIP.\n\nIf uncovered RED zones exist, **report them to the developer first**:\n\n```\n\u26A0\uFE0F Uncovered RED zones detected:\n \u2022 /checkout/payment \u2014 no annotation in this export\n \u2022 /seller/payouts \u2014 no annotation in this export\n\nThese are money/auth/irreversible flows. Do you want me to proceed with the\ncovered points only, or will you add annotations for the red zones first?\n```\n\nWait for developer confirmation before proceeding if any RED zone is uncovered.\n\n---\n\n## Step 3 \u2014 Act on Each Point\n\nFor each `## Point N` section in `notes.md`:\n\n### 3a. Read the annotation\n\n- **Page** \u2014 the route/URL where the issue was captured.\n- **Selector** \u2014 the CSS selector or aria identifier for the element.\n- **Note** \u2014 the tester's description of the problem or change request.\n\n### 3b. Open the screenshot\n\nLoad `screenshots/point-N.png` to visually confirm what the tester saw.\nThe screenshot is truth \u2014 if the selector doesn't resolve, the screenshot tells\nyou what element they meant.\n\n### 3c. Locate the code\n\nUse the selector priority chain below to find the relevant source:\n\n| Priority | Selector type | Action |\n| -------- | -------------------------------------------------- | --------------------------------------------------- |\n| 1 | `#some-id` | `grep -r 'some-id'` in `src/` |\n| 2 | `[data-testid=\"foo\"]` / `[data-test]` / `[data-cy]` | grep for the attribute value |\n| 3 | `aria-label` on interactive elements | grep for the label string |\n| 4 | `name` attribute on form fields | grep for `name=\"...\"` in the relevant form file |\n| 5 | Structural (e.g. `.card:nth-of-type(2) > button`) | narrow by page route \u2192 component file \u2192 visual match with screenshot |\n| Fallback | Selector didn't resolve | Use the screenshot: identify the element visually, search by text content or component name |\n\nNarrow your search by the **Page** field to avoid editing the wrong route's code.\n\n### 3d. Make the change\n\n- Follow the project's **Conventions** (from the preamble).\n- Respect all **Invariants** \u2014 never violate them even if the annotation implies it.\n- If the change touches a RED zone (money / auth / irreversible state), add an\n explicit comment: `// QA: red-zone change \u2014 reviewed <date>`.\n- Do **not** edit `qa.config.ts`, `qa.preamble.md`, or any qapture plugin files.\n\n---\n\n## Step 4 \u2014 Verify the Fix\n\n1. Run the app using the **Run Commands** from the preamble.\n2. Log in as the relevant role using **Login Context** credentials.\n (These are DEV/TEST/SEED only \u2014 never use production credentials.)\n3. Navigate to the **Page** listed in the annotation.\n4. **Reproduce** the original issue to confirm it existed, then verify it is fixed.\n5. In the browser console, run `document.querySelector('<selector>')` to confirm\n the element resolves as expected.\n6. Check adjacent paths for regressions, especially if the change is in a shared\n component.\n\n---\n\n## Step 5 \u2014 Grade and Report\n\nAfter acting on all points, produce a short report:\n\n```markdown\n## Qapture \u2014 Changes Summary\n\n| Point | Page | Change made | Verified | Risk |\n| ----- | --------------- | ----------------------------- | -------- | ----- |\n| 1 | /products | Fixed button label | \u2713 | green |\n| 2 | /checkout | Corrected total calculation | \u2713 | red |\n\n### Coverage vs Red Zones\n- [x] /checkout/payment \u2014 covered by Point 2\n- [ ] /seller/payouts \u2014 NOT covered (flagged in Step 2)\n\n### Uncovered items\nNone (all annotated points addressed).\n```\n\n---\n\n## Risk Zone Reference\n\n| Risk | Examples | Rule |\n| ------- | ----------------------------------------------------------------- | ------------------------------------------------ |\n| **red** | Payment, checkout, auth, order state, refunds, payouts, user data | Never change without developer review + comment |\n| **amber** | Cart, product listings, search, seller dashboard, inventory | Change carefully; verify end-to-end |\n| **green** | Static content, labels, colours, copy, tooltips, layout | Change freely; quick smoke-test |\n\n---\n\n## Security Note\n\n- **Dev/test/seed credentials only.** The Login Context in `notes.md` contains\n credentials from `.env.example` or seeder files \u2014 never from production.\n- **Never log, forward, store, or commit** Login Context values outside the\n development environment.\n- **Never read** `.env`, `.env.local`, `.env.production`, or any `secrets/`\n path. Qapture's CLI enforces this; you must too.\n- Qapture is **100% client-side** \u2014 it makes no network calls, holds no API\n keys, and sends no data anywhere.\n- **Never push, publish, or deploy** changes without explicit human approval,\n regardless of risk level.\n\n---\n\n## Common Pitfalls\n\n- **Don't assume selectors always resolve.** Selector strings may be stale if\n the DOM changed after annotation. When in doubt, use the screenshot.\n- **Don't skip the preamble.** Acting without reading the invariants or run\n commands is the most common source of broken fixes.\n- **Don't edit qapture config or plugin files** (`qa.config.ts`,\n `qa.preamble.md`, `.claude/skills/qapture/`, `src/components/qa-overlay/`).\n- **Don't use production credentials** \u2014 ever.\n- **Don't push/publish without human approval** \u2014 always present the changes\n for review first.\n- **Don't violate invariants** even if the annotation seems to imply it.\n Surface the conflict to the developer instead.\n\n---\n\n_Qapture \u2014 https://github.com/mohammed-farhood/qapture_\n";
32
+ var SKILL_default = "---\nname: qapture\ndescription: >\n Activated when the user provides a `qa-notes-*.zip` file exported from\n Qapture. Reads the preamble block in `notes.md` (project context, stack, run\n commands, dev/test login credentials, red-zone coverage report, and\n invariants), flags any uncovered RED risk zones before acting, then\n triages every `## Point N` as a batch \u2014 clustering points that share a\n root cause via their runtime-context evidence \u2014 before orchestrating\n Sonnet subagents (one per point/cluster, parallelized across disjoint\n files) to reproduce each issue live, fix it, and self-verify. Finally\n grades coverage against the red zones and reports, including any adjacent\n improvements noticed along the way. Also activates on a single point\n pasted directly via Qapture's \"Copy as agent prompt\" (no ZIP, no preamble\n \u2014 just one point).\n\n **No AI is bundled in Qapture \u2014 YOU are the AI reading these artifacts.**\n Qapture is a 100% client-side, keyless, network-free capture widget.\ntriggers:\n - qa-notes-*.zip\n---\n\n# Qapture \u2014 Agent Skill\n\n> **Core principle:** Qapture ships zero AI. No model, no API keys, no network\n> calls. The CLI is a plain deterministic scaffolder. **You** \u2014 the coding agent\n> reading this skill \u2014 are the AI. The developer used Qapture to capture\n> annotated screenshots + notes from their live app; your job is to act on them.\n\n---\n\n## What Is Qapture?\n\nQapture is a drop-in in-browser widget (Shadow DOM, keyless, no telemetry).\nTesters annotate the live app: click an element or draw a region, add a note,\nand the widget captures a screenshot automatically. When done, they export a\n`qa-notes-*.zip`. That ZIP is the hand-off to you.\n\n---\n\n## Working Model \u2014 You're the Brain, Subagents Are the Muscle\n\nFor anything past a single trivial point, don't work through the ZIP\npoint-by-point yourself. Orchestrate:\n\n- **You cluster and plan; subagents reproduce, fix, and self-verify.** You\n read every point first (Step 3), decide what's really one bug wearing N\n reports, and spawn one subagent per point or cluster to do the actual work.\n You do the fixing yourself only for a single, obviously trivial point where\n spinning up a subagent is pure overhead.\n- **Every subagent is Sonnet, pinned explicitly on every single call.** Never\n let a spawned agent inherit whatever model you happen to be running as\n orchestrator \u2014 an inherited model silently drifts to whatever you're on,\n and that's the kind of thing nobody notices until the cost or the quality\n looks wrong in hindsight. Pin it every time, no exceptions.\n- **Effort is your judgment call per task, not a formula.** A one-line CSS\n fix gets low effort. A bug whose runtime-context evidence (below) doesn't\n cleanly explain the symptom gets high or max \u2014 you decide based on how\n ambiguous the root cause actually looks, case by case.\n- **Parallelize by file, not by point.** Points/clusters that land in\n disjoint files can run as concurrent subagents safely. Anything that lands\n in the *same* file goes to one agent, or runs serially \u2014 never two agents\n editing the same file at once. This is the actual failure mode to guard\n against, not an abstract \"be careful.\"\n- **Supervise by reality-checking, not re-reading.** Don't reread every\n subagent's full diff. Do: read exactly what each one's own report claims\n changed, at the file/location it names; always personally open and read\n the diff for anything touching a RED zone, no exceptions, regardless of\n what the subagent reports; and treat the project's own test/verify command\n \u2014 run by you, independently, after every subagent lands \u2014 as the actual\n gate. A subagent saying \"done\" is a claim, not a fact.\n\n**Creative suggestions are always welcome; creative *changes* are gated like\neverything else, by risk colour (see Risk Zone Reference below).** Noticing a\npattern, an adjacent bug, or a missing feature costs nothing to write down \u2014\nput it in a `### Suggestions` section in your final report (Step 5) and never\nsuppress it for being out of scope. Whether you're allowed to *implement* it\nwithout being asked depends on where it lands:\n\n| Zone | An idea beyond the literal reported point |\n| --- | --- |\n| **green** | Implement inline, no permission needed \u2014 \"fixed the label, also fixed 3 nearby with the same casing bug\" is fine. |\n| **amber** | Implement it, but call it out explicitly in the report \u2014 the developer should see at a glance what went beyond what was asked. |\n| **red** | Propose only. Write it down, never touch the code. Same rule as everything else in a red zone: no silent business-logic decisions. |\n\nThat keeps the upside of a genuinely observant agent \u2014 catching the adjacent\nbug, proposing the missing feature \u2014 without that same latitude becoming the\nmechanism for quietly rewriting a payment flow nobody asked you to touch.\n\n---\n\n## ZIP Layout\n\n```\nqa-notes-<timestamp>.zip\n\u251C\u2500\u2500 notes.md \u2190 ALWAYS read this first (see Step 1)\n\u2514\u2500\u2500 screenshots/\n \u251C\u2500\u2500 point-1.png\n \u251C\u2500\u2500 point-2.png\n \u2514\u2500\u2500 ...\n```\n\n### `notes.md` structure\n\n```\n[PREAMBLE BLOCK]\n Project name, one-liner, stack, run commands,\n Login Context (dev/test credentials \u2014 see security note below),\n Coverage Report (red/amber/green zone checklist),\n Invariants, Additional Context.\n\n---NOTES---\n\n## Point 1\nPage: /some/path\nSeverity: bug (bug | question | polish \u2014 tester's own triage)\nStatus: open (open | verified)\nJourney step: <lane> \u2192 <path> (present when linked to a journey step)\nSelector: #some-element (or [data-testid=\"foo\"] etc.)\nNote: the tester's free-text description of the issue / request\n\n<details>Runtime context at capture \u2014 recent console/network events + env snapshot</details>\n\n## Point 2\n...\n```\n\nNote: a tester may also hand you a **single point directly**, pasted via\nQapture's \"Copy as agent prompt\" button, with no ZIP and no preamble at all.\nTreat it exactly like one `## Point N` section below \u2014 skip Steps 1 and 2\n(there is no preamble or coverage report to read), and go straight to Step 3.\n\n---\n\n## Step 1 \u2014 Read the Preamble First\n\nBefore touching any code, open `notes.md` and parse everything **above** the\n`---NOTES---` separator. Extract and internalize:\n\n| Section | What to do |\n| ------------------ | --------------------------------------------------------------------- |\n| **Project / Stack** | Understand the framework, router, ORM, and any unusual constraints. |\n| **Run Commands** | Know how to start the dev server and seed the database. |\n| **Login Context** | DEV/TEST/SEED credentials only. Use these to log in during verification. **Never log, forward, or commit these values.** |\n| **Coverage Report**| List of RED / AMBER / GREEN zones and whether they are covered. |\n| **Invariants** | Absolute rules you must never violate (e.g. \"prices \u2265 0\", \"checkout requires auth\"). |\n| **Conventions** | Codebase naming, file organisation, import rules, validation approach. |\n\n---\n\n## Step 2 \u2014 Flag Uncovered RED Zones Before Acting\n\nAfter reading the preamble, check the Coverage Report for any RED zones that\nare **not yet covered** by an annotation in this ZIP.\n\nIf uncovered RED zones exist, **report them to the developer first**:\n\n```\n\u26A0\uFE0F Uncovered RED zones detected:\n \u2022 /checkout/payment \u2014 no annotation in this export\n \u2022 /seller/payouts \u2014 no annotation in this export\n\nThese are money/auth/irreversible flows. Do you want me to proceed with the\ncovered points only, or will you add annotations for the red zones first?\n```\n\nWait for developer confirmation before proceeding if any RED zone is uncovered.\n\n---\n\n## Step 3 \u2014 Triage, Then Act\n\n### 3a. Triage & cluster before touching anything\n\nRead **every** `## Point N` in the ZIP before acting on any of them. Points\nthat look unrelated on the surface (different pages, different testers, even\ndifferent sessions) can share one root cause \u2014 check each point's runtime\ncontext (3c below) for a repeated signature: the same failing network URL,\nthe same status code, the same console error message. Group matches into one\ncluster. A cluster gets one fix and N verifications (one per point in it),\nnot N separate patches that might silently disagree with each other.\n\nWrite the plan down as an actual artifact before editing anything \u2014 a short\nmarkdown list is enough: each point/cluster, your root-cause hypothesis, the\nproposed fix, and its risk zone. This is the thing you hand to subagents in\nStep 3b, and the thing a developer can skim to sanity-check your read of the\nbatch before code starts moving.\n\n### 3b. Decide who does the work\n\nSingle trivial point, obviously green-zone, no ambiguity \u2192 you can just fix\nit. Anything else \u2192 spawn a subagent per point or cluster, per the Working\nModel above (Sonnet, pinned; effort by your judgment; parallel only across\ndisjoint files). Hand each subagent its point(s), the relevant preamble\ncontext (stack, conventions, invariants, login), and its risk zone.\n\n### 3c. Read the annotation\n\n- **Page** \u2014 the route/URL where the issue was captured.\n- **Severity** \u2014 `bug` (default), `question`, or `polish`. A `question` may\n not need a code change at all \u2014 read the note text before assuming one.\n- **Status** \u2014 `open` (default) or `verified`. A `verified` point was already\n re-checked by the tester after a previous fix; treat it as lower priority\n unless the note says otherwise.\n- **Journey step** \u2014 present when the point was captured during the guided\n walkthrough, or auto-linked by route match. Cross-reference it against the\n Coverage Report: a covered RED step usually has one of these attached.\n- **Selector** \u2014 the CSS selector or aria identifier for the element.\n- **Runtime context** (collapsed `<details>` block, when present) \u2014 recent\n `console.error`/`console.warn` output, uncaught errors, and failed/slow\n network calls captured in the moments before the tester clicked capture,\n plus an environment snapshot (viewport, language, timezone, page-load time).\n **Read this before assuming a UI-only cause.** \"The button does nothing\" is\n very often actually a console `TypeError` or a `500` that already happened\n \u2014 the evidence for it is right there, not something you have to reproduce\n blind. Query strings in any URL shown here have already been redacted by\n Qapture before export (see `SECURITY.md`); do not assume you're seeing a\n full URL, and never assume request bodies/headers were captured \u2014 they\n weren't, by design.\n- **Note** \u2014 the tester's description of the problem or change request.\n- **Forensics** (when present, inside the runtime-context `<details>` block)\n \u2014 computed facts about the exact captured element: `contrastFlag`\n (`low`/`ok`), `hasAccessibleName`, `tabReachable`, plus its computed\n styles. This turns \"looks fixed\" into something checkable: if\n `contrastFlag: low`, the fix isn't done until you can show the new colour\n pair actually clears a 4.5:1 ratio; if `tabReachable: false` on something\n that visually reads as interactive, that's an objective bug independent of\n whatever the tester's note text says. Treat these as acceptance criteria,\n not supplementary trivia.\n\n### 3d. Open the screenshot\n\nLoad `screenshots/point-N.png` to visually confirm what the tester saw.\nThe screenshot is truth \u2014 if the selector doesn't resolve, the screenshot tells\nyou what element they meant.\n\n### 3e. Locate the code\n\nUse the selector priority chain below to find the relevant source:\n\n| Priority | Selector type | Action |\n| -------- | -------------------------------------------------- | --------------------------------------------------- |\n| 1 | `#some-id` | `grep -r 'some-id'` in `src/` |\n| 2 | `[data-testid=\"foo\"]` / `[data-test]` / `[data-cy]` | grep for the attribute value |\n| 3 | `aria-label` on interactive elements | grep for the label string |\n| 4 | `name` attribute on form fields | grep for `name=\"...\"` in the relevant form file |\n| 5 | Structural (e.g. `.card:nth-of-type(2) > button`) | narrow by page route \u2192 component file \u2192 visual match with screenshot |\n| Fallback | Selector didn't resolve | Use the screenshot: identify the element visually, search by text content or component name |\n\nNarrow your search by the **Page** field to avoid editing the wrong route's code.\n\n### 3f. Reproduce it live, before writing a fix\n\nDon't go straight from \"read the note\" to \"guess the fix.\" Run the app, log\nin as the relevant role using **Login Context**, navigate to **Page**, and \u2014\nif the point has a **Journey step** with an `expect` field \u2014 try to actually\ntrigger the failure the way the journey step describes. This catches two\nthings a static screenshot can't: a report that's already stale (fixed\nelsewhere, doesn't reproduce), and a bug whose real trigger is an\ninteraction, not the state the screenshot happened to capture. Only once\nyou've confirmed the failure and understand *why* it happens do you move to\n3g \u2014 writing a fix against a guess is how you end up patching the symptom\nin the screenshot instead of the actual defect.\n\n### 3g. Make the change\n\n- Follow the project's **Conventions** (from the preamble).\n- Respect all **Invariants** \u2014 never violate them even if the annotation implies it.\n- If the change touches a RED zone (money / auth / irreversible state), add an\n explicit comment: `// QA: red-zone change \u2014 reviewed <date>`.\n- Do **not** edit `qa.config.ts`, `qa.preamble.md`, or any qapture plugin files.\n\n---\n\n## Step 4 \u2014 Verify the Fix\n\n1. Run the app using the **Run Commands** from the preamble.\n2. Log in as the relevant role using **Login Context** credentials.\n (These are DEV/TEST/SEED only \u2014 never use production credentials.)\n3. Navigate to the **Page** listed in the annotation.\n4. **Reproduce** the original issue to confirm it existed, then verify it is fixed.\n5. In the browser console, run `document.querySelector('<selector>')` to confirm\n the element resolves as expected.\n6. Check adjacent paths for regressions, especially if the change is in a shared\n component.\n\n---\n\n## Step 5 \u2014 Grade and Report\n\nAfter acting on all points, produce a short report:\n\n```markdown\n## Qapture \u2014 Changes Summary\n\n| Point | Page | Severity | Change made | Verified | Risk |\n| ----- | --------------- | -------- | ----------------------------- | -------- | ----- |\n| 1 | /products | bug | Fixed button label | \u2713 | green |\n| 2 | /checkout | bug | Corrected total calculation | \u2713 | red |\n\n### Coverage vs Red Zones\n- [x] /checkout/payment \u2014 covered by Point 2\n- [ ] /seller/payouts \u2014 NOT covered (flagged in Step 2)\n\n### Uncovered items\nNone (all annotated points addressed).\n\n### Suggestions (proposed, not implemented)\n- [amber] /cart \u2014 quantity stepper has no debounce; noticed while fixing\n Point 1, not part of the report, flagging rather than touching it.\n```\n\nAn **amber** suggestion you *did* implement inline still gets called out\nhere, same as above but phrased as done, not proposed. A **green** one\ndoesn't need a separate line at all \u2014 just mention it in the affected\npoint's \"Change made\" cell. **Red** ideas are always proposal-only, never a\nline item that reads as if it happened.\n\n---\n\n## Risk Zone Reference\n\n| Risk | Examples | Rule |\n| ------- | ----------------------------------------------------------------- | ------------------------------------------------ |\n| **red** | Payment, checkout, auth, order state, refunds, payouts, user data | Never change without developer review + comment |\n| **amber** | Cart, product listings, search, seller dashboard, inventory | Change carefully; verify end-to-end |\n| **green** | Static content, labels, colours, copy, tooltips, layout | Change freely; quick smoke-test |\n\n---\n\n## Security Note\n\n- **Dev/test/seed credentials only.** The Login Context in `notes.md` contains\n credentials from `.env.example` or seeder files \u2014 never from production.\n- **Never log, forward, store, or commit** Login Context values outside the\n development environment.\n- **Never read** `.env`, `.env.local`, `.env.production`, or any `secrets/`\n path. Qapture's CLI enforces this; you must too.\n- Qapture is **100% client-side** \u2014 it makes no network calls, holds no API\n keys, and sends no data anywhere.\n- **Runtime context evidence is already redacted for you.** Any URL shown in\n a point's runtime-context block has had its query string stripped by\n Qapture before export, and request/response bodies, headers, cookies, and\n storage values were never captured in the first place \u2014 treat this section\n as safe local debugging evidence, not as something you need to further\n sanitize.\n- **Never push, publish, or deploy** changes without explicit human approval,\n regardless of risk level.\n\n---\n\n## Common Pitfalls\n\n- **Don't assume selectors always resolve.** Selector strings may be stale if\n the DOM changed after annotation. When in doubt, use the screenshot.\n- **Don't skip the preamble.** Acting without reading the invariants or run\n commands is the most common source of broken fixes.\n- **Don't edit qapture config or plugin files** (`qa.config.ts`,\n `qa.preamble.md`, `.claude/skills/qapture/`, `src/components/qa-overlay/`).\n- **Don't use production credentials** \u2014 ever.\n- **Don't push/publish without human approval** \u2014 always present the changes\n for review first.\n- **Don't violate invariants** even if the annotation seems to imply it.\n Surface the conflict to the developer instead.\n- **Don't ignore the runtime context block.** A point's collapsed \"Runtime\n context at capture\" section is often the actual root cause, not\n supplementary detail \u2014 check it before guessing at one from the note text\n and screenshot alone.\n- **Don't treat a `question`-severity or `verified`-status point like a\n routine bug fix.** A `question` may just need an answer, not code; a\n `verified` point was already re-confirmed once and should be double-checked\n before you assume it's still broken.\n- **Don't work every point serially when they could parallelize.** If you\n catch yourself fixing point after point one at a time on a batch of five\n independent-file points, stop and re-read the Working Model section above.\n- **Don't let two subagents touch the same file at once.** File overlap, not\n point count, is what decides parallel vs serial \u2014 check this before you\n spawn anything.\n- **Don't treat a subagent's \"done\" as the verification gate.** Run the\n project's own test/verify command yourself, independently, after every\n subagent lands its change.\n- **Don't silently implement a creative idea beyond a green zone.** Amber\n gets implemented-and-disclosed; red gets proposed-and-left-alone. Never\n implemented-and-undisclosed.\n\n---\n\n_Qapture \u2014 https://github.com/mohammed-farhood/qapture_\n";
33
33
 
34
34
  // src/artifacts/AGENTS_SECTION.md
35
- var AGENTS_SECTION_default = '<!-- qa-studio-section -->\n\n## Qapture\n\nThis project uses **Qapture** \u2014 an in-browser QA capture widget that ships\n**zero AI** (no model, no API keys, no network calls). **You** are the AI.\n\n### When you receive a `qa-notes-*.zip`\n\n1. **Unzip** the file.\n2. **Read `notes.md` top-to-bottom**, starting with everything above the\n `---NOTES---` separator:\n - **Project context** \u2014 name, stack, run commands, conventions.\n - **Theme tokens** \u2014 colour palette (respect these in any UI changes).\n - **Login Context** \u2014 dev/test/seed credentials for the relevant roles.\n _(DEV/TEST/SEED only \u2014 never commit, log, or forward these values.)_\n - **Coverage Report** \u2014 red/amber/green zone checklist.\n - **Invariants** \u2014 rules you must never violate (e.g. "prices \u2265 0",\n "checkout requires auth").\n3. **Flag uncovered RED zones** before acting. RED = money / auth / irreversible\n state. If any red zone has no annotation in this ZIP, report it and ask the\n developer whether to proceed.\n4. **Act on each `## Point N`** annotation:\n - **Page** + **Selector** + **Note** \u2192 locate the element in the source\n (priority: `#id` \u2192 `[data-testid]` \u2192 `aria-label` \u2192 `name` \u2192 visual match\n via the `screenshots/point-N.png`).\n - Make the change following the project conventions and invariants.\n - **Verify**: run the app, log in as the relevant role, navigate to the page,\n confirm the fix.\n5. **Report** a summary table of changes, risk levels, and coverage status.\n\n### Full protocol\n\n`.claude/skills/qapture/SKILL.md` (always kept current by `qapture init`).\n\n### Rules\n\n- Never read `.env`, `.env.local`, `.env.production`, or any `secrets/` path.\n- Never edit `qa.config.ts`, `qa.preamble.md`, or any qapture plugin files.\n- Never push/publish/deploy without explicit human approval.\n- Dev/test/seed credentials only \u2014 never use or request production credentials.\n\n_Qapture \u2014 https://github.com/mohammed-farhood/qapture_\n\n<!-- /qa-studio-section -->\n';
35
+ var AGENTS_SECTION_default = "<!-- qa-studio-section -->\n\n## Qapture\n\nThis project uses **Qapture** \u2014 an in-browser QA capture widget that ships\n**zero AI** (no model, no API keys, no network calls). **You** are the AI.\n\n### When you receive a `qa-notes-*.zip`\n\n1. **Unzip** the file.\n2. **Read `notes.md` top-to-bottom**, starting with everything above the\n `---NOTES---` separator:\n - **Project context** \u2014 name, stack, run commands, conventions.\n - **Login Context** \u2014 dev/test/seed credentials for the relevant roles.\n _(DEV/TEST/SEED only \u2014 never commit, log, or forward these values.)_\n - **Coverage Report** \u2014 red/amber/green zone checklist.\n - **Invariants** \u2014 rules you must never violate (e.g. \"prices \u2265 0\",\n \"checkout requires auth\").\n3. **Flag uncovered RED zones** before acting. RED = money / auth / irreversible\n state. If any red zone has no annotation in this ZIP, report it and ask the\n developer whether to proceed.\n4. **Triage the whole batch before touching code.** Read every point first \u2014\n points on different pages can share one root cause (check their runtime\n context for a repeated failing URL or error message) and deserve one fix,\n not N. Write the plan down before editing anything.\n5. **Orchestrate, don't grind through it yourself.** For anything past a\n single trivial point: you're the brain, not the muscle. Spawn one Sonnet\n subagent per point/cluster (model pinned explicitly on every call, never\n inherited; effort is your judgment call per task), parallelized across\n points that touch disjoint files \u2014 never two agents on the same file. You\n supervise by checking what each subagent's report actually claims and by\n running the project's own verify/test command yourself afterward, not by\n rereading every diff or trusting a subagent's \"done.\"\n6. **Each point/cluster, before fixing:**\n - **Page** + **Selector** + **Note** \u2192 locate the element in the source\n (priority: `#id` \u2192 `[data-testid]` \u2192 `aria-label` \u2192 `name` \u2192 visual match\n via the `screenshots/point-N.png`).\n - **Severity** (`bug`/`question`/`polish`) and **Status** (`open`/`verified`)\n tell you how to treat the point \u2014 a `question` may not need a code\n change; a `verified` point was already re-checked once.\n - **Runtime context** (when present, in a collapsed `<details>` block) \u2014\n recent console errors/warnings and failed network calls captured right\n before the tester clicked capture, plus an environment snapshot, plus\n **forensics** (contrast/accessibility flags on the captured element) when\n present \u2014 treat these as objective acceptance criteria, not just the\n tester's prose. Read it before assuming a UI-only cause. Query strings in\n any URL there are already redacted; bodies, headers, cookies, and storage\n were never captured at all.\n - **Reproduce it live first** \u2014 run the app, log in as the relevant role,\n navigate to the page, actually trigger the failure \u2014 before writing a\n fix. A static screenshot can hide a stale report or an interaction-only\n bug.\n - Make the change following the project conventions and invariants, then\n re-verify the same repro.\n7. **Report** a summary table of changes, risk levels, and coverage status,\n plus a `### Suggestions` section for anything noticed beyond the reported\n points. Whether you may *implement* an extra idea (not just suggest it)\n follows the same red/amber/green gating as everything else: green \u2014 do it\n inline; amber \u2014 do it and disclose it; red \u2014 propose only, never touch it.\n\n### A single point, no ZIP\n\nA tester can also send you **one point directly**, pasted via Qapture's\n\"Copy as agent prompt\" button, with no ZIP and no preamble. Treat it exactly\nlike one point from step 6 above \u2014 there's nothing to cluster or triage with\nonly one point, and there's no Coverage Report to check or RED-zone flag to\nraise, since there's no journey context at all. Just fix it yourself if it's\ntrivial, or spawn a single subagent if it isn't.\n\n### Full protocol\n\n`.claude/skills/qapture/SKILL.md` (always kept current by `qapture init`).\n\n### Rules\n\n- Never read `.env`, `.env.local`, `.env.production`, or any `secrets/` path.\n- Never edit `qa.config.ts`, `qa.preamble.md`, or any qapture plugin files.\n- Never push/publish/deploy without explicit human approval.\n- Dev/test/seed credentials only \u2014 never use or request production credentials.\n\n_Qapture \u2014 https://github.com/mohammed-farhood/qapture_\n\n<!-- /qa-studio-section -->\n";
36
36
 
37
37
  // src/bin/utils/args.ts
38
38
  function parseArgs(argv2) {
@@ -227,7 +227,7 @@ function assertSafeToRead(filePath) {
227
227
  const ext = path3.extname(filePath).toLowerCase();
228
228
  if (basename3 === ".env.example") return true;
229
229
  if (/^\.env\.example(\.\w+)?$/.test(basename3)) return true;
230
- if (BLOCKED_EXACT_BASENAMES.has(basename3)) return false;
230
+ if (BLOCKED_EXACT_BASENAMES.has(basename3.toLowerCase())) return false;
231
231
  if (/^\.env\./i.test(basename3) && !/^\.env\.example/i.test(basename3)) {
232
232
  return false;
233
233
  }
@@ -264,11 +264,11 @@ function makeStep(routePath) {
264
264
  }
265
265
  function classifyPath(routePath) {
266
266
  const p = routePath.toLowerCase();
267
- if (/^\/(seller|store-owner|vendor)/.test(p)) return "seller";
268
- if (/^\/(admin|dashboard|control-panel|backoffice|back-office|management|cms)/.test(p)) {
267
+ if (/^\/(seller|store-owner|vendor)(?:\/|$)/.test(p)) return "seller";
268
+ if (/^\/(admin|dashboard|control-panel|backoffice|back-office|management|cms)(?:\/|$)/.test(p)) {
269
269
  return "admin";
270
270
  }
271
- if (/^\/(login|signin|sign-in|signup|sign-up|register|auth|forgot-password|reset-password|verify|email-verification|oauth)/.test(p)) {
271
+ if (/^\/(login|signin|sign-in|signup|sign-up|register|auth|forgot-password|reset-password|verify|email-verification|oauth)(?:\/|$)/.test(p)) {
272
272
  return "auth";
273
273
  }
274
274
  return "buyer";
@@ -383,205 +383,9 @@ function detectRoutes(targetDir) {
383
383
  return lanes;
384
384
  }
385
385
 
386
- // src/bin/detectors/detectTheme.ts
386
+ // src/bin/detectors/detectCredentials.ts
387
387
  var path5 = __toESM(require("path"), 1);
388
388
  var fs5 = __toESM(require("fs"), 1);
389
- var PLACEHOLDER = "#REPLACE_ME";
390
- var KEY_ALIASES = [
391
- {
392
- key: "primaryDark",
393
- patterns: [
394
- /primary[-_]?dark/i,
395
- /primary[-_]?(?:800|900|700|deep)/i,
396
- /brand[-_]?dark/i
397
- ]
398
- },
399
- {
400
- key: "primary",
401
- patterns: [
402
- /^primary$/i,
403
- /primary[-_]?(?:base|default|main|500|600)?$/i,
404
- /^brand$/i,
405
- /brand[-_]?(?:main|primary|base|default)?$/i
406
- ]
407
- },
408
- {
409
- key: "accentDark",
410
- patterns: [
411
- /accent[-_]?dark/i,
412
- /accent[-_]?(?:700|800|900|deep)/i,
413
- /secondary[-_]?dark/i
414
- ]
415
- },
416
- {
417
- key: "accent",
418
- patterns: [
419
- /^accent$/i,
420
- /accent[-_]?(?:base|default|main|500|600)?$/i,
421
- /^secondary$/i,
422
- /secondary[-_]?(?:main|base|default)?$/i,
423
- /^highlight$/i
424
- ]
425
- },
426
- {
427
- key: "sage",
428
- patterns: [
429
- /^sage$/i,
430
- /^muted$/i,
431
- /^neutral$/i,
432
- /^subdued$/i,
433
- /gray[-_]?500/i
434
- ]
435
- },
436
- {
437
- key: "cream",
438
- patterns: [
439
- /^cream$/i,
440
- /^background[-_]?light$/i,
441
- /^bg[-_]?light$/i,
442
- /^off[-_]?white$/i,
443
- /^paper$/i,
444
- /^canvas$/i
445
- ]
446
- },
447
- {
448
- key: "mauve",
449
- patterns: [
450
- /^mauve$/i,
451
- /^lavender$/i,
452
- /^purple[-_]?light$/i,
453
- /^lilac$/i,
454
- /^periwinkle$/i
455
- ]
456
- },
457
- {
458
- key: "surface",
459
- patterns: [
460
- /^surface$/i,
461
- /^card$/i,
462
- /^panel$/i,
463
- /^background$/i,
464
- /^bg$/i
465
- ]
466
- },
467
- {
468
- key: "ink",
469
- patterns: [
470
- /^ink$/i,
471
- /^text[-_]?(?:default|primary|base|main)?$/i,
472
- /^foreground$/i,
473
- /^content$/i,
474
- /^copy$/i
475
- ]
476
- }
477
- ];
478
- function resolveThemeKey(name) {
479
- for (const { key, patterns } of KEY_ALIASES) {
480
- for (const pat of patterns) {
481
- if (pat.test(name)) return key;
482
- }
483
- }
484
- return null;
485
- }
486
- var HEX_COLOR = /#[0-9a-fA-F]{3,8}\b/;
487
- function extractTailwindColors(content) {
488
- const colors = /* @__PURE__ */ new Map();
489
- const RE = /['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8})['"]?/g;
490
- let m;
491
- while ((m = RE.exec(content)) !== null) {
492
- const [, name, hex] = m;
493
- if (HEX_COLOR.test(hex)) {
494
- colors.set(name.toLowerCase(), hex);
495
- }
496
- }
497
- return colors;
498
- }
499
- function extractCssCustomProps(content) {
500
- const colors = /* @__PURE__ */ new Map();
501
- const RE = /--([\w-]+)\s*:\s*(#[0-9a-fA-F]{3,8})\b/g;
502
- let m;
503
- while ((m = RE.exec(content)) !== null) {
504
- const [, varName, hex] = m;
505
- if (!HEX_COLOR.test(hex)) continue;
506
- const stripped = varName.replace(/^(?:color|colour|clr|c|qs|qa)[-_]/, "").toLowerCase();
507
- if (!colors.has(stripped)) colors.set(stripped, hex);
508
- if (!colors.has(varName.toLowerCase())) colors.set(varName.toLowerCase(), hex);
509
- }
510
- return colors;
511
- }
512
- var CSS_SEARCH_DIRS = [
513
- "",
514
- // root
515
- "src",
516
- "styles",
517
- "css",
518
- "src/styles",
519
- "src/css",
520
- "src/app",
521
- "app",
522
- "assets",
523
- "assets/css",
524
- "assets/styles",
525
- "public"
526
- ];
527
- function detectTheme(targetDir) {
528
- const draft = {
529
- primary: PLACEHOLDER,
530
- primaryDark: PLACEHOLDER,
531
- accent: PLACEHOLDER,
532
- accentDark: PLACEHOLDER,
533
- sage: PLACEHOLDER,
534
- cream: PLACEHOLDER,
535
- mauve: PLACEHOLDER,
536
- surface: PLACEHOLDER,
537
- ink: PLACEHOLDER
538
- };
539
- const allColors = /* @__PURE__ */ new Map();
540
- const mergeColors = (extracted) => {
541
- for (const [k, v] of extracted) {
542
- if (!allColors.has(k)) allColors.set(k, v);
543
- }
544
- };
545
- const tailwindNames = [
546
- "tailwind.config.ts",
547
- "tailwind.config.js",
548
- "tailwind.config.cjs",
549
- "tailwind.config.mjs"
550
- ];
551
- for (const name of tailwindNames) {
552
- const filePath = path5.join(targetDir, name);
553
- if (fs5.existsSync(filePath) && assertSafeToRead(filePath)) {
554
- const content = readFileSafe(filePath);
555
- if (content) mergeColors(extractTailwindColors(content));
556
- break;
557
- }
558
- }
559
- const CSS_EXTS = /\.(css|scss|sass|less|styl)$/i;
560
- for (const rel of CSS_SEARCH_DIRS) {
561
- const dirPath = rel ? path5.join(targetDir, rel) : targetDir;
562
- if (!dirExists(dirPath)) continue;
563
- const files = rel.includes("style") || rel.includes("css") ? walk(dirPath).filter((f) => CSS_EXTS.test(f)) : fs5.readdirSync(dirPath).filter((f) => CSS_EXTS.test(f)).map((f) => path5.join(dirPath, f));
564
- for (const filePath of files) {
565
- if (!assertSafeToRead(filePath)) continue;
566
- const content = readFileSafe(filePath);
567
- if (content) mergeColors(extractCssCustomProps(content));
568
- }
569
- }
570
- for (const [name, hex] of allColors) {
571
- const key = resolveThemeKey(name);
572
- if (key && draft[key] === PLACEHOLDER) {
573
- draft[key] = hex;
574
- }
575
- }
576
- return draft;
577
- }
578
- function hasDetectedColors(draft) {
579
- return Object.values(draft).some((v) => v !== PLACEHOLDER);
580
- }
581
-
582
- // src/bin/detectors/detectCredentials.ts
583
- var path6 = __toESM(require("path"), 1);
584
- var fs6 = __toESM(require("fs"), 1);
585
389
  var CREDENTIALS_BANNER = '// DEV/TEST/SEED ONLY \u2014 never production, never commit real passwords.\n// Extracted from .env.example and seeder files only.\n// Replace any "TODO: set from env \u2026" values with your actual dev/test credentials.';
586
390
  var SEEDER_PATH_PATTERNS = [
587
391
  /[\\/]seeders?[\\/]/i,
@@ -596,15 +400,18 @@ function isSeederFile(filePath) {
596
400
  const norm = filePath.replace(/\\/g, "/");
597
401
  return SEEDER_PATH_PATTERNS.some((p) => p.test(norm));
598
402
  }
599
- function extractMatches(content) {
403
+ function extractMatches(content, file) {
600
404
  const out = [];
601
405
  const lines = content.split("\n");
602
406
  const FIELD_RE = /\b(email|login|username|password|phone|role)\s*[:=]\s*(?:["'`]([^"'`\r\n]+)["'`]|(process\.env\.(\w+)))/gi;
407
+ const FIELD_RE_EMBEDDED = /(?:^|[^A-Za-z0-9_])[A-Za-z][A-Za-z0-9_]*?(email|login|username|password|phone|role)\s*[:=]\s*(?:["'`]([^"'`\r\n]+)["'`]|(process\.env\.(\w+)))/gi;
603
408
  for (let i = 0; i < lines.length; i++) {
604
409
  const line = lines[i];
605
410
  let m;
606
411
  FIELD_RE.lastIndex = 0;
412
+ const claimedRanges = [];
607
413
  while ((m = FIELD_RE.exec(line)) !== null) {
414
+ claimedRanges.push([m.index, m.index + m[0].length]);
608
415
  const type = m[1].toLowerCase();
609
416
  let value;
610
417
  if (m[3] !== void 0) {
@@ -613,7 +420,22 @@ function extractMatches(content) {
613
420
  value = m[2].trim();
614
421
  if (/^[<{]/.test(value) || /^(change[_-]?me|your[_-])/i.test(value)) continue;
615
422
  }
616
- out.push({ type, value, lineIdx: i, context: line });
423
+ out.push({ type, value, lineIdx: i, context: line, file });
424
+ }
425
+ FIELD_RE_EMBEDDED.lastIndex = 0;
426
+ while ((m = FIELD_RE_EMBEDDED.exec(line)) !== null) {
427
+ const start = m.index;
428
+ const end = m.index + m[0].length;
429
+ if (claimedRanges.some(([s, e]) => start < e && end > s)) continue;
430
+ const type = m[1].toLowerCase();
431
+ let value;
432
+ if (m[3] !== void 0) {
433
+ value = `TODO: set from env ${m[4]} (use .env.example)`;
434
+ } else {
435
+ value = m[2].trim();
436
+ if (/^[<{]/.test(value) || /^(change[_-]?me|your[_-])/i.test(value)) continue;
437
+ }
438
+ out.push({ type, value, lineIdx: i, context: line, file });
617
439
  }
618
440
  }
619
441
  return out;
@@ -644,7 +466,8 @@ function groupMatches(matches) {
644
466
  const clusterHasIdentifier = current.some(
645
467
  (x) => x.type === "email" || x.type === "login" || x.type === "username"
646
468
  );
647
- if (m.lineIdx - prev.lineIdx > 20 || isIdentifier && clusterHasIdentifier) {
469
+ const sameFile = m.file === prev.file;
470
+ if (!sameFile || m.lineIdx - prev.lineIdx > 20 || isIdentifier && clusterHasIdentifier) {
648
471
  clusters.push(current);
649
472
  current = [m];
650
473
  } else {
@@ -692,21 +515,21 @@ var SEEDER_SEARCH_DIRS = [
692
515
  ];
693
516
  function detectCredentials(targetDir) {
694
517
  const allMatches = [];
695
- const envExample = path6.join(targetDir, ".env.example");
696
- if (fs6.existsSync(envExample) && assertSafeToRead(envExample)) {
518
+ const envExample = path5.join(targetDir, ".env.example");
519
+ if (fs5.existsSync(envExample) && assertSafeToRead(envExample)) {
697
520
  const content = readFileSafe(envExample);
698
- if (content) allMatches.push(...extractMatches(content));
521
+ if (content) allMatches.push(...extractMatches(content, envExample));
699
522
  }
700
523
  for (const rel of SEEDER_SEARCH_DIRS) {
701
- const dirPath = path6.join(targetDir, rel);
524
+ const dirPath = path5.join(targetDir, rel);
702
525
  if (!dirExists(dirPath)) continue;
703
526
  const files = walk(dirPath).filter((f) => {
704
- const ext = path6.extname(f);
527
+ const ext = path5.extname(f);
705
528
  return [".js", ".ts", ".mjs", ".cjs", ".json"].includes(ext) && isSeederFile(f) && assertSafeToRead(f);
706
529
  });
707
530
  for (const f of files) {
708
531
  const content = readFileSafe(f);
709
- if (content) allMatches.push(...extractMatches(content));
532
+ if (content) allMatches.push(...extractMatches(content, f));
710
533
  }
711
534
  }
712
535
  return groupMatches(allMatches);
@@ -716,22 +539,6 @@ function detectCredentials(targetDir) {
716
539
  function singleQuote(s) {
717
540
  return `'${s.replace(/'/g, "\\'")}'`;
718
541
  }
719
- function serializeTheme(theme) {
720
- const lines = [];
721
- const keys = Object.keys(theme);
722
- for (const key of keys) {
723
- const val = theme[key];
724
- const isPlaceholder = val === PLACEHOLDER;
725
- if (isPlaceholder) {
726
- lines.push(` ${key}: ${singleQuote(val)}, // TODO: replace with your brand colour`);
727
- } else {
728
- lines.push(` ${key}: ${singleQuote(val)},`);
729
- }
730
- }
731
- return ` theme: {
732
- ${lines.join("\n")}
733
- }`;
734
- }
735
542
  function serializeCredentials(creds) {
736
543
  if (creds.length === 0) {
737
544
  return ` // ${CREDENTIALS_BANNER.replace(/\n/g, "\n // ")}
@@ -814,7 +621,7 @@ function serializePreamble() {
814
621
  ].join("\n");
815
622
  }
816
623
  function genConfigText(opts) {
817
- const { namespace, isTypeScript, theme, journey, credentials, frameworkHints = [] } = opts;
624
+ const { namespace, isTypeScript, journey, credentials, frameworkHints = [] } = opts;
818
625
  const filename = isTypeScript ? "qa.config.ts" : "qa.config.js";
819
626
  const hintsComment = frameworkHints.length > 0 ? ` *
820
627
  * Auto-detected stack:
@@ -839,7 +646,6 @@ ${frameworkHints.map((h) => ` * \u2022 ${h}`).join("\n")}` : "";
839
646
  hintsComment,
840
647
  ` */`
841
648
  ].filter((l) => l !== "").join("\n");
842
- const themeBlock = serializeTheme(theme);
843
649
  const credentialsBlock = serializeCredentials(credentials);
844
650
  const journeyBlock = serializeJourney(journey);
845
651
  const preambleBlock = serializePreamble();
@@ -847,7 +653,9 @@ ${frameworkHints.map((h) => ` * \u2022 ${h}`).join("\n")}` : "";
847
653
  `const config${typeAnnotation} = {`,
848
654
  ` namespace: ${singleQuote(namespace)},`,
849
655
  ``,
850
- themeBlock + ",",
656
+ ` // NOTE: custom themes were removed in Qapture 0.3.0 \u2014 the widget now`,
657
+ ` // ships one fixed, self-contained design. There is no \`theme\` key to`,
658
+ ` // fill in here any more.`,
851
659
  ``,
852
660
  ` brand: {`,
853
661
  ` label: 'TODO: Your Project Name', // displayed in the QA panel header`,
@@ -967,13 +775,13 @@ var REPO_URL = "https://github.com/mohammed-farhood/qapture";
967
775
  var PKG_VERSION = (() => {
968
776
  try {
969
777
  const candidates = [
970
- path7.join(__dirname, "..", "..", "package.json"),
971
- path7.join(__dirname, "..", "package.json"),
972
- path7.join(__dirname, "package.json")
778
+ path6.join(__dirname, "..", "..", "package.json"),
779
+ path6.join(__dirname, "..", "package.json"),
780
+ path6.join(__dirname, "package.json")
973
781
  ];
974
782
  for (const p of candidates) {
975
- if (fs7.existsSync(p)) {
976
- const pkg = JSON.parse(fs7.readFileSync(p, "utf8"));
783
+ if (fs6.existsSync(p)) {
784
+ const pkg = JSON.parse(fs6.readFileSync(p, "utf8"));
977
785
  if (pkg.version) return pkg.version;
978
786
  }
979
787
  }
@@ -983,14 +791,14 @@ var PKG_VERSION = (() => {
983
791
  })();
984
792
  function readTargetPkg(targetDir) {
985
793
  try {
986
- const raw = fs7.readFileSync(path7.join(targetDir, "package.json"), "utf8");
794
+ const raw = fs6.readFileSync(path6.join(targetDir, "package.json"), "utf8");
987
795
  return JSON.parse(raw);
988
796
  } catch {
989
797
  return {};
990
798
  }
991
799
  }
992
800
  function hasFile(targetDir, ...names) {
993
- return names.some((n) => fs7.existsSync(path7.join(targetDir, n)));
801
+ return names.some((n) => fs6.existsSync(path6.join(targetDir, n)));
994
802
  }
995
803
  function detectFrameworkHints(targetDir, pkg) {
996
804
  const hints = [];
@@ -1043,7 +851,7 @@ function printVersion() {
1043
851
  `);
1044
852
  }
1045
853
  var DIVIDER = "\u2500".repeat(60);
1046
- function printSummary(targetDir, configFile, results, routeCount, credCount, colorsDetected) {
854
+ function printSummary(targetDir, configFile, results, routeCount, credCount) {
1047
855
  const icon = (r) => r === "skipped" ? " (skip)" : " \u2713";
1048
856
  const configLabel = configFile;
1049
857
  const preambleLabel = "qa.preamble.md";
@@ -1065,9 +873,8 @@ ${icon(results.preamble)} ${preambleLabel}${preambleNote}
1065
873
  \u2713 ${agentsLabel}${agentsNote}
1066
874
 
1067
875
  Detected:
1068
- \u2022 Routes/steps : ${routeCount > 0 ? routeCount : "none (fallback placeholder added)"}
1069
- \u2022 Brand colours: ${colorsDetected ? "partial palette detected" : "none (all #REPLACE_ME)"}
1070
- \u2022 Credentials : ${credCount > 0 ? credCount + " row(s) from .env.example/seeders" : "none (add manually)"}
876
+ \u2022 Routes/steps: ${routeCount > 0 ? routeCount : "none (fallback placeholder added)"}
877
+ \u2022 Credentials : ${credCount > 0 ? credCount + " row(s) from .env.example/seeders" : "none (add manually)"}
1071
878
 
1072
879
  ${DIVIDER}
1073
880
  Mount the widget near your app root:
@@ -1119,16 +926,16 @@ function main(argv2) {
1119
926
  printUsage();
1120
927
  process2.exit(0);
1121
928
  }
1122
- const targetDir = path7.resolve(args.dir);
929
+ const targetDir = path6.resolve(args.dir);
1123
930
  const { force } = args;
1124
- if (!fs7.existsSync(targetDir)) {
931
+ if (!fs6.existsSync(targetDir)) {
1125
932
  process2.stderr.write(`
1126
933
  Error: target directory does not exist: ${targetDir}
1127
934
 
1128
935
  `);
1129
936
  process2.exit(1);
1130
937
  }
1131
- if (!fs7.statSync(targetDir).isDirectory()) {
938
+ if (!fs6.statSync(targetDir).isDirectory()) {
1132
939
  process2.stderr.write(`
1133
940
  Error: ${targetDir} is not a directory
1134
941
 
@@ -1145,10 +952,6 @@ qapture init \u2014 scanning ${targetDir} ...
1145
952
  `);
1146
953
  const journey = detectRoutes(targetDir);
1147
954
  const routeCount = journey.reduce((n, lane) => n + lane.steps.length, 0);
1148
- process2.stdout.write(` Detecting theme ...
1149
- `);
1150
- const theme = detectTheme(targetDir);
1151
- const colorsDetected = hasDetectedColors(theme);
1152
955
  process2.stdout.write(` Detecting credentials (safe sources only) ...
1153
956
  `);
1154
957
  const credentials = detectCredentials(targetDir);
@@ -1159,16 +962,15 @@ qapture init \u2014 scanning ${targetDir} ...
1159
962
  const { filename: configFilename, text: configText } = genConfigText({
1160
963
  namespace,
1161
964
  isTypeScript,
1162
- theme,
1163
965
  journey,
1164
966
  credentials,
1165
967
  frameworkHints
1166
968
  });
1167
969
  const preambleText = genPreambleText({ projectName, frameworkHints });
1168
- const configPath = path7.join(targetDir, configFilename);
1169
- const preamblePath = path7.join(targetDir, "qa.preamble.md");
1170
- const skillPath = path7.join(targetDir, ".claude", "skills", "qapture", "SKILL.md");
1171
- const agentsMdPath = path7.join(targetDir, "AGENTS.md");
970
+ const configPath = path6.join(targetDir, configFilename);
971
+ const preamblePath = path6.join(targetDir, "qa.preamble.md");
972
+ const skillPath = path6.join(targetDir, ".claude", "skills", "qapture", "SKILL.md");
973
+ const agentsMdPath = path6.join(targetDir, "AGENTS.md");
1172
974
  const configResult = writeIfAbsent(configPath, configText, force);
1173
975
  const preambleResult = writeIfAbsent(preamblePath, preambleText, force);
1174
976
  writeAlways(skillPath, SKILL_default);
@@ -1179,8 +981,7 @@ qapture init \u2014 scanning ${targetDir} ...
1179
981
  configFilename,
1180
982
  { config: configResult, preamble: preambleResult, skill: skillResult, agents: agentsResult },
1181
983
  routeCount,
1182
- credentials.length,
1183
- colorsDetected
984
+ credentials.length
1184
985
  );
1185
986
  }
1186
987
  main(process2.argv.slice(2));