Skip to content
Back to skills

Qf Init

ASecurity

First-time Qualflare setup for this project. Detects languages and test frameworks, writes .qualflare/test-state.md, and configures the optional Stop hook. Use when the user runs /qf-init or asks to "set up Qualflare" or "initialize Qualflare".

  • 3 stars
  • 0 votes
  • 0 copies
  • 8 views
  • Added May 27, 2026
testing-securitytypescriptpythonrustgophpshellbashreactnodegit

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add Qualflare/qualflare-claude-code --skill qf-init --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Qf Init?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Qf Init
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/qualflare-qf-init/badge)](https://www.skillsdirectory.com/skills/qualflare-qf-init)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: qf-init
description: >
  First-time Qualflare setup for this project. Detects languages and test
  frameworks, writes .qualflare/test-state.md, and configures the optional
  Stop hook. Use when the user runs /qf-init or asks to "set up
  Qualflare" or "initialize Qualflare".
allowed-tools: Read Write Edit Glob Bash(git:*) Bash(mkdir:*) Bash(node:*) Bash(qf:*)
---

You are executing the `qf-init` skill. Follow every step below in order. Do not skip steps or reorder them.

---

## Step 1 — Verify project root

Check whether the current working directory (`${CLAUDE_PROJECT_DIR}`) contains at least one of the following:

- `.git/`
- `package.json`
- `go.mod`
- `pyproject.toml`
- `Cargo.toml`
- `Gemfile`
- `composer.json`
- `pom.xml`
- `build.gradle`

Use the Read tool to check for these files. If **none** of them is present, stop and tell the user:

> "I couldn't find a recognizable project root in the current directory. Please `cd` to your project root and run `/qf-init` again."

Do not proceed past this step if no root indicator is found.

---

## Step 2 — Detect workspaces

Check whether the project is a monorepo by looking for any of these signals at `${CLAUDE_PROJECT_DIR}`:

1. **npm/pnpm workspaces**: Read `package.json`. If it contains a top-level `workspaces` key (either an array or an object with a `packages` array), the project is a monorepo.
2. **pnpm workspace file**: Check whether `pnpm-workspace.yaml` exists.
3. **Multiple Go modules**: Use Glob with pattern `**/go.mod`. If more than one file is found and at least two are in subdirectories matching `apps/`, `services/`, `packages/`, `cmd/`, or `libs/`, the project is a monorepo.
4. **Multiple Python packages**: Use Glob with pattern `**/pyproject.toml`. If more than one file is found under `apps/`, `services/`, `packages/`, or `libs/`, the project is a monorepo.

**If NO signal matches — single-package project:**

Create a synthetic package list with one entry:
- Path: `(root)`
- Name: inferred from the root manifest — value of `package.json` `"name"` field, last segment of `go.mod` module path (e.g., `module github.com/acme/api` → `api`), `pyproject.toml` `[project].name`, or the project directory name as a fallback.

Proceed to Step 3 with this single-entry list.

**If ANY signal matches — monorepo:**

Enumerate packages. The source depends on the signal:
- **npm/pnpm workspaces**: resolve the workspace glob patterns (e.g., `packages/*`) from `package.json` `workspaces` or `pnpm-workspace.yaml` `packages:` to get the actual subdirectory list. Use Glob to expand each pattern.
- **Multiple go.mod**: each directory containing a `go.mod` (other than the root, if there is a root one) is a package.
- **Multiple pyproject.toml**: each directory containing a `pyproject.toml` (other than the root) is a package.

For each package directory found, build a package entry:
- **Path**: directory path relative to `${CLAUDE_PROJECT_DIR}` (e.g., `packages/web`).
- **Name**: read the manifest in that directory — `package.json` `"name"`, last segment of `go.mod` module path, `pyproject.toml` `[project].name`. If no manifest or no name field, fall back to the directory path itself.

**Edge case:** If workspace declarations exist but no matching directories are found on disk, treat the project as single-package and emit a note: "Workspace configuration found but no matching package directories exist — treating as single-package."

---

## Step 3 — Detect stack via subagent

Tell the user: `"Detecting your project's tech stack..."`

For monorepos with more than one package: `"Detecting tech stack for <N> packages..."`

Dispatch **one Explore subagent per package**. Each subagent is scoped to its package directory. Compute `<PACKAGE_DIR>` per package:
- If the package path is `(root)`: `<PACKAGE_DIR>` = `${CLAUDE_PROJECT_DIR}`
- Otherwise: `<PACKAGE_DIR>` = `${CLAUDE_PROJECT_DIR}/<package-path>`

Use this brief verbatim for each subagent — substitute `<PACKAGE_DIR>` and `${CLAUDE_PLUGIN_ROOT}` with actual paths:

> "You are a project stack detector. Read the manifest and config files in the package at: `<PACKAGE_DIR>`. Only report frameworks for files under this package directory — do not scan the whole repository.
>
> Your goal: identify all programming languages, test frameworks in use, and test file locations.
>
> **What to read:** `package.json`, `go.mod`, `pyproject.toml`, `Cargo.toml`, `Gemfile`, `composer.json`, `pom.xml`, `build.gradle`, `.nvmrc`, `.python-version`, and any framework config files (`jest.config.*`, `playwright.config.*`, `cypress.config.*`, `.rspec`, `phpunit.xml`, `sonar-project.properties`).
>
> **Framework slugs:** You MUST map every framework you detect to exactly one of the canonical slugs listed in the file at: `${CLAUDE_PLUGIN_ROOT}/skills/qf-init/references/framework-slugs.md`. Read that file first. Use ONLY slugs from that list. **Exception — Vitest:** report a Vitest package under `jest`, never `vitest` (the reference lists `vitest` only because the CLI accepts it; /qf-run, /qf-fix and /qf-doctor handle Vitest through the `jest` row).
>
> **Glob test files:** For each detected framework, use the `Glob` tool with the glob patterns from the reference file to estimate the test file count; for a slug whose glob cell is `*(skip counting)*`, do not Glob — report its count as `—`. Report: slug, estimated test count, top-level test directories (paths relative to `${CLAUDE_PROJECT_DIR}`, not `<PACKAGE_DIR>`).
>
> **Suggestions:** If you see strong indicators for a framework the project doesn't currently use (e.g., React SPA with no E2E framework), note it as a suggestion.
>
> **Return a structured report with these sections:**
> 1. Languages detected (list)
> 2. Frameworks in use: table of slug | file count | top-level paths (relative to project root)
> 3. Frameworks suggested (not yet installed): list of slug + reason
> 4. Naming conventions observed (e.g., `*.test.ts`, `*_test.go`)
> 5. Coverage threshold (from `jest.config.*`, `pyproject.toml`, etc. — or 'none detected')"

Wait for all subagents to complete. Collect each package's structured report.

---

## Step 4 — Confirm with user

Present the detection results in a readable format. For monorepos, group results by package:

```
packages/web  (@acme/web)
  jest (TypeScript) — 42 test files    src/**/*.test.ts
  playwright (TypeScript) — 18 test files    e2e/**/*.spec.ts

packages/api  (acme-api)
  golang (Go) — 15 test files    **/*_test.go
```

For single-package projects, use the flat format without package headers.

Then ask:

> "Does this look right? You can correct any framework names, add missing ones, or remove incorrect ones."

Accept any corrections the user provides. Update your in-memory list of detected frameworks and notes accordingly.

After the user confirms the framework list, ask one more question:

> "Any notes about test conventions in this project? (Or press Enter to skip)"

Accept free-text input. If the user presses Enter or provides nothing, record this as "None".

---

## Step 5 — Write `.qualflare/test-state.md`

If `.qualflare/test-state.md` already exists, tell the user:

> "`.qualflare/test-state.md` already exists. I'll overwrite it with the updated information."

Create the `.qualflare/` directory if it does not exist:

```bash
mkdir -p "${CLAUDE_PROJECT_DIR}/.qualflare"
```

Write (or overwrite) `${CLAUDE_PROJECT_DIR}/.qualflare/test-state.md` using the template below. Fill in all `<placeholder>` values from Steps 3 and 4:

- `<project-name>`: for single-package, infer from root manifest; for monorepos, use the directory name or a descriptive label.
- `<languages>`: comma-separated list of all detected languages across all packages (e.g., `TypeScript, Go`).
- `<ISO 8601 timestamp>`: current date and time in ISO 8601 format (e.g., `2026-04-20T14:32:00Z`).
- `<plugin-version>`: read the `version` field from `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json` (e.g., `0.16.0`).
- `## Packages` table: one row per package with `Path` and a derived `Identifier` (see derivation rule below). For single-package: one row with path `(root)`.
- `## Frameworks in use` table: one row per (package, framework) pair including the `Package` column. For single-package: use `(root)` in the Package column.
- `<suggestions>`: bullet list of suggested frameworks with reasons, or `None` if empty.
- `<naming-pattern>`: the observed naming convention (e.g., `*.test.ts`, `*_test.go`).
- `<coverage-threshold>`: the detected coverage threshold, or `none detected`.
- `<user-notes>`: the user's free-text notes from Step 3, or `None`.

**Identifier derivation:** For each package, derive an `Identifier` from its name by:
1. Lowercasing the name.
2. Replacing every character outside `[a-z0-9_-]` with `-` (this covers `@`, `/`, `.`, whitespace, etc.).
3. Collapsing runs of `-` into a single `-`.
4. Trimming leading and trailing `-`.
5. If the result is empty or starts with anything other than `[a-z0-9]` (e.g., would otherwise begin with `_`), prepend the literal string `pkg-`.
6. If the result matches a CLI-reserved name — `login`, `logout`, `projects`, `version`, `list-formats`, `formats`, `lf`, `help`, `completion`, `__complete` — prepend the literal string `pkg-`. (E.g., a package called `help` becomes `pkg-help`.)
7. Truncating to 63 characters.

The identifier must match `^[a-z0-9][a-z0-9_-]{0,62}$` and is used as the local CLI alias passed to `qf <identifier> collect …`. Examples: `@acme/web` → `acme-web`, `Acme.Api` → `acme-api`, `my pkg` → `my-pkg`, `help` → `pkg-help`.

```markdown
<!-- qualflare-test-state v:1 -->
# Qualflare Test State

> Source of truth for AI coding agents working on this project's tests.
> Regenerate with `/qf-init`.

## Project
- Name: <project-name>
- Languages: <languages>
- Generated at: <ISO 8601 timestamp>
- Plugin version: <plugin-version>

## Packages
| Path | Identifier |
|------|------------|
<one row per package>

## Frameworks in use
| Package | Slug | Language | File count | Top-level paths |
|---------|------|----------|------------|-----------------|
<one row per (package, framework) pair>

## Frameworks suggested (not yet installed)
<bullet list, or "None" if empty>

## Conventions
- Test naming: <naming-pattern>
- Coverage threshold: <coverage-threshold>

## Notes
<user-notes>
```

**Single-package example:**
```markdown
## Packages
| Path | Identifier |
|------|------------|
| (root) | my-app |

## Frameworks in use
| Package | Slug | Language | File count | Top-level paths |
|---------|------|----------|------------|-----------------|
| (root) | jest | TypeScript | 42 | src/**/*.test.ts |
```

**Monorepo example:**
```markdown
## Packages
| Path | Identifier |
|------|------------|
| packages/web | acme-web |
| packages/api | acme-api |

## Frameworks in use
| Package | Slug | Language | File count | Top-level paths |
|---------|------|----------|------------|-----------------|
| packages/web | jest | TypeScript | 42 | packages/web/src/**/*.test.ts |
| packages/api | golang | Go | 15 | packages/api/**/*_test.go |
```

---

## Step 6 — Set up authentication

Run:
```bash
qf version
```

If the CLI is unavailable (exit 127 or command not found), tell the user:
> "`qf` CLI not found. Install it first from https://qualflare.com/docs/cli, then re-run `/qf-init`."
Stop here.

If the `## Packages` table has no `Identifier` values (the user skipped that input), tell the user:
> "No project identifiers configured in test-state.md. Edit `.qualflare/test-state.md` to fill in the `Identifier` column, then run `/qf-init` again."

Stop here — skip the rest of Step 6.

Run:
```bash
qf projects
```

Capture the set of identifiers that are already saved. Two output shapes to handle:
- The CLI's hint `No projects configured. Run 'qf login <identifier> <token>' to get started.` → treat as zero configured. (That hint is the CLI's own wording; never repeat its `<token>` argument form to the user.)
- One identifier per line → parse into a set.

Build `missing` = the list of `Identifier` values from `## Packages` that are **not** present in the saved set.

If `missing` is empty: print `✅  All project identifiers are already authenticated.` and proceed to Step 7.

**Never ask for, accept, or handle the token yourself.** A Qualflare token is a project write credential: anything typed into this chat is sent to the model and kept in the session transcript, and a token passed as a command-line argument is visible in the process list and shell history. The user registers each token with `qf login <identifier>`, which reads it at a hidden terminal prompt. If the user pastes a token into the chat anyway, do not use it or repeat it; tell them to revoke it in Qualflare (it is now in the transcript), create a new one, and register it with `qf login` as below.

Print, once, for all identifiers in `missing`:

```
🔑 Authenticate these Qualflare projects from your own terminal — not in this chat:

     qf login <identifier-1>
     qf login <identifier-2>

   Each command asks for the token at a hidden prompt.
   Create tokens at: https://app.qualflare.com/project/<identifier>/settings/access-tokens

   Reply `done` when finished, or `skip` to authenticate later.
```

Emit one `qf login <identifier>` line per missing identifier. Then **stop and wait for the user's next message.** Do not run `qf login` yourself: it needs an interactive terminal for the hidden prompt, and the Bash tool does not provide one.

- If the user replies `skip` (case-insensitive): print `⏭  Skipped authentication — run \`qf login <identifier>\` in your terminal before running \`/qf-run\`.` and proceed to Step 7.
- Otherwise re-run `qf projects` and rebuild `missing`. For each identifier that is now saved, print `✅  Saved credentials for <identifier>.` For each identifier still missing, print `⚠️  Identifier \`<id>\` still not saved — run \`qf login <id>\` in your terminal before using \`/qf-run\`.` Then proceed to Step 7 (do not loop).

---

## Step 7 — Ask about Stop hook (was Step 6)

Ask the user:

> "Enable the post-session test suggestion hook? After each Claude Code session where source files changed without matching test edits, it prints a one-line suggestion to run /qf-cover. This is a passive nudge only — it never writes code automatically.
>
> You can toggle it later with `/qf-hook on` or `/qf-hook off`."

Accept `yes`, `no`, `y`, or `n` (case-insensitive). Treat any variant of "yes"/"y" as `true` and any variant of "no"/"n" as `false`.

Create `.qualflare/` if not already done. Write `${CLAUDE_PROJECT_DIR}/.qualflare/config.json` with the following content, substituting `<true or false>` with the boolean result:

```json
{
  "version": 1,
  "stopHookEnabled": <true or false>
}
```

---

## Step 8 — Update CLAUDE.md

Read `${CLAUDE_PROJECT_DIR}/CLAUDE.md` if it exists.

Determine which case applies:

**Case A — CLAUDE.md does not exist:** Create it with only the marker block (see content below).

**Case B — CLAUDE.md exists and already contains `<!-- BEGIN qualflare-ai -->`:** Replace only the content between `<!-- BEGIN qualflare-ai -->` and `<!-- END qualflare-ai -->`. Do not modify any content outside those markers.

**Case C — CLAUDE.md exists but does not contain `<!-- BEGIN qualflare-ai -->`:** Append the full marker block (markers + content) at the very end of the file. Do not modify any existing content.

The marker block to write — substitute `<slugs>` with a comma-separated list of all confirmed framework slugs from Step 4, and include the `<packages-line>` only when applicable (see rule below):

```
<!-- BEGIN qualflare-ai -->
## Qualflare Test Integration

This project uses [Qualflare](https://qualflare.com) for test management.

### Session startup
At the start of every session, read `.qualflare/test-state.md` silently before responding. This gives you the current framework list, package layout, naming conventions, and test context — so `/qf-cover`, `/qf-run`, and `/qf-fix` are context-aware from the first message.

### Quick reference
- Test state: `.qualflare/test-state.md` — auto-read at session start (see above)
- Upload results: `qf <identifier> collect <results-file>` (identifier from `## Packages`)
- Frameworks: <slugs>
<packages-line>
- Skill: `qf-cover` — use when asked to write or improve tests
- Command: `/qf-cover` — generate tests for changed code
- Command: `/qf-run` — run tests and upload results to Qualflare
- Command: `/qf-fix` — fix failing tests from the last run
- Command: `/qf-doctor` — health check: CLI, auth, config, file-count drift
- Command: `/qf-update` — refresh file counts in test-state.md
- Command: `/qf-state` — inspect current Qualflare state
- Command: `/qf-init` — re-run setup (re-detect frameworks, reset state)
<!-- END qualflare-ai -->
```

**`<packages-line>` rule:**
- If the project has **more than one package**: emit exactly `- Packages: <N> (see .qualflare/test-state.md ## Packages for the list)` where `<N>` is the package count.
- If the project is **single-package**: omit this line entirely (do not emit a blank line in its place).

**Critical rules:**
- Never remove or alter content outside the markers.
- Never duplicate the marker block.
- The markers themselves must appear on their own lines exactly as shown above.

---

## Step 9 — Outro

Run `qf projects` to list the locally configured CLI identifiers. Two cases to handle:

- **Output is the CLI's hint** `No projects configured. Run 'qf login <identifier> <token>' to get started.` → treat as zero configured.
- **Output is one identifier per line** → parse into a set.

Cross-reference the identifiers in the `## Packages` table against this set. Build a list of `Identifier` values that are missing locally.

Print the following summary. Include the `⚠️  Authenticate` block **only** when one or more identifiers are still not configured after Step 6 (i.e., were skipped, not yet registered, or the CLI wasn't found):

```
✅ Qualflare initialized!

Created:
  .qualflare/test-state.md     — project test context
  .qualflare/config.json       — hook setting
  CLAUDE.md                    — updated with Qualflare section

Next steps:
  /qf-cover   — generate tests for changed code
  /qf-run     — run tests and upload to Qualflare
  /qf-update  — refresh file counts after adding tests
  ⚠️  Still need to authenticate before running /qf-run — in your own terminal:
      qf login <identifier-1>
      qf login <identifier-2>
      Each asks for the token at a hidden prompt. Get tokens at https://app.qualflare.com/project/<identifier>/settings/access-tokens
```

Emit one `qf login <identifier>` line per identifier that is still missing from `qf projects`. Omit the entire `⚠️  Authenticate` block if every identifier in `## Packages` is configured.

If `qf` itself is not on PATH (the command exits 127 / not found), still print the summary and append:

```
  ⚠️  qf CLI not found on PATH. Install it from https://qualflare.com/docs/cli, then run `qf login <identifier>` in your terminal for each package above.
```

---

## Edge cases

- **`.qualflare/test-state.md` already exists:** Overwrite it after informing the user (as described in Step 5). Do not ask for confirmation beyond the note — the user already triggered re-init by running `/qf-init`.
- **CLAUDE.md markers already exist:** Update the content between the markers in-place. Do not append a second block. Do not touch content outside the markers. (Case B above.)
- **User provides no notes in Step 4:** Record `None` in the `## Notes` section.
- **Subagent detects vitest:** Use the `jest` slug (the CLI reads `vitest` with the same parser, and /qf-run's `jest` row runs Vitest). Write exactly `jest` in the Slug column — never `jest (vitest)`, which breaks slug lookups; mention Vitest under `## Notes` instead.
- **Subagent detects cargo-test (Rust):** Do not assign a slug. Include a warning note in `.qualflare/test-state.md` under `## Notes` that cargo-test is detected but not yet uploadable to Qualflare.
- **No test frameworks detected at all:** Do not abort. Write the state file with an empty framework table and add a note: "No test frameworks detected automatically. Edit this file manually to add framework entries."
- **Workspace declarations found but no package directories on disk:** Treat as single-package. Emit a note in the outro.
- **Package has no detected frameworks:** Include it in `## Packages` table but omit it from `## Frameworks in use`. Note it in the outro with: "No frameworks detected in `<package-path>` — edit `.qualflare/test-state.md` manually to add entries."
- **`package.json` lacks a `name` field:** Fall back to the directory path as the Qualflare project name. Do not prompt the user.

Files in this skill

  • SKILL.md19.6 KB
  • references/framework-slugs.md5.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.