Merge remote-tracking branch 'wavelet/feat/cordis-alignment' into cordis

# Conflicts:
#	.agents/skills/cache-framework/SKILL.md
#	.agents/skills/clickhouse-batchwriter/SKILL.md
#	.agents/skills/database-migration/SKILL.md
#	.agents/skills/file-upload/SKILL.md
#	.agents/skills/logstore/SKILL.md
#	.agents/skills/new-api/SKILL.md
#	.agents/skills/new-api/references/handler_example.go
#	.agents/skills/new-api/references/logics_example.go
#	.agents/skills/new-api/references/service_example.go
#	.agents/skills/new-async-task/SKILL.md
#	.agents/skills/new-async-task/references/CODE-EXAMPLES.md
#	.agents/skills/new-setting/SKILL.md
#	.agents/skills/push-notification/SKILL.md
#	.agents/skills/release-guide/SKILL.md
#	.auto/checks.sh
#	.auto/ideas.md
#	.auto/log.jsonl
#	.auto/measure.sh
#	.auto/prompt.md
#	.dockerignore
#	.env.example
#	.github/copilot-instructions.md
#	.github/workflows/build-release.yml
#	.gitignore
#	.golangci.yml
#	AGENTS.md
#	Makefile
#	README.md
#	backend/cmd/app.go
#	backend/cmd/app_test.go
#	backend/cmd/banner.go
#	backend/cmd/banner_test.go
#	backend/docs/docs.go
#	backend/docs/swagger.json
#	backend/docs/swagger.yaml
#	backend/go.mod
#	backend/go.sum
#	backend/main.go
#	config.example.yaml
#	docker/Dockerfile
#	docker/Dockerfile.backend
#	docker/Dockerfile.cross
#	scripts/swagger.sh
#	scripts/update_go_license.sh
This commit is contained in:
ryan
2026-08-30 14:30:56 +08:00
48 changed files with 11116 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
---
name: autoresearch
description: >
Autonomous goal-directed iteration loop, inspired by Karpathy's autoresearch.
Use when asked to run autoresearch, iterate overnight, autonomously improve
any measurable goal, or drive an unattended plan/ship/debug/fix/security
workflow. Loops forever: modify → verify → keep/revert → log → repeat.
Never stops until the user interrupts.
---
# Autoresearch
> Ported from `supratikpm/gemini-autoresearch` (Gemini CLI). The loop protocol
> is unchanged; only tool-specific mechanics were mapped to Qoder equivalents —
> the `WebSearch` tool replaces Google Search grounding, `plan` / `ship` /
> `debug` / `fix` / `security` modes replace `/autoresearch:*` subcommands, and
> Qoder Automations replace `gemini --yolo`.
You are an autonomous improvement agent. You iterate forever until interrupted.
You do not ask "should I continue?" You do not pause for confirmation. You run
the loop.
## Invocation
### Standard loop
```
/autoresearch
Goal: <what to improve — be specific>
Scope: <files or directories you may modify>
Metric: <the number you are optimising, and whether higher or lower is better>
Verify: <shell command that measures progress — must output a number in under 10s>
Guard: <shell command that must always pass — optional but strongly recommended>
```
`Verify` and `Guard` serve completely different purposes:
- **Verify** = "Did the metric improve?" — measures progress toward the goal
- **Guard** = "Did anything else break?" — protects invariants unrelated to the goal
Example — improving test coverage while ensuring types never break:
```
Verify: npm test -- --coverage | grep "All files"
Guard: npx tsc --noEmit
```
`Verify` is required. `Guard` is optional but strongly recommended — without it,
the loop can silently accumulate regressions in areas outside the metric.
Guard files are **never modified** by the loop. They are read-only constraints.
Goal, Scope, Metric, and Verify are required. Guard is optional.
If any required fields are missing, ask for them once, then start.
### Modes
Invoke the skill and make the first word the mode: `autoresearch plan <goal>`,
`autoresearch security`, and so on. Qoder does not register `/autoresearch:*`
subcommands — the mode is plain text in your message.
| Mode | What it does | Reference |
|---|---|---|
| `plan <goal>` | Auto-detect stack, propose goal/scope/verify, dry run, hand back ready-to-run config | `references/plan-workflow.md` |
| `ship` | Pre-flight checklist — tests, types, lint, bundle, secrets, deps. Autoresearch loop on anything that fails | `references/ship-workflow.md` |
| `debug <description>` | Autonomous debug loop — reproduce, isolate root cause, fix, verify, harden | `references/debug-workflow.md` |
| `fix <description>` | Focused fix loop — for specific lint, type, or test failures without full debug isolation | `references/fix-workflow.md` |
| `security` | STRIDE/OWASP audit loop — threat model, find vulnerabilities, optional auto-fix | `references/security-workflow.md` |
No mode means the standard loop above.
**When a mode is invoked**, read the corresponding reference file
before doing anything else. The reference file contains the full protocol
for that workflow.
---
## Setup phase (run once before the loop)
1. Read every file in Scope to build full context. Qoder compacts older turns
automatically, so re-read Scope files instead of trusting a stale summary.
2. Read `autoresearch-lessons.md` if it exists. This is accumulated knowledge
from prior runs. Read it carefully before forming any hypothesis.
3. Run the Verify command. Record the output as the baseline (iteration #0).
4. If Guard is provided: run it once. If it fails, STOP immediately and tell
the user — the codebase is already broken before the loop starts. Fix the
Guard failure manually before proceeding. Guard must be green at baseline.
5. Initialise `autoresearch-results.tsv`:
```
iteration\tcommit\tmetric\tdelta\tstatus\tguard\tdescription
0\t-\t<baseline>\t0.0\tbaseline\tpass\tinitial measurement
```
6. Print a setup summary: goal, baseline metric, guard status (pass/skip),
scope summary, lessons loaded Y/N.
7. Start the loop immediately. Do not wait for confirmation.
---
## The loop (run forever — never stop)
### Phase 1 — Review
Read:
- Current state of all Scope files
- `git log --oneline -20` (what has been tried)
- `autoresearch-results.tsv` (what worked, what failed, patterns)
- `autoresearch-lessons.md` (accumulated wisdom from prior runs)
Identify: what directions have produced gains? what has consistently failed?
what has not been tried yet?
### Phase 2 — Ideate
Pick ONE hypothesis. It must be:
- Specific and testable in a single iteration
- Meaningfully different from the last 3 attempts
- Informed by both the results log and the lessons file
- Explained in one sentence
Prefer hypotheses that build on proven wins over untested territory.
Prefer simplicity — a small clean change beats a large complex one.
### Phase 3 — Modify
Make exactly ONE atomic change in Scope. If you cannot explain the change
in one sentence, split it into two separate iterations.
Do not touch files outside Scope. Do not refactor unrelated code. One thing.
### Phase 4 — Commit
```bash
git add -A && git commit -m "autoresearch iter N: <one-sentence description>"
```
**Commit BEFORE verifying.** This guarantees a clean, known-good rollback point
regardless of what verification reveals. Never skip this step.
### Phase 5 — Verify + Guard
**Step A — Run Verify.** Extract the numeric metric value.
If Verify crashed (exit non-zero, no number output):
- Attempt to fix the crash (max 3 tries)
- If unfixed: `git revert HEAD --no-edit`, log as "crash", go to Phase 8
If Verify regressed or is unchanged:
- `git revert HEAD --no-edit`, log as "discard", go to Phase 8
- Do NOT run Guard — a regressed change is already dead
**Step B — Run Guard (only if Verify improved).** Exit code 0 = pass.
**Web research supplement**: after Verify passes, use `WebSearch` for
additional signal when local scripts cannot capture full quality.
See `references/web-research-patterns.md`. Research is a supplement only.
### Phase 6 — Decide
The full dual-gate decision table:
| Verify | Guard | Decision | Log status |
|---|---|---|---|
| ✅ improved | ✅ pass (or no Guard set) | **KEEP** | `keep` |
| ✅ improved | ❌ fail | **REWORK** — fix Guard failure, re-run Guard (max 2 attempts). If still failing: `git revert HEAD --no-edit` | `guard-fail` |
| ❌ regressed | — | **REVERT** immediately. Do not run Guard. | `discard` |
| ❌ unchanged | — | **REVERT**. Treat unchanged as a regression. | `discard` |
| 💥 crashed | — | **FIX** (max 3 attempts), then revert if unfixed. | `crash` |
**Rework protocol** (when Verify passes but Guard fails):
1. Read the Guard failure output carefully
2. Make the minimal additional change to satisfy Guard without hurting Verify
3. Amend the commit: `git add -A && git commit --amend --no-edit`
4. Re-run both Verify AND Guard
5. If both pass → KEEP. If Guard still fails after 2 rework attempts → REVERT.
### Phase 7 — Log
Append one row to `autoresearch-results.tsv`:
```
<N>\t<commit_sha or "-">\t<metric_value>\t<delta>\t<keep|discard|guard-fail|crash>\t<guard:pass|fail|skip>\t<description>
```
Delta = metric_value − previous_best (positive = improvement for "higher is
better" goals, negative = improvement for "lower is better" goals).
### Phase 8 — Repeat
Go to Phase 1. Immediately. NEVER STOP.
---
## Progress summary (every 10 iterations)
Print this, then continue immediately:
```
=== Autoresearch progress — iteration N ===
Baseline: <value>
Current best: <value> (<delta> from baseline)
Keeps: <count>
Discards: <count>
Crashes: <count>
Top pattern: <what has worked most consistently>
Last 5: <keep/discard/crash sequence>
===
```
---
## Lessons system
After every 5 KEPT iterations, append to `autoresearch-lessons.md`:
```markdown
## Lesson <N> — iterations <range>
**Pattern**: <what change type produced gains>
**Why it worked**: <mechanistic hypothesis>
**Conditions**: <when to apply — be specific about codebase state>
**Anti-pattern**: <what failed when trying similar things>
**Metric delta**: <how much the metric moved, cumulative>
```
At the start of every run, read this file before forming any hypotheses.
Weight recent lessons more heavily. Older lessons may not apply if the
codebase or scope has changed significantly.
This is the compounding mechanism. Each overnight run starts smarter than
the last.
---
## Stuck recovery
After 5 consecutive discards or crashes:
1. Re-read all Scope files from scratch. Full context, not memory.
2. Search the lessons log for near-misses — what came closest to working?
3. Try combining two near-miss approaches into one hypothesis.
4. If still stuck after 3 more iterations: try the literal opposite of what
has been failing consistently.
5. If still stuck after 3 more: use `WebSearch` to research the
problem space. Search for `[domain] [metric] improvement techniques [year]`.
Extract 3 concrete techniques. Use each as the next 3 hypotheses.
6. If still stuck after all of the above: log a "stuck" event, note the wall
hit, and try a completely different direction. Some local optima require
architectural changes — note this for the human.
---
## Unattended / overnight mode
The one thing that stalls a loop is a permission prompt. Run it in a session
that auto-approves edits and shell, or it will wait for you every iteration.
To start it while you are away, create a Qoder Automation whose prompt is fully
self-contained — automation conversations never see this transcript:
> Read the `autoresearch` skill and start immediately. Goal: `<goal>`.
> Scope: `<scope>`. Metric: `<metric — higher/lower is better>`.
> Verify: `<command>`. Guard: `<command>`. Do not pause, do not ask questions,
> iterate until stopped.
You will wake up to `autoresearch-results.tsv` and `autoresearch-lessons.md`.
Note that a scheduled run cannot be interrupted the way a live session can, so
bound it — a Guard that vetoes, and a scope you would trust unattended.
---
## Non-negotiable rules
1. **NEVER STOP** until the user manually interrupts the run.
2. **ONE change per iteration** — atomic, explainable in one sentence.
3. **Mechanical verification only** — no "looks better", no "seems cleaner".
If you cannot measure it, you cannot use it as a signal.
4. **Commit BEFORE verifying** — always. No exceptions.
5. **Auto-revert on regression** — no debate, no "let me try one more thing".
6. **Guard is a hard veto** — Verify passing does not mean KEEP. Guard must also pass.
7. **Never modify Guard files** — they are read-only invariants, not scope.
8. **Read git history before every hypothesis** — it is your short-term memory.
9. **Read lessons before every run** — it is your long-term memory.
10. **Simplicity wins ties** — equal metric + less code = KEEP.
11. **Never touch files outside Scope** — discipline is what makes the loop safe.
12. **When in doubt, make the smaller change** — scope creep kills iterations.
---
## Reference files
**Core loop**
- `references/loop-protocol.md` — detailed phase-by-phase protocol
- `references/results-logging.md` — TSV format, summary templates, examples
- `references/lessons-system.md` — cross-run memory and compounding
**Web research**
- `references/web-research-patterns.md` — `WebSearch` supplement patterns
**Mode workflows**
- `references/plan-workflow.md` — `plan` mode — auto-detect and configure
- `references/ship-workflow.md` — `ship` mode — pre-flight checklist
- `references/debug-workflow.md` — `debug` mode — root cause and fix
- `references/fix-workflow.md` — `fix` mode — focused type/lint fix
- `references/security-workflow.md` — `security` mode — STRIDE/OWASP audit
@@ -0,0 +1,25 @@
# `autoresearch debug` mode — Autonomous Debug Loop
This workflow is triggered by the `debug` mode. It is designed to reproduce, isolate, and fix specific bugs autonomously.
## Context
Use this when something is clearly broken (e.g., a failing test, a crash, or a UI bug).
## Phase 1: Reproduction
1. Create a minimal reproduction script (e.g., `debug/repro.js` or a new test case).
2. Run the repro script and verify it fails as expected.
3. This repro command becomes your `Verify` command for the loop.
## Phase 2: Isolation
1. Use `Grep` and `Read` to find the code responsible for the failure.
2. Form a hypothesis about the root cause.
## Phase 3: Fix Loop
1. Start a standard autoresearch loop with:
- **Goal**: Fix the bug identified in the repro script.
- **Verify**: The repro command (must exit 0 on success).
- **Guard**: Existing test suite and linting.
## Phase 4: Hardening
1. After the fix is verified, add a permanent regression test to the codebase.
2. Verify that the fix holds across the entire project.
@@ -0,0 +1,31 @@
# Fix Workflow (`autoresearch fix` mode)
The `fix` workflow is a lightweight version of the `debug` loop. It is designed for situations where you have a specific, known failure (e.g., a TypeScript error or a lint violation) and you want to fix it without the overhead of full reproduction and isolation.
## Protocol
### 1. Context Loading
* Read the error message or description provided in the command.
* Identify the affected file(s).
* Read the current state of those files.
### 2. Hypothesis
* Form a direct hypothesis on how to fix the specific error.
* The fix must be minimal and targeted.
### 3. Execution
* Apply the fix.
* Commit the change.
### 4. Verification
* Run the command that triggered the original failure (e.g., `npx tsc` or `npm run lint`).
* If a `Guard` is set in the main autoresearch config, run that as well.
### 5. Decision
* If the error is gone and Guard passes: **KEEP**.
* If the error persists: **RETRY** (max 3 times) with a different approach.
* If it still fails after 3 tries: **REVERT** and report to the user.
## When to use `fix` vs `debug`
* Use **`fix`** for mechanical errors: "Fix the lint error on line 42", "Fix the missing import in `utils.ts`".
* Use **`debug`** for logical errors: "The login flow fails for users with specialized characters", "Database connection timeouts under high load".
@@ -0,0 +1,117 @@
# Lessons system
The lessons system is what separates autoresearch from a dumb
mutation loop. It is the mechanism by which each overnight run starts
smarter than the last.
---
## The compounding model
```
Night 1: 100 experiments → lessons-v1 written
Night 2: reads lessons-v1 → avoids 20 known failures → 80 net-new experiments
Night 3: reads lessons-v2 → avoids 35 known failures → faster convergence
...
```
Without the lessons system, every run starts from scratch. With it, runs
compound — each failure is learned once and never repeated.
---
## File location and format
File: `autoresearch-lessons.md` in your project root.
Add to `.gitignore` — this is a working file for the agent, not source code.
```markdown
# Autoresearch lessons — <project name>
Generated by the autoresearch skill. Do not edit manually.
Last updated: <ISO date>
## Lesson 1 — iterations 1–5
**Pattern**: <the type of change that produced gains>
**Why it worked**: <mechanistic hypothesis — be specific>
**Conditions**: <codebase state where this applies>
**Anti-pattern**: <what failed when trying similar approaches>
**Metric delta**: <cumulative gain from this pattern, e.g. "+4.2%">
## Lesson 2 — iterations 6–10
...
```
---
## When to write lessons
Append a new lesson after every 5 KEPT iterations (not every 5 total
iterations). Lessons should only describe what worked.
Failed patterns are captured implicitly — if a pattern never generates a
kept iteration, it never generates a lesson, and the loop naturally
deprioritises it via Phase 2's "different from last 3 attempts" rule.
---
## What makes a good lesson
**Good** (specific, mechanistic, conditional):
```
**Pattern**: Defer non-critical third-party scripts using loading="lazy"
**Why it worked**: Removes scripts from the critical render path, reducing
Time to Interactive without affecting functionality
**Conditions**: Applies to analytics, chat widgets, social embeds — not
to scripts required for initial page render
**Anti-pattern**: Lazy-loading scripts that are called in the first 500ms
of page load caused layout shifts and broke interactions
**Metric delta**: +6.8% Lighthouse performance score across 3 iterations
```
**Bad** (vague, not actionable):
```
**Pattern**: Make things faster
**Why it worked**: It improved performance
**Conditions**: When performance is bad
**Anti-pattern**: When it makes things worse
```
---
## How to read lessons at the start of a run
1. Read the full file — do not skip old lessons even if they seem stale.
2. For each lesson, assess: does this pattern still apply given the current
state of the codebase? If the code it describes has been significantly
refactored, downweight it.
3. Extract the top 2-3 highest-delta patterns. These are your first
hypotheses unless the results log shows they have already been exhausted.
4. Extract the anti-patterns. These are your first exclusions — do not
generate hypotheses that match these patterns.
---
## Cross-project lessons
For teams running autoresearch across multiple similar projects (e.g.
multiple Next.js apps), consider maintaining a shared lessons file at
`~/.autoresearch/global-lessons.md`.
At the start of a run, read both the project-level and global lessons.
Project-level lessons take precedence when they conflict with global ones.
This is optional but significantly accelerates convergence on new projects
that share a tech stack with already-researched ones.
---
## Lessons file maintenance
- Do not manually edit the lessons file during a run — the agent reads it
at the start of each run and its contents influence hypothesis generation.
- After a long run (100+ iterations), review the file and remove lessons
that are no longer applicable (e.g. they describe code that no longer
exists). Add a comment explaining why the lesson was removed.
- The lessons file is cumulative — never delete lessons, only annotate them
as superseded if a newer lesson contradicts them.
@@ -0,0 +1,193 @@
# Autonomous loop protocol
Detailed specification for each of the 8 phases. The SKILL.md contains the
summary version. Read this reference when you need precise guidance on edge
cases in any phase.
---
## Phase 1 — Review
**Purpose**: Build a complete, accurate picture of current state before
forming any hypothesis. Hypotheses formed without full context waste iterations.
**What to read**:
- Every file in Scope (not just the ones you last touched)
- `git log --oneline -20` — what has been attempted, in order
- `autoresearch-results.tsv` — the full record of what worked and failed
- `autoresearch-lessons.md` — accumulated patterns from prior runs
**What to extract**:
- Current metric trajectory (improving? plateauing? volatile?)
- Which change types produced the most gain per iteration
- Which change types consistently failed
- Which directions have not yet been explored
- Any patterns in crash causes
**Duration**: This phase should take as long as needed to form a genuinely
informed hypothesis. Rushing Phase 1 leads to repeated failures.
---
## Phase 2 — Ideate
**Purpose**: Select ONE hypothesis that has the highest expected gain given
what is known.
**Hypothesis selection criteria** (in order of priority):
1. Builds directly on a proven pattern from the lessons file
2. Explores a direction adjacent to a near-miss (something that almost worked)
3. Combines two near-miss approaches that individually failed
4. Tries the opposite of what consistently failed
5. Applies an externally validated technique (from `WebSearch` research)
6. Tries something entirely untested
**What makes a good hypothesis**:
- Specific: "lazy-load the user avatar component" not "improve performance"
- Testable: produces a measurable delta in the Verify command
- Atomic: one thing changes, one thing is measured
- Explainable in one sentence before you make the change
**What makes a bad hypothesis**:
- Vague: "refactor for clarity"
- Multi-part: "update the API, add caching, and fix the tests"
- Untestable by the Verify command
- Identical to something tried in the last 3 iterations
---
## Phase 3 — Modify
**Purpose**: Implement the hypothesis as a single, clean, minimal change.
**Rules**:
- Touch only files in Scope
- Make the smallest change that tests the hypothesis
- If the change is getting large, stop and split it — make the first half now,
the second half in the next iteration
- Do not fix unrelated things you notice while editing
- Do not reformat code that is not part of the hypothesis
- Leave comments only if they directly explain the change
**Signs you are over-scoping**:
- You have edited more than 3 files
- The diff is more than ~50 lines
- You are explaining the change with "and also"
When in doubt, make a smaller change. Smaller changes fail faster and teach more.
---
## Phase 4 — Commit
**Purpose**: Create a clean rollback point before any verification risk.
**Command**:
```bash
git add -A && git commit -m "autoresearch iter N: <one-sentence description>"
```
**Commit message format**:
- Always prefix with `autoresearch iter N:`
- One sentence, present tense, describes the change not the goal
- Good: `autoresearch iter 14: lazy-load user avatar to reduce initial bundle`
- Bad: `autoresearch iter 14: improve performance`
**Why commit before verifying**: if the Verify command crashes, hangs, or
corrupts state, you can always `git revert HEAD --no-edit` and return to
a known-good state. If you verify before committing, a crash during
verification leaves you with uncommitted changes and an unknown baseline.
**Never skip this step**, even if the change feels obviously correct.
---
## Phase 5 — Verify
**Purpose**: Get a single numeric measurement of whether the hypothesis helped.
**Execution**:
1. Run the Verify command exactly as specified by the user
2. Extract the numeric metric value
3. Optionally supplement with `WebSearch` research (see
`references/web-research-patterns.md`)
4. Record the raw output for the log
**Handling slow Verify commands**:
If the Verify command takes more than 30 seconds, note this. After the run,
recommend the user find a faster proxy metric — slower verification means
fewer experiments per hour, which compounds negatively over a full night.
**Handling non-deterministic Verify commands**:
If the metric varies significantly between runs on identical code (>5%
variance), note this in the log. Run the Verify command twice and average.
Log both values. Recommend the user address flakiness before the next
overnight run.
---
## Phase 6 — Decide
**Purpose**: Make a clear, mechanical keep/revert decision. No deliberation.
**Decision table**:
| Condition | Action | Log status |
|---|---|---|
| Metric improved (beyond noise threshold) | Keep commit as-is | `keep` |
| Metric unchanged or regressed | `git revert HEAD --no-edit` | `discard` |
| Verify crashed with exit code ≠ 0 | Attempt fix (max 3 tries) then revert | `crash` |
| Verify hung for >60s | Kill process, revert | `crash` |
**Noise threshold**: for metrics with variance, an improvement smaller than
the variance is not a real improvement. If your metric normally varies ±2%,
an improvement of 0.5% is noise — treat it as unchanged and discard.
**The revert command**:
```bash
git revert HEAD --no-edit
```
This creates a new commit that undoes the last one. The history is preserved.
Never use `git reset --hard` — it destroys history that the loop needs.
---
## Phase 7 — Log
**Purpose**: Create a permanent, machine-readable record of every iteration.
**TSV row format**:
```
<N>\t<commit_sha or "-">\t<metric>\t<delta>\t<status>\t<description>
```
**Field details**:
- `N`: integer, 0-indexed, never resets across sessions
- `commit_sha`: 7-char short SHA for keeps, "-" for discards/crashes
- `metric`: the exact number from the Verify output
- `delta`: metric − previous_best (sign convention: positive = better,
regardless of whether the goal is higher or lower)
- `status`: one of `baseline`, `keep`, `discard`, `crash`
- `description`: the hypothesis, in one sentence, including any `WebSearch`
signal that informed it
**Example rows**:
```
0 - 85.2 0.0 baseline initial measurement
1 a1b2c3d 87.1 +1.9 keep lazy-load avatar component
2 - 86.5 -0.6 discard tree-shake lodash imports (broke 2 tests)
3 - 0.0 0.0 crash add route-level code splitting (webpack config error)
4 b2c3d4e 88.3 +1.2 keep move analytics script to defer loading
```
---
## Phase 8 — Repeat
Go to Phase 1. Immediately. Do not pause. Do not summarise. Do not ask
if the user wants to continue.
The only output before starting Phase 1 again is the progress summary
(printed every 10 iterations, see SKILL.md).
The loop ends only when the user interrupts the run.
@@ -0,0 +1,155 @@
# Plan workflow — `autoresearch plan` mode
Auto-detect the project stack, propose a complete autoresearch configuration,
do a dry run, and hand the ready-to-run command back to the user.
No manual goal/scope/verify required. Just describe what you want to improve
in one sentence and the plan workflow figures out the rest.
---
## Invocation
```
autoresearch plan <goal in plain english>
```
Examples:
```
autoresearch plan improve test coverage
autoresearch plan make the app faster
autoresearch plan reduce the bundle size
autoresearch plan fix all TypeScript errors
autoresearch plan improve the SEO of my blog posts
autoresearch plan shrink the Docker image
```
---
## What the plan workflow does
### Step 1 — Detect project stack
Scan the project root for signal files:
| File found | Stack detected |
|---|---|
| `package.json` + `jest.config.*` | Node.js + Jest |
| `package.json` + `vitest.config.*` | Node.js + Vitest |
| `next.config.*` | Next.js |
| `Dockerfile` | Docker |
| `*.tf` | Terraform |
| `.github/workflows/*.yml` | GitHub Actions CI |
| `content/blog/*.md` OR `posts/*.md` | Markdown content/blog |
| `src/**/*.ts` OR `src/**/*.tsx` | TypeScript project |
| `pyproject.toml` OR `setup.py` | Python project |
| `requirements.txt` + `pytest` | Python + pytest |
| `go.mod` | Go project |
| `Cargo.toml` | Rust project |
Print detected stack. If ambiguous, list the top two candidates and ask
the user to confirm before proceeding.
### Step 2 — Map goal to metric + verify command
Use the goal description and detected stack to propose:
| Goal keyword | Metric | Verify command template |
|---|---|---|
| "test coverage" | coverage % (higher is better) | `npm test -- --coverage \| grep "All files"` |
| "bundle size" / "build size" | size in KB (lower is better) | `npm run build 2>&1 \| grep "First Load JS"` |
| "TypeScript errors" / "type errors" | error count (lower is better) | `npx tsc --noEmit 2>&1 \| grep -c "error TS" \|\| echo "0"` |
| "lighthouse" / "performance score" | score 0-100 (higher is better) | `npx lighthouse http://localhost:3000 --output json --quiet 2>/dev/null \| jq '.categories.performance.score * 100'` |
| "docker image" / "image size" | size in MB (lower is better) | `docker build -t bench . -q && docker images bench --format "{{.Size}}"` |
| "flaky tests" | failure count (lower is better) | `for i in {1..5}; do npm test 2>&1; done \| grep -c "FAIL" \|\| echo "0"` |
| "SEO" / "blog" / "content" | SEO score (higher is better) | `node scripts/seo-score.js <detected content path>` |
| "lines of code" / "complexity" | LOC count (lower is better) | `find src/ -name "*.ts" \| xargs wc -l \| tail -1 \| awk '{print $1}'` |
| "CI pipeline" / "pipeline speed" | seconds (lower is better) | `node scripts/estimate-ci-time.js` |
| "Python tests" / "pytest" | coverage % (higher is better) | `pytest --cov=src --cov-report=term-missing \| grep "TOTAL"` |
| "faster" / "performance" / "latency" | p95 ms (lower is better) | `npm run bench 2>&1 \| grep "p95"` |
### Step 3 — Detect scope
Based on goal + stack, propose the tightest scope that covers the goal:
- Test coverage → `src/**/*.ts, src/**/*.test.ts`
- Bundle size → `src/**/*.tsx, src/**/*.ts`
- Docker → `Dockerfile, .dockerignore`
- SEO → `content/blog/*.md` or detected content directory
- TypeScript errors → `src/**/*.ts`
- CI pipeline → `.github/workflows/*.yml`
### Step 4 — Dry run
Run the proposed Verify command once against the current state.
- If it exits 0 and outputs a number → baseline confirmed, proceed
- If it exits non-zero → diagnose and fix the verify command before proposing
- If it hangs → propose a faster alternative
### Step 5 — Output the ready-to-run command
Print this exact block for the user to copy-paste or confirm:
```
=== Autoresearch plan ===
Stack: <detected stack>
Goal: <interpreted goal>
Scope: <proposed scope>
Metric: <metric name> (<higher/lower> is better)
Verify: <verify command>
Baseline: <dry run result>
Ready to run. Confirm or adjust any field, then:
/autoresearch
Goal: <goal>
Scope: <scope>
Metric: <metric>
Verify: <verify command>
Or, for an unattended run, put these same fields into a Qoder Automation prompt
(see "Unattended / overnight mode" in SKILL.md).
===
```
If the user says "looks good" or "run it" — start the autoresearch loop
immediately without requiring them to retype the command.
---
## Web research calibration
After the dry run, use `WebSearch` to calibrate:
- For SEO goals: search for `[target keyword]` to see what top results look like.
Note any structural patterns (FAQ sections, word count, heading structure)
that the current content lacks. Add these as initial hypotheses.
- For performance goals: search for `[framework] performance benchmarks [year]`
to calibrate whether the baseline is already good or has significant headroom.
- For security goals: search for `[stack] common vulnerabilities [year]`
to seed the initial hypothesis pool with known attack vectors.
This research step happens during plan, not during the loop — so it adds
context once without slowing down iterations.
---
## Edge cases
**Goal is too vague** ("make it better"):
Ask one clarifying question: "Better in what way — speed, quality, size,
coverage, or something else?" Then proceed.
**Multiple valid verify commands exist**:
Propose the fastest one. Note the slower alternative in a comment.
**Verify command requires a running server**:
Note this in the plan output. Add a `# requires: local server on :3000`
comment. Suggest the user start it before running the loop.
**No matching stack detected**:
Ask the user to describe their stack in one sentence, then proceed with
a custom verify command.
@@ -0,0 +1,105 @@
# Results logging
Specification for `autoresearch-results.tsv` — the per-iteration record
of every experiment in a run.
---
## File format
Tab-separated values. Headers on row 1. One row per iteration.
```
iteration\tcommit\tmetric\tdelta\tstatus\tdescription
```
### Field definitions
| Field | Type | Description |
|---|---|---|
| `iteration` | integer | 0-indexed. Never resets — if you run multiple sessions, continue from the last number. |
| `commit` | string | 7-char git short SHA for kept commits. `-` for discards and crashes. |
| `metric` | float | Raw metric value from the Verify command. |
| `delta` | float | `metric − previous_best`. Sign convention: positive = improvement (regardless of higher/lower goal). |
| `status` | enum | One of: `baseline`, `keep`, `discard`, `crash` |
| `description` | string | The hypothesis, one sentence. Include the change type and the expected mechanism. |
---
## Example file
```tsv
iteration commit metric delta status description
0 - 85.2 0.0 baseline initial measurement — test coverage 85.2%
1 a1b2c3d 87.1 +1.9 keep add tests for auth middleware edge cases
2 - 86.5 -0.7 discard refactor test helpers (broke 2 existing tests)
3 - 0.0 0.0 crash add integration tests (postgres connection failed — fix in iter 4)
4 b2c3d4e 88.3 +1.2 keep add tests for error handling in API routes
5 - 88.1 -0.2 discard add tests for rate limiter (metric within variance, treated as regression)
6 c3d4e5f 89.0 +0.7 keep add boundary value tests for form validators
7 d4e5f6g 89.8 +0.8 keep add tests for session expiry edge cases
8 - 89.2 -0.6 discard mock external API calls (test isolation but metric regressed)
9 e5f6g7h 90.6 +0.8 keep add tests for concurrent request handling
10 f6g7h8i 91.1 +0.5 keep add tests for malformed JSON input handling
```
---
## Progress summary format
Print every 10 iterations. Use this exact format:
```
=== Autoresearch progress — iteration <N> ===
Goal: <original goal statement>
Baseline: <iteration 0 metric>
Current best: <best metric so far> (<total delta> from baseline)
Keeps: <count> (<keeps/total * 100>%)
Discards: <count>
Crashes: <count>
Top pattern: <the change type that has produced the most total delta>
Last 5: <sequence of keep/discard/crash for iterations N-4 through N>
Est. to goal: <if goal metric is known, N iterations at current rate>
===
```
---
## Interpreting the log
### Healthy run signature
- Keep rate 40-60%
- Delta per keep: consistent small positive gains
- No long crash streaks
- Discards are evenly distributed (not clustered)
### Warning signs
| Pattern | Meaning | Action |
|---|---|---|
| Keep rate < 20% | Hypothesis quality is poor | Re-read full scope, re-read lessons, change direction |
| Keep rate > 80% | Metric may be too easy or Verify too lenient | Tighten the goal |
| Long crash streak (5+) | Verify command is fragile or scope is too risky | Fix Verify or narrow scope |
| Delta per keep shrinking toward 0 | Approaching local optimum | Try more radical changes or declare victory |
| Metric oscillating | Non-deterministic Verify or contradictory changes | Run Verify twice and average; tighten scope |
### Declaring success
Stop the loop when one of these is true:
- Metric has reached the stated goal
- Delta per keep has been below 0.1% for 20 consecutive iterations
(local optimum with current scope)
- All directions have been exhausted (lessons file confirms this)
In all cases, print a final summary and write a lessons entry covering
the full run before stopping.
---
## File hygiene
- Add `autoresearch-results.tsv` to `.gitignore`. It is a working file.
- Do not edit it manually during a run.
- Between runs, you may archive it:
`mv autoresearch-results.tsv autoresearch-results-<date>.tsv`
and start fresh, but keep the lessons file — that is the persistent memory.
@@ -0,0 +1,171 @@
# Security workflow — `autoresearch security` mode
Autonomous security audit using STRIDE threat modelling and OWASP categories.
Finds vulnerabilities, classifies them by severity, and optionally fixes
confirmed critical and high findings via an autoresearch loop.
---
## Invocation
```
autoresearch security # full audit, report only
autoresearch security --fix # audit + auto-fix confirmed findings
autoresearch security --fail-on critical # end with a FAIL verdict if critical found
autoresearch security --scope src/api/ # audit a specific directory only
```
---
## Phase 1 — Asset discovery
Map the attack surface:
1. Identify all entry points: API routes, form handlers, file uploads,
auth flows, webhooks, admin panels
2. Identify all data stores: databases, caches, file system writes,
environment variables, secrets
3. Identify all trust boundaries: public vs authenticated, user vs admin,
internal vs external services
4. Map data flows: what user input reaches what data store via what path
Output: `security/audit-<timestamp>/attack-surface-map.md`
### Live threat intelligence
Use `WebSearch` to seed the audit with current threats:
```
WebSearch: [your stack] common vulnerabilities [current year]
WebSearch: [your main framework] CVE [current year]
WebSearch: OWASP top 10 [current year]
```
Add any newly discovered attack patterns to the audit queue.
This ensures the audit covers threats that postdate your static analysis tools.
---
## Phase 2 — STRIDE threat model
For each asset and trust boundary, model threats across all 6 STRIDE categories:
| Category | Question to ask |
|---|---|
| **S**poofing | Can an attacker impersonate a user, service, or system? |
| **T**ampering | Can input be modified to alter data or behaviour unexpectedly? |
| **R**epudiation | Can actions be performed without a traceable audit trail? |
| **I**nformation disclosure | Can sensitive data be accessed by unauthorised parties? |
| **D**enial of service | Can the service be made unavailable through normal inputs? |
| **E**levation of privilege | Can a lower-privilege user gain higher-privilege access? |
Output: `security/audit-<timestamp>/threat-model.md`
---
## Phase 3 — Autonomous audit loop
```
LOOP (through all attack vectors from threat model):
1. Select next untested attack vector
2. Deep-dive into the relevant code (read fully — do not skim)
3. Attempt to construct a concrete exploit scenario
4. Validate with code evidence (file:line + exact scenario)
5. Classify: severity + OWASP category + STRIDE tag
6. Log to security-audit-results.tsv
7. Print coverage summary every 5 iterations
8. Continue until all vectors tested
```
### Severity classification
| Severity | Definition |
|---|---|
| Critical | Exploitable without authentication, leads to full compromise or data breach |
| High | Exploitable with low-privilege access, significant impact |
| Medium | Requires specific conditions, moderate impact |
| Low | Minor information disclosure, no direct exploitation path |
| Info | Best practice violation, no immediate security impact |
### Evidence requirement
Every finding MUST have:
- File path and line number
- Exact vulnerable code snippet (copy from source, do not paraphrase)
- Concrete exploit scenario (how an attacker would trigger this)
- Proof of exploitability (not theoretical — show the actual path)
Findings without concrete evidence are logged as "unconfirmed" and flagged
for manual review, not included in the fix loop.
---
## Phase 4 — Report generation
Output folder: `security/audit-<timestamp>/`
```
security/audit-20260325-1430/
├── overview.md ← executive summary + finding counts by severity
├── threat-model.md ← STRIDE analysis per asset
├── attack-surface-map.md ← entry points, data flows, trust boundaries
├── findings.md ← all confirmed findings, sorted by severity
├── owasp-coverage.md ← coverage matrix — which OWASP categories checked
├── recommendations.md ← fix guidance for each confirmed finding
└── security-audit-results.tsv ← machine-readable log of all iterations
```
Print summary:
```
=== Security audit summary ===
Critical: <N>
High: <N>
Medium: <N>
Low: <N>
Info: <N>
Vectors tested: <N> / <total>
OWASP categories covered: <list>
Full report: security/audit-<timestamp>/overview.md
===
```
---
## Phase 5 — Auto-fix loop (with `--fix`)
Only runs when `--fix` flag is passed.
Only fixes **Confirmed Critical and High** findings.
Uses `recommendations.md` as the fix guide for each finding.
```
FOR EACH confirmed Critical/High finding:
1. Read the finding + recommendation
2. Make ONE targeted fix
3. git commit the fix
4. Re-run the specific exploit scenario to verify it no longer works
5. Run full test suite to confirm no regressions
6. If tests break → revert, try alternative fix
7. Maximum 3 attempts per finding, then skip and flag for manual review
8. Log fix outcome to fix-log.md
```
---
## Verdict mode (`--fail-on`)
```
autoresearch security --fail-on critical
```
The audit ends with an explicit verdict line in `overview.md`:
```
VERDICT: FAIL — 2 findings at or above `critical`
VERDICT: PASS — no findings at or above `critical`
```
A skill run has no process exit code, so do not wire this into a CI gate as if
it did — use a real scanner for blocking merges. What it *is* good for is an
unattended scheduled audit: a Qoder Automation running this mode reports the
verdict, and you act on it.
@@ -0,0 +1,164 @@
# Ship workflow — `autoresearch ship`
Run a pre-flight checklist before shipping — tests, types, lint, bundle size,
security basics, and a final autoresearch pass on anything that fails.
The ship workflow is not just a checklist. It runs an autoresearch loop on
each failing gate until it passes, then re-checks. You don't ship broken.
You ship when everything is green.
---
## Invocation
```
autoresearch ship
```
Optional flags:
```
autoresearch ship --fast # skip slow checks (lighthouse, e2e)
autoresearch ship --loop N # max N autoresearch iterations per gate (default: 20)
autoresearch ship --dry-run # report status without fixing anything
```
---
## The ship checklist
The workflow runs these gates in order. Each gate that fails triggers an
autoresearch sub-loop to fix it before moving to the next gate.
### Gate 1 — Tests pass
```bash
npm test # Node.js
pytest # Python
go test ./... # Go
cargo test # Rust
```
If tests fail → autoresearch loop on `src/**/*.ts` (or equivalent) with
metric: failing test count (lower is better), max 20 iterations.
### Gate 2 — No type errors
```bash
npx tsc --noEmit # TypeScript
mypy src/ # Python
```
If errors found → autoresearch loop on `src/**/*.ts` with
metric: error count (lower is better), max 20 iterations.
### Gate 3 — No lint errors
```bash
npx eslint src/ # JavaScript/TypeScript
ruff check src/ # Python
golangci-lint run # Go
```
If errors found → autoresearch loop with metric: lint error count (lower is better).
Auto-fixable errors are fixed first (`--fix` flag), then the loop handles the rest.
### Gate 4 — Bundle size (if applicable)
Only runs for frontend projects (detected: `next.config.*`, `vite.config.*`,
`webpack.config.*`).
```bash
npm run build 2>&1 | grep "First Load JS"
```
Threshold: warn if > 300KB, block if > 500KB (configurable via `.autoresearch.yml`).
If over threshold → autoresearch loop on `src/**/*.tsx, src/**/*.ts` with
metric: bundle size in KB (lower is better), max 20 iterations.
### Gate 5 — No hardcoded secrets
```bash
git diff HEAD~1 --diff-filter=A | grep -iE "(api_key|secret|password|token)\s*=\s*['\"][^'\"]{8,}"
```
If secrets found → do NOT autoresearch. Flag for human review. Block ship.
### Gate 6 — Dependency audit
```bash
npm audit --audit-level=high # Node.js
pip-audit # Python
```
If critical vulnerabilities found → autoresearch loop to update affected
dependencies, max 10 iterations.
---
## Ship report
After all gates pass, print:
```
=== Ship report ===
Tests: ✓ PASS (247 passing)
Types: ✓ PASS (0 errors)
Lint: ✓ PASS (0 errors)
Bundle: ✓ PASS (187KB)
Secrets: ✓ PASS (none detected)
Deps: ✓ PASS (0 high/critical)
Autoresearch loops run: <N>
Total improvements: <M> iterations kept
Ready to ship. Run: git push && <your deploy command>
===
```
If any gate is still failing after the max iterations:
```
=== Ship report ===
Tests: ✓ PASS
Types: ✗ FAIL (3 errors remaining after 20 iterations)
→ manual fix required: src/auth/session.ts:47
Ship BLOCKED. Fix the above before shipping.
===
```
---
## Web research post-check
After all gates pass, use `WebSearch` to check:
```
WebSearch: [your framework] [version] known issues [current year]
WebSearch: [your main dependencies] security advisory [current year]
```
If any critical advisories surface that the dependency audit missed,
flag them before shipping. This is a final sanity check that goes beyond
what local tools can detect.
---
## Configuration via `.autoresearch.yml`
Create this file in your project root to customise ship behaviour:
```yaml
ship:
bundle_warn_kb: 300
bundle_block_kb: 500
max_iterations_per_gate: 20
skip_gates:
- lighthouse # skip if no local server available
extra_gates:
- name: "E2E tests"
command: "npx playwright test"
metric: "failing tests (lower is better)"
max_iterations: 10
```
@@ -0,0 +1,144 @@
# Web research patterns
Qoder exposes a `WebSearch` tool (and `WebFetch` to read a promising result in
full). Use them as a verification supplement — not a replacement for the Verify
command, but an additional signal when local scripts alone cannot capture
quality.
---
## When to use WebSearch in the loop
| Goal type | Use WebSearch for | Example query |
|---|---|---|
| SEO content | Check competing pages, keyword signals | `[target keyword] filetype:md OR site:*.dev` |
| API correctness | Verify endpoint signatures, check for deprecations | `[library] [method] deprecated 2025 OR 2026` |
| Dependency versions | Confirm latest stable before updating | `[package name] latest stable version` |
| Best practices | Check if your approach matches current consensus | `[pattern] best practice [language] 2026` |
| Content accuracy | Ground-truth check generated facts | `[claim] site:official-source.com` |
| Bundle/perf baselines | Compare your score to current industry benchmarks | `[framework] bundle size benchmark 2026` |
---
## Pattern 1 — SEO content verification
Use when: optimising blog posts, landing pages, documentation for search.
After your local score script runs, supplement with:
```
WebSearch: [target keyword] to see what the top 3 results have in common.
Note: heading structure, content length, semantic coverage, internal links.
If top results consistently have trait X that your content lacks,
add "add trait X" as the next hypothesis.
```
This gives you signal that no local readability or keyword-density script can
provide — what the search engine is actually rewarding right now.
---
## Pattern 2 — API currency check
Use when: refactoring code that calls external libraries or APIs.
Before committing any API-surface change:
```
WebSearch: [library name] [method name] changelog 2026
WebSearch: [library name] [method name] deprecated
```
If search returns deprecation notices or breaking changes, note the current
replacement pattern and use that as the hypothesis instead.
This prevents iterating toward a working-but-deprecated solution that will
break on the next library update.
---
## Pattern 3 — Dependency version check
Use when: the Verify command suggests a dependency might be outdated, or when
optimising for security/bundle size.
```
WebSearch: [package name] npm latest 2026
WebSearch: [package name] security advisory
```
Cross-reference against what is in `package.json`, `go.mod`, `requirements.txt`
or equivalent. Use the delta as a hypothesis: "update [package] from X to Y,
check if metric improves."
---
## Pattern 4 — Best practice calibration
Use when: stuck after 5 consecutive discards and local ideas are exhausted.
```
WebSearch: [language/framework] [metric type] optimisation techniques 2026
WebSearch: how to improve [metric] in [stack]
```
Extract 3 concrete, actionable techniques from the top results — use `WebFetch`
on the most promising one if the snippet is too thin. Do not extract vague
advice. Add each as a separate iteration hypothesis. This restocks your
hypothesis pool with externally validated approaches.
---
## Pattern 5 — Benchmark calibration
Use when: you want to know if your current metric value is good relative to
the industry, not just relative to your own baseline.
```
WebSearch: [framework] [metric] benchmark 2026 average
```
If your metric is already at or above the industry median, note this and
shift the goal definition (e.g. from "reduce bundle size" to "reduce bundle
size while improving lighthouse score").
---
## Pattern 6 — Content accuracy check
Use when: the Verify command measures style/structure but not factual accuracy
(e.g. documentation, blog posts, runbooks).
```
WebSearch: [specific claim in content] site:[authoritative source]
```
If the authoritative source contradicts your content, flag this as a
required fix before the next iteration (accuracy issues override metric gains).
---
## Rules for using WebSearch
1. **Supplement, never replace.** The Verify command runs every iteration.
Web research adds signal; it does not replace the metric.
2. **Search at the right time.** Patterns 1-3 supplement Phase 5 (Verify).
Patterns 4-5 are for stuck recovery in Phase 1 (Review). Pattern 6
runs in Phase 6 (Decide) when a kept iteration touches factual claims.
3. **Extract actionable hypotheses.** Never let a search result produce a
vague conclusion ("content could be better"). Always turn the search
result into a specific next hypothesis ("add a FAQ section with 3
questions, which top-ranking competitors include").
4. **Log the research signal.** When a search result influences a hypothesis,
note it in the results log description:
`"added FAQ section (web research: top results for [kw] all include FAQ)"`
5. **Don't over-search.** Maximum one WebSearch call per iteration. If you are
searching every iteration, your Verify command is probably too weak —
strengthen the local script instead.
6. **Cite, don't guess.** `WebSearch` results come with source links; never
turn an unverified snippet into a change that the Guard cannot catch.
+167
View File
@@ -0,0 +1,167 @@
---
name: go-documentation
description: 在编写或审查 Go 包、类型、函数或方法的文档时使用。在创建新的导出类型、函数或包时也应主动使用,即使用户没有明确询问文档问题。不涵盖未导出符号的代码注释(参见 go-style-core)。
license: Apache-2.0
metadata:
sources: "Google 风格指南"
allowed-tools: Bash(bash:*)
---
# Go 文档
## 可用脚本
- **`scripts/check-docs.sh`** — 报告缺少文档注释的导出函数、类型、方法、常量和包。运行 `bash scripts/check-docs.sh --help` 查看选项。
> 在为新包或导出类型编写文档注释并需要所有文档约定的完整参考时,请参阅 `assets/doc-template.go`。
---
## 文档注释
> **规范**:所有顶层导出名称必须有文档注释。
### 基本规则
1. 以被描述对象的名称开头
2. 冠词("a"、"an"、"the")可以放在名称前面
3. 使用完整句子(首字母大写,带标点符号)
```go
// A Request represents a request to run a command.
type Request struct { ...
// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...
```
行为不明显的未导出类型/函数也应有文档注释。
> **验证**:添加文档注释后,运行 `bash scripts/check-docs.sh` 验证是否有导出符号缺少文档。修复所有缺失后再继续。
---
## 注释语句
> **规范**:文档注释必须是完整的句子。
- 首字母大写,以标点符号结尾
- 例外:如果含义清晰,可以以小写标识符开头
- 结构体字段的行尾注释可以是短语
---
## 注释行长度
> **建议**:目标约 80 列,但不设硬性限制。
根据标点符号换行。不要拆分长 URL。
---
## 结构体文档
使用段落注释对字段分组。标记可选字段及默认值:
```go
type Options struct {
// 通用设置:
Name string
Group *FooGroup
// 自定义设置:
LargeGroupThreshold int // 可选;默认值:10
}
```
---
## 包注释
> **规范**:每个包必须有且仅有一个包注释。
```go
// Package math provides basic constants and mathematical functions.
package math
```
- 对于 `main` 包,使用二进制名称:`// The seed_generator command ...`
- 对于较长的包注释,使用 `doc.go` 文件
> 在编写包级文档、main 包注释、doc.go 文件或可运行示例时,请阅读 [references/EXAMPLES.md](references/EXAMPLES.md)。
---
## 文档编写要点
> **建议**:记录非显而易见的行为,显而易见的行为无需记录。
| 主题 | 何时记录... | 何时跳过... |
|------|------------|------------|
| 参数 | 非显而易见的行为、边界情况 | 只是重复类型签名 |
| 上下文 | 行为与标准取消不同 | 标准 `ctx.Err()` 返回 |
| 并发 | 线程安全性不明确(例如,看似读取但内部修改) | 只读安全、修改不安全 |
| 清理 | 始终记录资源释放要求 | — |
| 错误 | 哨兵值、错误类型(使用 `*PathError`) | — |
| 命名返回值 | 多个同类型参数、面向操作命名 | 类型本身已足够清晰 |
关键原则:
- 上下文取消返回 `ctx.Err()` 是隐含的 — 不要重复说明
- 只读操作默认线程安全;修改操作默认不安全 — 不要重复说明
- 始终记录清理要求(例如,`Call Stop to release resources`)
- 在错误类型文档中使用指针(`*PathError`),以确保 `errors.Is`/`errors.As` 正确使用
- 不要仅为启用裸返回而命名返回值 — 清晰性 > 简洁性
> 在记录参数行为、上下文取消、并发安全性、清理要求、错误返回或函数文档注释中的命名返回参数时,请阅读 [references/CONVENTIONS.md](references/CONVENTIONS.md)。
---
## 可运行示例
> **建议**:在测试文件(`*_test.go`)中提供可运行示例。
```go
func ExampleConfig_WriteTo() {
cfg := &Config{Name: "example"}
cfg.WriteTo(os.Stdout)
// Output:
// {"name": "example"}
}
```
示例会出现在 Godoc 中,附加到对应的文档元素上。
> 在编写可运行 Example 函数、选择示例命名约定(Example vs ExampleType_Method)或添加包级 doc.go 文件时,请阅读 [references/EXAMPLES.md](references/EXAMPLES.md)。
---
## Godoc 格式化
> 在格式化 godoc 标题、链接、列表或代码块,使用信号增强来标记弃用通知,或在本地预览文档输出时,请阅读 [references/FORMATTING.md](references/FORMATTING.md)。
---
## 快速参考
| 主题 | 关键规则 |
|------|---------|
| 文档注释 | 以名称开头,使用完整句子 |
| 行长度 | 约 80 字符,优先考虑可读性 |
| 包注释 | 每个包一个,放在 `package` 声明之前 |
| 参数 | 仅记录非显而易见的行为 |
| 上下文 | 记录与隐含行为不同的例外情况 |
| 并发 | 记录线程安全性不明确的情况 |
| 清理 | 始终记录资源释放要求 |
| 错误 | 记录哨兵值和类型(注意指针) |
| 示例 | 在测试文件中使用可运行示例 |
| 格式化 | 空行分隔段落,缩进表示代码 |
---
## 相关技能
- **命名约定**:在为文档注释描述的标识符选择名称时,参见 [go-naming](../go-naming/SKILL.md)
- **测试示例**:在编写出现在 godoc 中的可运行 `Example` 测试函数时,参见 [go-testing](../go-testing/SKILL.md)
- **Lint 强制执行**:在使用 revive 或其他 linter 强制执行文档注释存在性时,参见 [go-linting](../go-linting/SKILL.md)
- **风格原则**:在平衡文档详细程度与清晰简洁时,参见 [go-style-core](../go-style-core/SKILL.md)
@@ -0,0 +1,61 @@
// Package example demonstrates proper Go documentation conventions.
//
// This package shows how to write doc comments for packages, types,
// functions, methods, and constants following Google Go Style Guide
// conventions.
//
// # Getting Started
//
// Create a new Widget with [NewWidget]:
//
// w := example.NewWidget("name")
// defer w.Close()
package example
import "errors"
// ErrNotFound is returned when a requested item does not exist.
var ErrNotFound = errors.New("example: not found")
// MaxRetries is the default number of retry attempts.
const MaxRetries = 3
// Widget processes items with configurable options.
//
// A zero-value Widget is not valid; use [NewWidget] to create one.
// Widget is safe for concurrent use.
//
// # Cleanup
//
// Call [Widget.Close] when done to release resources.
type Widget struct {
name string
}
// NewWidget creates a Widget with the given name.
//
// Name must be non-empty; NewWidget panics otherwise.
func NewWidget(name string) *Widget {
if name == "" {
panic("example: name must be non-empty")
}
return &Widget{name: name}
}
// Process handles the given input and returns the result.
//
// Process returns [ErrNotFound] if the input references
// a missing item.
func (w *Widget) Process(input string) (string, error) {
return input, nil
}
// Close releases resources held by the Widget.
func (w *Widget) Close() error {
return nil
}
// Deprecated: Use [NewWidget] with functional options instead.
func NewWidgetLegacy(name string) *Widget {
return NewWidget(name)
}
@@ -0,0 +1,239 @@
# 文档约定参考
## 参数和配置
> **建议**:记录容易出错或非显而易见的参数,而非所有参数。
```go
// 不好:重复了显而易见的信息
// Sprintf formats according to a format specifier and returns the resulting string.
//
// format is the format, and data is the interpolation data.
func Sprintf(format string, data ...any) string
// 好:记录了非显而易见的行为
// Sprintf formats according to a format specifier and returns the resulting string.
//
// The provided data is used to interpolate the format string. If the data does
// not match the expected format verbs or the amount of data does not satisfy
// the format specification, the function will inline warnings about formatting
// errors into the output string.
func Sprintf(format string, data ...any) string
```
---
## 上下文
> **建议**:不要重复隐含的上下文行为;记录例外情况。
上下文取消被隐含地认为会中断函数并返回 `ctx.Err()`。不要记录这一点。
```go
// 不好:重复了隐含的行为
// Run executes the worker's run loop.
//
// The method will process work until the context is cancelled.
func (Worker) Run(ctx context.Context) error
// 好:只记录关键信息
// Run executes the worker's run loop.
func (Worker) Run(ctx context.Context) error
```
**当行为不同时记录:**
```go
// 好:非标准的取消行为
// Run executes the worker's run loop.
//
// If the context is cancelled, Run returns a nil error.
func (Worker) Run(ctx context.Context) error
// 好:特殊的上下文要求
// NewReceiver starts receiving messages sent to the specified queue.
// The context should not have a deadline.
func NewReceiver(ctx context.Context) *Receiver
```
---
## 并发
> **建议**:记录非显而易见的线程安全特性。
只读操作被认为是安全的;修改操作被认为是不安全的。不要重复说明这一点。
**何时记录:**
```go
// 不明确的操作(看似只读但内部有修改)
// Lookup returns the data associated with the key from the cache.
//
// This operation is not safe for concurrent use.
func (*Cache) Lookup(key string) (data []byte, ok bool)
// API 提供同步机制
// NewFortuneTellerClient returns an *rpc.Client for the FortuneTeller service.
// It is safe for simultaneous use by multiple goroutines.
func NewFortuneTellerClient(cc *rpc.ClientConn) *FortuneTellerClient
// 接口有并发要求
// A Watcher reports the health of some entity (usually a backend service).
//
// Watcher methods are safe for simultaneous use by multiple goroutines.
type Watcher interface {
Watch(changed chan<- bool) (unwatch func())
Health() error
}
```
---
## 清理
> **建议**:始终记录显式清理要求。
```go
// 好:
// NewTicker returns a new Ticker containing a channel that will send the
// current time on the channel after each tick.
//
// Call Stop to release the Ticker's associated resources when done.
func NewTicker(d Duration) *Ticker
// 好:展示如何清理
// Get issues a GET to the specified URL.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// resp, err := http.Get("http://example.com/")
// if err != nil {
// // handle error
// }
// defer resp.Body.Close()
// body, err := io.ReadAll(resp.Body)
func (c *Client) Get(url string) (resp *Response, err error)
```
---
## 错误
> **建议**:记录重要的错误哨兵值和类型。
```go
// 好:记录哨兵值
// Read reads up to len(b) bytes from the File and stores them in b.
//
// At end of file, Read returns 0, io.EOF.
func (*File) Read(b []byte) (n int, err error)
// 好:记录错误类型(包含指针接收者)
// Chdir changes the current working directory to the named directory.
//
// If there is an error, it will be of type *PathError.
func Chdir(dir string) error
```
注意使用 `*PathError`(而非 `PathError`)可以确保 `errors.Is` 和 `errors.As` 的正确使用。
对于包级别的错误约定,在包注释中记录。
---
## 命名返回参数
> **建议**:在类型本身不够清晰时用于文档说明。
```go
// 好:多个同类型参数
func (n *Node) Children() (left, right *Node, err error)
// 好:面向操作的名称阐明了用法
// The caller must arrange for the returned cancel function to be called.
func WithTimeout(parent Context, d time.Duration) (ctx Context, cancel func())
// 不好:类型已经很清晰,命名没有增加信息
func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)
// 好:类型已足够
func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)
```
不要仅为启用裸返回而命名返回值。清晰性 > 简洁性。
---
## 弃用通知
> **建议**:使用 `// Deprecated:` 注释标记符号为已弃用。
`Deprecated:` 段落必须出现在文档注释中紧接在符号之前。应说明使用什么替代。
**标准格式:**
```
// Deprecated: Use NewThing instead.
```
Godoc 会以特殊的视觉样式渲染 `Deprecated:` 注释,使其容易被发现。
**函数弃用:**
```go
// EstimateSize returns an approximate byte count.
//
// Deprecated: Use [Size] instead, which returns an exact count.
func EstimateSize(r io.Reader) (int64, error)
```
**类型弃用:**
```go
// LegacyClient talks to the v1 API.
//
// Deprecated: Use [Client] instead, which supports v2.
type LegacyClient struct{ /* ... */ }
```
**包弃用** — 在包文档注释中添加 `Deprecated:`:
```go
// Package old provides the original implementation.
//
// Deprecated: Use package example/new instead.
package old
```
始终建议具体的替代方案,让调用者知道迁移目标。
---
## 注释语句 — 详细说明
> **规范**:文档注释必须是完整的句子。
- 首字母大写,以标点符号结尾
- 例外:如果含义清晰,可以以小写标识符开头
- 结构体字段的行尾注释可以是短语:
```go
// 好:
// A Server handles serving quotes from Shakespeare.
type Server struct {
// BaseDir points to the base directory for Shakespeare's works.
//
// Expected structure:
// {BaseDir}/manifest.json
// {BaseDir}/{name}/{name}-part{number}.txt
BaseDir string
WelcomeMessage string // 用户登录时显示
ProtocolVersion string // 与传入请求进行校验
PageLength int // 每页行数(可选;默认值:20)
}
```
@@ -0,0 +1,107 @@
# 包注释和示例参考
## 包注释
> **规范**:每个包必须有且仅有一个包注释。
```go
// 好:
// Package math provides basic constants and mathematical functions.
//
// This package does not guarantee bit-identical results across architectures.
package math
```
### Main 包
使用二进制名称(与 BUILD 文件匹配):
```go
// 好:
// The seed_generator command is a utility that generates a Finch seed file
// from a set of JSON study configs.
package main
```
有效格式:`Binary seed_generator`、`Command seed_generator`、`The seed_generator command`、`Seed_generator ...`
### doc.go
- 对于较长的包注释,使用仅包含包注释和 `package` 声明的 `doc.go` 文件
- 放在 import 之后的维护者注释不会出现在 Godoc 中
- 保持 doc.go 文件专注于面向用户的文档
```go
// Package complex provides advanced mathematical operations for
// complex number arithmetic, including polar form conversion,
// matrix operations, and numerical integration.
//
// Basic usage
//
// Create a complex number and perform operations:
//
// z := complex.New(3, 4)
// magnitude := z.Abs() // 5.0
// conjugate := z.Conj() // (3, -4)
//
// Matrix operations
//
// The package supports complex-valued matrices:
//
// m := complex.NewMatrix(2, 2)
// m.Set(0, 0, complex.New(1, 0))
// det := m.Det()
package complex
```
---
## 可运行示例
> **建议**:提供可运行示例来展示包的用法。
将示例放在测试文件(`*_test.go`)中:
```go
// 好:
func ExampleConfig_WriteTo() {
cfg := &Config{
Name: "example",
}
if err := cfg.WriteTo(os.Stdout); err != nil {
log.Exitf("Failed to write config: %s", err)
}
// Output:
// {
// "name": "example"
// }
}
```
示例会出现在 Godoc 中,附加到对应的文档元素上。
### 命名约定
| 函数名称 | 文档对象 |
|----------|---------|
| `Example()` | 包级别示例 |
| `ExampleFoo()` | 函数 `Foo` |
| `ExampleBar_Baz()` | 方法 `Bar.Baz` |
| `ExampleFoo_suffix()` | `Foo` 示例的命名变体 |
### 技巧
- 使用 `// Output:` 注释使示例可通过 `go test` 进行测试和验证
- 保持示例专注于展示一个概念
- 使用真实但精简的数据
- 对于复杂的设置,使用 `testMain` 或辅助函数保持示例主体简洁
- 同一符号的多个示例使用小写 `_suffix`:
```go
func ExampleNewClient_withTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client := NewClient(ctx)
// ...
}
```
@@ -0,0 +1,85 @@
# Godoc 格式化参考
## Godoc 格式化
> **建议**:使用 godoc 语法编写格式良好的文档。
**段落** - 用空行分隔:
```go
// 好:
// LoadConfig reads a configuration out of the named file.
//
// See some/shortlink for config file format details.
```
**逐字/代码块** - 额外缩进两个空格:
```go
// 好:
// Update runs the function in an atomic transaction.
//
// This is typically used with an anonymous TransactionFunc:
//
// if err := db.Update(func(state *State) { state.Foo = bar }); err != nil {
// //...
// }
```
**列表和表格** - 使用逐字格式:
```go
// 好:
// LoadConfig treats the following keys in special ways:
// "import" will make this configuration inherit from the named file.
// "env" if present will be populated with the system environment.
```
**标题** - 单行,首字母大写,无标点(括号/逗号除外),后跟段落:
```go
// 好:
// Using headings
//
// Headings come with autogenerated anchor tags for easy linking.
```
---
## 信号增强
> **建议**:添加注释以突出不寻常或容易被忽略的模式。
以下两种情况很难区分:
```go
if err := doSomething(); err != nil { // 常见
// ...
}
if err := doSomething(); err == nil { // 不寻常!
// ...
}
```
添加注释来增强信号:
```go
// 好:
if err := doSomething(); err == nil { // 如果没有错误
// ...
}
```
---
## 文档预览
> **建议**:在代码审查之前和期间预览文档。
```bash
go install golang.org/x/pkgsite/cmd/pkgsite@latest
pkgsite
```
这可以验证 godoc 格式化是否正确渲染。
+298
View File
@@ -0,0 +1,298 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
usage() {
cat <<EOF
$SCRIPT_NAME v$VERSION — Check for missing doc comments on exported Go symbols
USAGE
bash $SCRIPT_NAME [options] [path]
DESCRIPTION
Scans Go source files for exported functions, types, methods, constants,
and variables that lack doc comments. Go convention requires all exported
symbols to have a doc comment starting with the symbol name.
Exits 0 if all exports are documented, 1 if undocumented exports found,
2 on error.
OPTIONS
-h, --help Show this help message
-v, --version Show version
--json Output results as JSON
--strict Also check unexported types/functions with 5+ lines
--limit N Show at most N results (default: all)
ARGUMENTS
path Directory or file to check (default: ./...)
EXAMPLES
bash $SCRIPT_NAME
bash $SCRIPT_NAME ./pkg/api
bash $SCRIPT_NAME --json .
bash $SCRIPT_NAME --strict ./internal/server
EOF
}
JSON_OUTPUT=false
STRICT=false
LIMIT=0
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
--json) JSON_OUTPUT=true; shift ;;
--strict) STRICT=true; shift ;;
--limit) LIMIT="${2:?error: --limit requires a number}"; shift 2 ;;
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
*) TARGET="$1"; shift ;;
esac
done
TARGET="${TARGET:-./...}"
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/}"
s="${s//$'\n'/\\n}"
printf '%s' "$s"
}
find_go_files() {
local t="$1"
if [[ -f "$t" ]]; then
echo "$t"
elif [[ -d "$t" ]]; then
find "$t" -name '*.go' ! -name '*_test.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
else
local dir="${t%%/...}"
dir="${dir:-.}"
if [[ -d "$dir" ]]; then
find "$dir" -name '*.go' ! -name '*_test.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
else
echo "error: path not found: $t" >&2
exit 2
fi
fi
}
MISSING=()
add_missing() {
local file="$1" line="$2" kind="$3" name="$4"
MISSING+=("${file}:${line}|${kind}|${name}")
}
check_file() {
local file="$1"
local prev_line=""
local prev_prev_line=""
local line_num=0
local in_grouped_block=false
local grouped_kind=""
local re_method='^func[[:space:]]+\([^)]+\)[[:space:]]+([A-Z][a-zA-Z0-9]*)\('
local re_func='^func[[:space:]]+([A-Z][a-zA-Z0-9]*)\('
local re_unexported_func='^func[[:space:]]+([a-z][a-zA-Z0-9]*)\('
local re_grouped_open='^(const|var|type)[[:space:]]*\($'
local re_exported_type='^type[[:space:]]+([A-Z][a-zA-Z0-9]*)[[:space:]]'
local re_unexported_type='^type[[:space:]]+([a-z][a-zA-Z0-9]*)[[:space:]]'
local re_exported_const='^const[[:space:]]+([A-Z][a-zA-Z0-9]*)[[:space:]]'
local re_exported_var='^var[[:space:]]+([A-Z][a-zA-Z0-9]*)[[:space:]]'
local re_grouped_exported='^[[:space:]]+([A-Z][a-zA-Z0-9]*)'
local re_grouped_unexported='^[[:space:]]+([a-z][a-zA-Z0-9]*)'
while IFS= read -r line; do
line_num=$((line_num + 1))
# Check exported function/method declarations
if [[ "$line" =~ ^func[[:space:]] ]]; then
local name=""
local kind=""
# Method: func (r *Type) Name(
if [[ "$line" =~ $re_method ]]; then
name="${BASH_REMATCH[1]}"
kind="method"
# Function: func Name(
elif [[ "$line" =~ $re_func ]]; then
name="${BASH_REMATCH[1]}"
kind="function"
fi
if [[ -n "$name" ]]; then
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "$kind" "$name"
fi
fi
# Strict mode: also check unexported functions
if $STRICT && [[ -z "$name" ]] && [[ "$line" =~ $re_unexported_func ]]; then
name="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "function" "$name"
fi
fi
fi
# Check exported type declarations
if [[ "$line" =~ $re_exported_type ]]; then
local name="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "type" "$name"
fi
fi
# Strict mode: also check unexported type declarations
if $STRICT && [[ "$line" =~ $re_unexported_type ]]; then
local name="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "type" "$name"
fi
fi
# Check exported const (single-line, not in block)
if [[ "$line" =~ $re_exported_const ]]; then
local name="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "const" "$name"
fi
fi
# Check exported var (single-line, not blank identifier)
if [[ "$line" =~ $re_exported_var ]]; then
local name="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "var" "$name"
fi
fi
# Check package comment
if [[ "$line" =~ ^package[[:space:]]+ ]]; then
if ! is_documented "$prev_line" "$prev_prev_line"; then
local pkg_name
pkg_name=$(echo "$line" | sed 's/^package[[:space:]]*//;s/[[:space:]]*$//')
add_missing "$file" "$line_num" "package" "$pkg_name"
fi
fi
# Track grouped declaration blocks: const ( ... ), var ( ... ), type ( ... )
if [[ "$line" =~ $re_grouped_open ]]; then
in_grouped_block=true
grouped_kind="${BASH_REMATCH[1]}"
fi
if $in_grouped_block && [[ "$line" =~ ^\)[[:space:]]*$ ]]; then
in_grouped_block=false
grouped_kind=""
fi
if $in_grouped_block && [[ -n "$grouped_kind" ]]; then
# Check for exported names inside grouped block
if [[ "$line" =~ $re_grouped_exported ]]; then
local gname="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "$grouped_kind" "$gname"
fi
fi
# Strict: also check unexported names in grouped blocks
if $STRICT && [[ "$line" =~ $re_grouped_unexported ]]; then
local gname="${BASH_REMATCH[1]}"
if ! is_documented "$prev_line" "$prev_prev_line"; then
add_missing "$file" "$line_num" "$grouped_kind" "$gname"
fi
fi
fi
prev_prev_line="$prev_line"
prev_line="$line"
done < "$file"
}
is_documented() {
local prev="$1"
local prev_prev="$2"
# Previous line is a comment (// or end of block comment */)
if [[ "$prev" =~ ^[[:space:]]*//.* ]] || [[ "$prev" =~ \*/[[:space:]]*$ ]]; then
return 0
fi
# Previous line might be empty but line before is comment (allow one blank line)
if [[ -z "${prev// /}" ]] && [[ "$prev_prev" =~ ^[[:space:]]*//.* ]]; then
return 0
fi
return 1
}
FILES=()
while IFS= read -r f; do
[[ -n "$f" ]] && FILES+=("$f")
done < <(find_go_files "$TARGET")
if [[ ${#FILES[@]} -eq 0 ]]; then
if $JSON_OUTPUT; then
echo '{"missing":[],"count":0,"status":"no_go_files"}'
else
echo "No Go files found in: $TARGET"
fi
exit 0
fi
for file in "${FILES[@]}"; do
check_file "$file"
done
# Truncation
TOTAL=${#MISSING[@]}
TRUNCATED=false
if [[ $LIMIT -gt 0 && $TOTAL -gt $LIMIT ]]; then
MISSING=("${MISSING[@]:0:$LIMIT}")
TRUNCATED=true
fi
if $JSON_OUTPUT; then
echo "{"
echo ' "missing": ['
first=true
for entry in "${MISSING[@]+"${MISSING[@]}"}"; do
IFS='|' read -r location kind name <<< "$entry"
file="${location%%:*}"
line="${location#*:}"
$first || echo ","
first=false
printf ' {"file":"%s","line":%s,"kind":"%s","name":"%s"}' \
"$(json_escape "$file")" "$line" "$(json_escape "$kind")" "$(json_escape "$name")"
done
echo ""
echo " ],"
printf ' "total": %d,\n' "$TOTAL"
printf ' "truncated": %s\n' "$TRUNCATED"
echo "}"
else
if [[ $TOTAL -eq 0 ]]; then
echo "All exported symbols are documented."
exit 0
fi
echo "Undocumented exported symbols:"
echo ""
for entry in "${MISSING[@]}"; do
IFS='|' read -r location kind name <<< "$entry"
printf " %s [%s] %s\n" "$location" "$kind" "$name"
done
if $TRUNCATED; then
echo " ... and $((TOTAL - LIMIT)) more (use --limit to adjust)"
fi
echo ""
echo "Total: $TOTAL undocumented symbol(s)"
fi
if [[ $TOTAL -gt 0 ]]; then
exit 1
fi
exit 0