qapture2 0.2.4 → 0.3.1
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/README.md +145 -23
- package/dist/bin/init.cjs +31 -266
- package/dist/{chunk-RC7ZUQ5X.js → chunk-2CAMCMJP.js} +2314 -999
- package/dist/{chunk-DPJW626S.cjs → chunk-PIN23NW3.cjs} +2313 -998
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +27 -5
- package/dist/index.d.ts +27 -5
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +1 -1
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
|
|
28
|
-
var
|
|
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 =
|
|
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) {
|
|
@@ -383,222 +383,9 @@ function detectRoutes(targetDir) {
|
|
|
383
383
|
return lanes;
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
// src/bin/detectors/
|
|
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
|
-
const RE_NESTED = /['"]?([\w-]+)['"]?\s*:\s*\{([^{}]*)\}/g;
|
|
498
|
-
let nm;
|
|
499
|
-
while ((nm = RE_NESTED.exec(content)) !== null) {
|
|
500
|
-
const [, outerName, innerBlock] = nm;
|
|
501
|
-
const shades = /* @__PURE__ */ new Map();
|
|
502
|
-
const RE_INNER = /['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8})['"]?/g;
|
|
503
|
-
let im;
|
|
504
|
-
while ((im = RE_INNER.exec(innerBlock)) !== null) {
|
|
505
|
-
shades.set(im[1].toLowerCase(), im[2]);
|
|
506
|
-
}
|
|
507
|
-
if (shades.size === 0) continue;
|
|
508
|
-
const hex = shades.get("default") ?? shades.get("500") ?? shades.get("600") ?? shades.get("400") ?? shades.get("700") ?? shades.values().next().value;
|
|
509
|
-
if (hex) {
|
|
510
|
-
const key = outerName.toLowerCase();
|
|
511
|
-
if (!colors.has(key)) colors.set(key, hex);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return colors;
|
|
515
|
-
}
|
|
516
|
-
function extractCssCustomProps(content) {
|
|
517
|
-
const colors = /* @__PURE__ */ new Map();
|
|
518
|
-
const RE = /--([\w-]+)\s*:\s*(#[0-9a-fA-F]{3,8})\b/g;
|
|
519
|
-
let m;
|
|
520
|
-
while ((m = RE.exec(content)) !== null) {
|
|
521
|
-
const [, varName, hex] = m;
|
|
522
|
-
if (!HEX_COLOR.test(hex)) continue;
|
|
523
|
-
const stripped = varName.replace(/^(?:color|colour|clr|c|qs|qa)[-_]/, "").toLowerCase();
|
|
524
|
-
if (!colors.has(stripped)) colors.set(stripped, hex);
|
|
525
|
-
if (!colors.has(varName.toLowerCase())) colors.set(varName.toLowerCase(), hex);
|
|
526
|
-
}
|
|
527
|
-
return colors;
|
|
528
|
-
}
|
|
529
|
-
var CSS_SEARCH_DIRS = [
|
|
530
|
-
"",
|
|
531
|
-
// root
|
|
532
|
-
"src",
|
|
533
|
-
"styles",
|
|
534
|
-
"css",
|
|
535
|
-
"src/styles",
|
|
536
|
-
"src/css",
|
|
537
|
-
"src/app",
|
|
538
|
-
"app",
|
|
539
|
-
"assets",
|
|
540
|
-
"assets/css",
|
|
541
|
-
"assets/styles",
|
|
542
|
-
"public"
|
|
543
|
-
];
|
|
544
|
-
function detectTheme(targetDir) {
|
|
545
|
-
const draft = {
|
|
546
|
-
primary: PLACEHOLDER,
|
|
547
|
-
primaryDark: PLACEHOLDER,
|
|
548
|
-
accent: PLACEHOLDER,
|
|
549
|
-
accentDark: PLACEHOLDER,
|
|
550
|
-
sage: PLACEHOLDER,
|
|
551
|
-
cream: PLACEHOLDER,
|
|
552
|
-
mauve: PLACEHOLDER,
|
|
553
|
-
surface: PLACEHOLDER,
|
|
554
|
-
ink: PLACEHOLDER
|
|
555
|
-
};
|
|
556
|
-
const allColors = /* @__PURE__ */ new Map();
|
|
557
|
-
const mergeColors = (extracted) => {
|
|
558
|
-
for (const [k, v] of extracted) {
|
|
559
|
-
if (!allColors.has(k)) allColors.set(k, v);
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
const tailwindNames = [
|
|
563
|
-
"tailwind.config.ts",
|
|
564
|
-
"tailwind.config.js",
|
|
565
|
-
"tailwind.config.cjs",
|
|
566
|
-
"tailwind.config.mjs"
|
|
567
|
-
];
|
|
568
|
-
for (const name of tailwindNames) {
|
|
569
|
-
const filePath = path5.join(targetDir, name);
|
|
570
|
-
if (fs5.existsSync(filePath) && assertSafeToRead(filePath)) {
|
|
571
|
-
const content = readFileSafe(filePath);
|
|
572
|
-
if (content) mergeColors(extractTailwindColors(content));
|
|
573
|
-
break;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
const CSS_EXTS = /\.(css|scss|sass|less|styl)$/i;
|
|
577
|
-
for (const rel of CSS_SEARCH_DIRS) {
|
|
578
|
-
const dirPath = rel ? path5.join(targetDir, rel) : targetDir;
|
|
579
|
-
if (!dirExists(dirPath)) continue;
|
|
580
|
-
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));
|
|
581
|
-
for (const filePath of files) {
|
|
582
|
-
if (!assertSafeToRead(filePath)) continue;
|
|
583
|
-
const content = readFileSafe(filePath);
|
|
584
|
-
if (content) mergeColors(extractCssCustomProps(content));
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
for (const [name, hex] of allColors) {
|
|
588
|
-
const key = resolveThemeKey(name);
|
|
589
|
-
if (key && draft[key] === PLACEHOLDER) {
|
|
590
|
-
draft[key] = hex;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return draft;
|
|
594
|
-
}
|
|
595
|
-
function hasDetectedColors(draft) {
|
|
596
|
-
return Object.values(draft).some((v) => v !== PLACEHOLDER);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/bin/detectors/detectCredentials.ts
|
|
600
|
-
var path6 = __toESM(require("path"), 1);
|
|
601
|
-
var fs6 = __toESM(require("fs"), 1);
|
|
602
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.';
|
|
603
390
|
var SEEDER_PATH_PATTERNS = [
|
|
604
391
|
/[\\/]seeders?[\\/]/i,
|
|
@@ -728,16 +515,16 @@ var SEEDER_SEARCH_DIRS = [
|
|
|
728
515
|
];
|
|
729
516
|
function detectCredentials(targetDir) {
|
|
730
517
|
const allMatches = [];
|
|
731
|
-
const envExample =
|
|
732
|
-
if (
|
|
518
|
+
const envExample = path5.join(targetDir, ".env.example");
|
|
519
|
+
if (fs5.existsSync(envExample) && assertSafeToRead(envExample)) {
|
|
733
520
|
const content = readFileSafe(envExample);
|
|
734
521
|
if (content) allMatches.push(...extractMatches(content, envExample));
|
|
735
522
|
}
|
|
736
523
|
for (const rel of SEEDER_SEARCH_DIRS) {
|
|
737
|
-
const dirPath =
|
|
524
|
+
const dirPath = path5.join(targetDir, rel);
|
|
738
525
|
if (!dirExists(dirPath)) continue;
|
|
739
526
|
const files = walk(dirPath).filter((f) => {
|
|
740
|
-
const ext =
|
|
527
|
+
const ext = path5.extname(f);
|
|
741
528
|
return [".js", ".ts", ".mjs", ".cjs", ".json"].includes(ext) && isSeederFile(f) && assertSafeToRead(f);
|
|
742
529
|
});
|
|
743
530
|
for (const f of files) {
|
|
@@ -752,22 +539,6 @@ function detectCredentials(targetDir) {
|
|
|
752
539
|
function singleQuote(s) {
|
|
753
540
|
return `'${s.replace(/'/g, "\\'")}'`;
|
|
754
541
|
}
|
|
755
|
-
function serializeTheme(theme) {
|
|
756
|
-
const lines = [];
|
|
757
|
-
const keys = Object.keys(theme);
|
|
758
|
-
for (const key of keys) {
|
|
759
|
-
const val = theme[key];
|
|
760
|
-
const isPlaceholder = val === PLACEHOLDER;
|
|
761
|
-
if (isPlaceholder) {
|
|
762
|
-
lines.push(` ${key}: ${singleQuote(val)}, // TODO: replace with your brand colour`);
|
|
763
|
-
} else {
|
|
764
|
-
lines.push(` ${key}: ${singleQuote(val)},`);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return ` theme: {
|
|
768
|
-
${lines.join("\n")}
|
|
769
|
-
}`;
|
|
770
|
-
}
|
|
771
542
|
function serializeCredentials(creds) {
|
|
772
543
|
if (creds.length === 0) {
|
|
773
544
|
return ` // ${CREDENTIALS_BANNER.replace(/\n/g, "\n // ")}
|
|
@@ -850,7 +621,7 @@ function serializePreamble() {
|
|
|
850
621
|
].join("\n");
|
|
851
622
|
}
|
|
852
623
|
function genConfigText(opts) {
|
|
853
|
-
const { namespace, isTypeScript,
|
|
624
|
+
const { namespace, isTypeScript, journey, credentials, frameworkHints = [] } = opts;
|
|
854
625
|
const filename = isTypeScript ? "qa.config.ts" : "qa.config.js";
|
|
855
626
|
const hintsComment = frameworkHints.length > 0 ? ` *
|
|
856
627
|
* Auto-detected stack:
|
|
@@ -875,7 +646,6 @@ ${frameworkHints.map((h) => ` * \u2022 ${h}`).join("\n")}` : "";
|
|
|
875
646
|
hintsComment,
|
|
876
647
|
` */`
|
|
877
648
|
].filter((l) => l !== "").join("\n");
|
|
878
|
-
const themeBlock = serializeTheme(theme);
|
|
879
649
|
const credentialsBlock = serializeCredentials(credentials);
|
|
880
650
|
const journeyBlock = serializeJourney(journey);
|
|
881
651
|
const preambleBlock = serializePreamble();
|
|
@@ -883,7 +653,9 @@ ${frameworkHints.map((h) => ` * \u2022 ${h}`).join("\n")}` : "";
|
|
|
883
653
|
`const config${typeAnnotation} = {`,
|
|
884
654
|
` namespace: ${singleQuote(namespace)},`,
|
|
885
655
|
``,
|
|
886
|
-
|
|
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.`,
|
|
887
659
|
``,
|
|
888
660
|
` brand: {`,
|
|
889
661
|
` label: 'TODO: Your Project Name', // displayed in the QA panel header`,
|
|
@@ -1003,13 +775,13 @@ var REPO_URL = "https://github.com/mohammed-farhood/qapture";
|
|
|
1003
775
|
var PKG_VERSION = (() => {
|
|
1004
776
|
try {
|
|
1005
777
|
const candidates = [
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
778
|
+
path6.join(__dirname, "..", "..", "package.json"),
|
|
779
|
+
path6.join(__dirname, "..", "package.json"),
|
|
780
|
+
path6.join(__dirname, "package.json")
|
|
1009
781
|
];
|
|
1010
782
|
for (const p of candidates) {
|
|
1011
|
-
if (
|
|
1012
|
-
const pkg = JSON.parse(
|
|
783
|
+
if (fs6.existsSync(p)) {
|
|
784
|
+
const pkg = JSON.parse(fs6.readFileSync(p, "utf8"));
|
|
1013
785
|
if (pkg.version) return pkg.version;
|
|
1014
786
|
}
|
|
1015
787
|
}
|
|
@@ -1019,14 +791,14 @@ var PKG_VERSION = (() => {
|
|
|
1019
791
|
})();
|
|
1020
792
|
function readTargetPkg(targetDir) {
|
|
1021
793
|
try {
|
|
1022
|
-
const raw =
|
|
794
|
+
const raw = fs6.readFileSync(path6.join(targetDir, "package.json"), "utf8");
|
|
1023
795
|
return JSON.parse(raw);
|
|
1024
796
|
} catch {
|
|
1025
797
|
return {};
|
|
1026
798
|
}
|
|
1027
799
|
}
|
|
1028
800
|
function hasFile(targetDir, ...names) {
|
|
1029
|
-
return names.some((n) =>
|
|
801
|
+
return names.some((n) => fs6.existsSync(path6.join(targetDir, n)));
|
|
1030
802
|
}
|
|
1031
803
|
function detectFrameworkHints(targetDir, pkg) {
|
|
1032
804
|
const hints = [];
|
|
@@ -1079,7 +851,7 @@ function printVersion() {
|
|
|
1079
851
|
`);
|
|
1080
852
|
}
|
|
1081
853
|
var DIVIDER = "\u2500".repeat(60);
|
|
1082
|
-
function printSummary(targetDir, configFile, results, routeCount, credCount
|
|
854
|
+
function printSummary(targetDir, configFile, results, routeCount, credCount) {
|
|
1083
855
|
const icon = (r) => r === "skipped" ? " (skip)" : " \u2713";
|
|
1084
856
|
const configLabel = configFile;
|
|
1085
857
|
const preambleLabel = "qa.preamble.md";
|
|
@@ -1101,9 +873,8 @@ ${icon(results.preamble)} ${preambleLabel}${preambleNote}
|
|
|
1101
873
|
\u2713 ${agentsLabel}${agentsNote}
|
|
1102
874
|
|
|
1103
875
|
Detected:
|
|
1104
|
-
\u2022 Routes/steps
|
|
1105
|
-
\u2022
|
|
1106
|
-
\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)"}
|
|
1107
878
|
|
|
1108
879
|
${DIVIDER}
|
|
1109
880
|
Mount the widget near your app root:
|
|
@@ -1155,16 +926,16 @@ function main(argv2) {
|
|
|
1155
926
|
printUsage();
|
|
1156
927
|
process2.exit(0);
|
|
1157
928
|
}
|
|
1158
|
-
const targetDir =
|
|
929
|
+
const targetDir = path6.resolve(args.dir);
|
|
1159
930
|
const { force } = args;
|
|
1160
|
-
if (!
|
|
931
|
+
if (!fs6.existsSync(targetDir)) {
|
|
1161
932
|
process2.stderr.write(`
|
|
1162
933
|
Error: target directory does not exist: ${targetDir}
|
|
1163
934
|
|
|
1164
935
|
`);
|
|
1165
936
|
process2.exit(1);
|
|
1166
937
|
}
|
|
1167
|
-
if (!
|
|
938
|
+
if (!fs6.statSync(targetDir).isDirectory()) {
|
|
1168
939
|
process2.stderr.write(`
|
|
1169
940
|
Error: ${targetDir} is not a directory
|
|
1170
941
|
|
|
@@ -1181,10 +952,6 @@ qapture init \u2014 scanning ${targetDir} ...
|
|
|
1181
952
|
`);
|
|
1182
953
|
const journey = detectRoutes(targetDir);
|
|
1183
954
|
const routeCount = journey.reduce((n, lane) => n + lane.steps.length, 0);
|
|
1184
|
-
process2.stdout.write(` Detecting theme ...
|
|
1185
|
-
`);
|
|
1186
|
-
const theme = detectTheme(targetDir);
|
|
1187
|
-
const colorsDetected = hasDetectedColors(theme);
|
|
1188
955
|
process2.stdout.write(` Detecting credentials (safe sources only) ...
|
|
1189
956
|
`);
|
|
1190
957
|
const credentials = detectCredentials(targetDir);
|
|
@@ -1195,16 +962,15 @@ qapture init \u2014 scanning ${targetDir} ...
|
|
|
1195
962
|
const { filename: configFilename, text: configText } = genConfigText({
|
|
1196
963
|
namespace,
|
|
1197
964
|
isTypeScript,
|
|
1198
|
-
theme,
|
|
1199
965
|
journey,
|
|
1200
966
|
credentials,
|
|
1201
967
|
frameworkHints
|
|
1202
968
|
});
|
|
1203
969
|
const preambleText = genPreambleText({ projectName, frameworkHints });
|
|
1204
|
-
const configPath =
|
|
1205
|
-
const preamblePath =
|
|
1206
|
-
const skillPath =
|
|
1207
|
-
const agentsMdPath =
|
|
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");
|
|
1208
974
|
const configResult = writeIfAbsent(configPath, configText, force);
|
|
1209
975
|
const preambleResult = writeIfAbsent(preamblePath, preambleText, force);
|
|
1210
976
|
writeAlways(skillPath, SKILL_default);
|
|
@@ -1215,8 +981,7 @@ qapture init \u2014 scanning ${targetDir} ...
|
|
|
1215
981
|
configFilename,
|
|
1216
982
|
{ config: configResult, preamble: preambleResult, skill: skillResult, agents: agentsResult },
|
|
1217
983
|
routeCount,
|
|
1218
|
-
credentials.length
|
|
1219
|
-
colorsDetected
|
|
984
|
+
credentials.length
|
|
1220
985
|
);
|
|
1221
986
|
}
|
|
1222
987
|
main(process2.argv.slice(2));
|