| name | pm-feedback |
| description | Use this skill when the user wants to report a bug, an unexpected result, or an improvement idea about the project-manager plugin ITSELF (or one of its pm-* skills) to the functional-claude maintainers — or when the agent has just hit a project-manager failure (a pm-* CLI error, a wrong/empty briefing, a crash, a clearly-incorrect result) and should offer to report it. Trigger phrases "report a bug in pm-review / pm-notes / any pm-* skill", "this plugin is broken", "pm gave the wrong answer", "send feedback about project-manager", "tell the functional-claude team", "/pm-feedback", "file a plugin issue". The defining cue is that the target of the complaint is the plugin or a named pm-* skill, not the user's own product. Assembles a README-shaped report (plugin + version, repro, expected vs actual, safe environment), scrubs every credential via the tested `hooks/lib/feedback-report.js` guard, previews it, then delivers — email to the Linear intake address (via resend-cli), GitHub issue fallback, or copy-paste draft. NOT for filing issues about the user's OWN project or product (that's pm-issues) — a bare "report a bug" that names no plugin or skill is ambiguous, so ask whether the bug is in the plugin or in their project rather than assuming. |
| version | 0.44.4 |
PM Feedback
Turn "this plugin broke" or "I have an idea for project-manager" into a clean, credential-safe report and route it to the functional-claude maintainers. This is the outbound counterpart to the pm-troubleshoot agent: troubleshoot diagnoses locally, pm-feedback reports upstream.
Usage
/pm-feedback # interactive: ask what went wrong
/pm-feedback <one-line description> # seed the summary, then fill the rest
description (string, optional) — a one-line summary of the bug/idea. If omitted, the skill asks.
When to run this
- The user explicitly asks to report a problem or idea about the project-manager plugin (not their own project's work).
- Proactively — right after a project-manager operation fails or returns a clearly-wrong result: a
pm-cache.js / pm-issue.js / pm-project.js non-zero exit with an error code, a briefing that's empty when issues plainly exist, a tracker-mismatch the user didn't cause, a crash/stack trace from a pm-* script. Offer once: "That looks like a project-manager bug — want me to send a report to the maintainers? I'll show you exactly what gets sent first." Never send without explicit confirmation.
Not when: the user wants to file an issue in their own tracker — that's pm-issues. This skill reports problems with the plugin to its authors.
What gets reported (and what never does)
The destination is the maintainers' intake, documented in the repo README.md → "Feedback & Issues". Email routes straight into their tracker with no account required.
- Maintainer email:
function-claude-e053690b4af6@intake.linear.app
- GitHub fallback:
https://github.com/nthplusio/functional-claude/issues (repo nthplusio/functional-claude)
Privacy guarantee — enforced in code, not by trust. All report assembly and redaction go through hooks/lib/feedback-report.js:
safeContext() whitelists only non-sensitive diagnostic fields (repo slug, branch, tracker type, team key, commit SHA). It never copies the raw merged project config, which can embed linear_api_key, recipient emails, or spec paths.
scrubString() / scrubObject() redact Linear/GitHub/Resend/JWT/Bearer/AWS/Slack token shapes and any *_API_KEY / *_TOKEN / *_SECRET env assignment, recursively, as a second net.
- The skill must never read
.resend.md, .claude/pm/project.local.json, or environment variables into the report body.
Workflow
Step 1 — Capture
Gather, from the conversation and the user (ask only for what's missing):
- kind:
bug | unexpected | idea
- problem: one-line summary
- reproduction: the steps (for a bug, the exact
/pm… command or pm-* invocation that failed)
- expected vs actual
- errorCode: if a pm-* CLI returned a structured
{ ok:false, code }, capture the code string (e.g. TRACKER_WRITE_FAILED, NO_API_KEY)
Step 2 — Gather safe context + version (deterministic)
PM_VERSION=$(node -e 'console.log(require(process.env.CLAUDE_PLUGIN_ROOT + "/.claude-plugin/plugin.json").version)')
CLAUDE_VERSION=$(claude --version 2>/dev/null || echo "unknown")
SLUG=...
CTX="$HOME/.claude/project-manager/cache/$SLUG/context.json"
Then assemble + scrub the report in one shot. Pass the captured fields and the raw context through feedback-report.js — do not hand-build the body:
node -e '
const fr = require(process.env.CLAUDE_PLUGIN_ROOT + "/hooks/lib/feedback-report.js");
const fs = require("fs");
const ctxPath = process.argv[1];
let context = null;
try { context = JSON.parse(fs.readFileSync(ctxPath, "utf8")); } catch (_) {}
const report = fr.assembleReport({
pluginVersion: process.env.PM_VERSION,
claudeVersion: process.env.CLAUDE_VERSION,
kind: process.env.FB_KIND,
problem: process.env.FB_PROBLEM,
reproduction: process.env.FB_REPRO,
expected: process.env.FB_EXPECTED,
actual: process.env.FB_ACTUAL,
errorCode: process.env.FB_CODE,
context,
});
process.stdout.write(JSON.stringify(report));
' "$CTX"
assembleReport returns { subject, body, environment } — all already scrubbed.
Step 3 — Preview (mandatory)
Show the user the exact subject and body that will be sent, and the chosen channel. This mirrors the preview-then-send discipline of pm-spec / pm-prep. Get explicit confirmation. If the user edits the text, re-run it through scrubString before sending.
Step 4 — Deliver (auto-detect, email-first)
Pick the first available channel; tell the user which one you used.
- Email (preferred) — if
RESEND_API_KEY is set or a .resend.md exists at the repo root, deliver via the resend-cli skill, overriding the recipient to the maintainer intake (not the project's default to):
- to:
function-claude-e053690b4af6@intake.linear.app
- from: the verified sender from
.resend.md (from: field)
- subject / text: the
subject / body from Step 2
- Invoke the resend-cli skill to perform the send so its config + error handling apply.
- GitHub issue (fallback) — else if
gh auth status succeeds:
printf '%s' "$BODY" > "$CLAUDE_JOB_DIR/tmp/pm-feedback.md" 2>/dev/null || printf '%s' "$BODY" > /tmp/pm-feedback.md
gh issue create --repo nthplusio/functional-claude \
--title "$SUBJECT" --body-file <the file above>
Tell the user this creates a public issue and confirm before running.
- Draft only (always works) — else, present the scrubbed
subject + body and the maintainer address so the user can paste it into their own mail client. Remind them the README also accepts GitHub issues.
Step 5 — Confirm
Report the outcome: channel used + the message ID / issue URL if the send returned one. If delivery failed, fall back to the draft (Step 4.3) so the user is never left empty-handed.
Failure modes
| Symptom | Handling |
|---|
No .resend.md and no gh auth | Fall through to draft (4.3) — never block. |
feedback-report.js missing / errors | Stop and tell the user; do not hand-build an unscrubbed body. |
| User pasted a credential into the description | The scrubber catches known shapes, but warn the user and re-preview. |
resend send returns RESEND_NOT_CONFIGURED | Drop to the GitHub or draft channel. |
Why a tested lib instead of prompt-only
context.json is written by pm-session-start.js from the merged project config, which can include linear_api_key. "Please don't include secrets" in a prompt is a hope; safeContext() + scrubObject() with unit tests (feedback-report.test.js) are a guarantee. Security-relevant invariants live in the deterministic layer.