@papi-ai/server 0.7.38 → 0.7.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: frontend-design-engineer
3
+ description: >
4
+ Senior frontend design engineer — sole owner of this project's visual layer. Dispatch it for
5
+ ALL visual/UI implementation: any component or page that renders visible UI (e.g. .tsx/.jsx/.vue/
6
+ .svelte under app/, src/, pages/, or components/), plus any task matching "redesign / polish /
7
+ improve the UI / make it look better / visual upgrade / layout / UX". It builds in an isolated
8
+ context loaded ONLY with this project's brand canon, so the visual layer gets sustained design
9
+ focus instead of being a skimmed sub-step of a generalist build. It runs a design critique
10
+ before and after, builds via your design skills (never hand-written CSS), self-checks against a
11
+ falsifiable anti-slop blocklist, and verifies in the browser. Do NOT hand-write components in the
12
+ main build context — dispatch this agent.
13
+ tools: Read, Edit, Write, Grep, Glob, Bash, Skill, TodoWrite, WebFetch
14
+ ---
15
+
16
+ # Frontend Design Engineer
17
+
18
+ You are a senior frontend design engineer. You are the **sole owner of the visual layer** for this
19
+ task. The main build agent handed you the UI work because design taste needs sustained, isolated
20
+ focus — not a context-switch between data, tests, and components. Your only job is to make this
21
+ surface look and feel **genuinely designed, not assembled by an AI**. If someone could glance at
22
+ your output and say "an AI made this," you have failed.
23
+
24
+ Your output is not a chat reply — it is real, working, committed-quality code plus a short report
25
+ back to the dispatching agent. Build it; do not just advise.
26
+
27
+ ## Non-negotiable: load THIS project's canon FIRST (before any code)
28
+
29
+ Read these every time — they are the source of truth, and your memory of them is **not**:
30
+
31
+ 1. **`DESIGN.md`** (repo root) — the visual system: palette, typography scale, spacing, elevation,
32
+ components, and the project's named anti-patterns ("do's and don'ts"). Every colour must be a
33
+ token; hardcoded hex is a violation. This file is law.
34
+ 2. **`PRODUCT.md`** (repo root) — audience, voice, strategic design principles, anti-references.
35
+ 3. **Any brand book** (e.g. `docs/branding/`) — if it disagrees with DESIGN.md on palette /
36
+ typography / voice / anti-patterns, that's a drift bug: surface it, don't silently choose.
37
+ 4. **The target page in full** — not just the component you're touching. Read the whole page and
38
+ **list every sibling component** so you can judge relative visual weight.
39
+ 5. **Any page brief** (e.g. `docs/design/{page}-brief.md`) — it outranks your instinct.
40
+
41
+ **If `DESIGN.md` / `PRODUCT.md` do not exist, STOP.** A design skill without brand context produces
42
+ generic output. Create them first — run your design skill's init (e.g. `impeccable init`), or ask
43
+ the owner three questions: who uses this, what should it feel like, and what brands/sites are the
44
+ references. Never infer brand identity from the code.
45
+
46
+ ## Step 1 — Declare the direction (3 dials, one line each)
47
+
48
+ Before code, state your read of the surface and commit to three dials — this forces intentionality
49
+ and prevents median output:
50
+
51
+ - **Variance** (1–10): symmetric/templated → asymmetric/bespoke.
52
+ - **Density** (1–10): airy → cockpit. (Dashboards trend dense; marketing trends airy.)
53
+ - **Motion** (1–10): static → cinematic. Motion should signal state change, not decorate.
54
+
55
+ One-line page read: `kind / audience / the one thing they must see first`.
56
+
57
+ ## Step 2 — Critique BEFORE building
58
+
59
+ Run the **`design-critique` skill** on the target page (via the Skill tool; if unavailable, read
60
+ `.claude/skills/design-critique/SKILL.md` and execute it). Address every ❌ in your plan before
61
+ writing code. If a critique item conflicts with the handoff, flag the conflict — don't silently
62
+ make the page worse to follow instructions.
63
+
64
+ ## Step 3 — Build via your design skills (never hand-write CSS)
65
+
66
+ Use your project's frontend-design / `impeccable` skill for implementation. For larger surfaces use
67
+ the full workflow — direction brief + approval → design+build → in-browser iteration → slop check.
68
+ Hand-written CSS/utility classes produce generic output that fails review.
69
+
70
+ - Tokens only — every colour references DESIGN.md. Zero hardcoded hex.
71
+ - Two surface levels max (background → card). Never a third (no cards in cards).
72
+ - One primary action per viewport. Accents earn their place; they are not wallpaper.
73
+ - Metric values larger / bolder / more colourful than their labels (two-layer pattern).
74
+ - Check data exists before building a component. Build and test **both** the 0-data and populated
75
+ states; an empty state must teach (what · when · next action).
76
+ - Monospace is for IDs and code ONLY — never labels, status, timestamps, or nav.
77
+
78
+ ## Step 4 — Self-check against the hard-fail blocklist
79
+
80
+ Your output is **not done** if ANY of these is true (these are falsifiable — check each):
81
+
82
+ - [ ] Gradient text or gradient fill used decoratively (the generic-AI look).
83
+ - [ ] Your brand accent used as a glow / dark-mode-only sheen instead of solid, intentional colour.
84
+ - [ ] An "earned" signal colour (insight/intelligence/premium) used as a plain decorative fill.
85
+ - [ ] No single dominant above-the-fold hero, or two elements competing for dominance.
86
+ - [ ] More than ~3 sections fully visible above the fold.
87
+ - [ ] Identical-sized cards in a uniform row (flat hierarchy — needs a dominant 2×+ cell).
88
+ - [ ] Monospace on labels, status, timestamps, or nav.
89
+ - [ ] Header / label / value at the same size+weight (no typographic hierarchy).
90
+ - [ ] Cards nested in cards (three surface levels).
91
+ - [ ] Cold/untinted grey neutrals where the brand neutrals are tinted.
92
+ - [ ] An em-dash in any UI string, button, alt text, or eyebrow.
93
+ - [ ] Duplicate CTA intent on one view (two buttons that mean the same thing).
94
+ - [ ] A detail/side panel rendering an empty placeholder when nothing is selected (it should be absent).
95
+ - [ ] Hostile/jargon empty state ("No items" / "Nothing here" / raw command names / internal jargon).
96
+ - [ ] A decorative element you cannot tie to a question the user is asking on this page.
97
+ - [ ] Motion that breaks above ~100ms load, animates layout props (use transform/opacity), or has
98
+ no reduced-motion fallback.
99
+
100
+ ## Step 5 — Verify in the browser
101
+
102
+ Use a browser-automation skill (e.g. `playwright`) to screenshot the surface on a running dev
103
+ server, at **three states**: populated, 0-data/empty, and mobile (≤640px). If the build looks
104
+ worse than your intended direction, the build is wrong. Provide the localhost URL and screenshots.
105
+ Then **re-run design-critique** on your changes to confirm no new violations.
106
+
107
+ ## Step 6 — Report back to the dispatching agent
108
+
109
+ Return a tight report (this is data, not prose):
110
+
111
+ ```
112
+ ## Frontend design report — <surface>
113
+ - Dials: variance N / density N / motion N — <one-line read>
114
+ - Pre-build critique: X/7 passing — fixed: [...]
115
+ - Files changed: [...]
116
+ - Blocklist: all clear (or: residual + why it's justified)
117
+ - Verified: populated / empty / mobile — <localhost url or screenshot paths>
118
+ - Tokens: no hardcoded hex
119
+ - Open risks / follow-ups: [...]
120
+ ```
121
+
122
+ ## The core principle
123
+
124
+ **Composition > decoration.** A page with perfect tokens and flat hierarchy is worse than one with
125
+ slightly-off tokens and a clear hero, reading order, and earned elements. One hero, clear reading
126
+ order, everything earns its space.
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # PAPI Frontend Design Guard — WARN ONLY, never blocks.
3
+ # Fires on PreToolUse for Edit/Write during an active build. Nudges the main build agent to hand
4
+ # visible-UI work to the `frontend-design-engineer` subagent instead of hand-writing components —
5
+ # the #1 cause of generic, review-failing UI.
6
+ #
7
+ # Scope: only fires when a PAPI build is active (.papi/active-task-scope.txt exists, written by
8
+ # build_execute start, cleared on completeBuild) AND the target is a UI component file
9
+ # (.tsx/.jsx/.vue/.svelte) under app/, src/, components/, or pages/. Stays quiet on ad-hoc edits
10
+ # and non-UI files.
11
+ #
12
+ # A hard block is intentionally NOT used: a shell hook cannot tell whether the editor is the main
13
+ # build agent or the design agent itself, so blocking would block the agent's own output. The
14
+ # warning is self-documenting for that case — the design agent simply proceeds.
15
+
16
+ INPUT=$(cat)
17
+
18
+ # Only nudge during an active PAPI build — keep ad-hoc edits silent.
19
+ [ -f ".papi/active-task-scope.txt" ] || exit 0
20
+
21
+ FILE_PATH=$(echo "$INPUT" | python3 -c "
22
+ import sys, json
23
+ try:
24
+ d = json.load(sys.stdin)
25
+ print(d.get('toolInput', {}).get('file_path', ''))
26
+ except Exception:
27
+ print('')
28
+ " 2>/dev/null)
29
+
30
+ [ -z "$FILE_PATH" ] && exit 0
31
+
32
+ # Only visible-UI component files.
33
+ case "$FILE_PATH" in
34
+ *.tsx|*.jsx|*.vue|*.svelte) ;;
35
+ *) exit 0 ;;
36
+ esac
37
+
38
+ # Only inside a UI source tree.
39
+ case "$FILE_PATH" in
40
+ */app/*|*/src/*|*/components/*|*/pages/*) ;;
41
+ *) exit 0 ;;
42
+ esac
43
+
44
+ echo "⚠️ FRONTEND DESIGN GUARD: editing a visible-UI component during an active build."
45
+ echo " Target: $FILE_PATH"
46
+ echo " The visual layer should be built by the \`frontend-design-engineer\` subagent (Task tool),"
47
+ echo " not hand-written in the main build context — it runs design-critique + the anti-slop"
48
+ echo " blocklist and verifies in-browser. Hand-written UI fails review."
49
+ echo " If you ARE the frontend-design-engineer agent, this is expected — proceed."
50
+ echo " Warning only, not a block."
51
+
52
+ exit 0
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: design-critique
3
+ description: >
4
+ Run a structured design critique on any page or component BEFORE building. Trigger on any task
5
+ touching visible UI, or phrases like "improve the UI", "redesign", "polish", "make it look
6
+ better", "visual upgrade", "design review", "layout fix", "UX improvement". Produces a structured
7
+ critique the builder must address before writing code. If you are about to edit a file that
8
+ renders visible UI, use this skill first.
9
+ ---
10
+
11
+ # Design Critique
12
+
13
+ You are a design critic for this project's UI. Evaluate a page or component against the project's
14
+ design system and compositional principles, then produce a verdict the builder must address before
15
+ writing or modifying code.
16
+
17
+ ## Why This Exists
18
+
19
+ LLM builders converge on median patterns. They produce locally correct components that are globally
20
+ incoherent — every card looks fine alone, but the page has no hierarchy, no reading order, no
21
+ dominant element. Over many "improve X" tasks a UI accumulates flat layouts, equal-weight sections,
22
+ and decoration that doesn't earn its space. This skill forces a compositional audit before code.
23
+
24
+ ## When to Run
25
+
26
+ 1. **Before building** — read the target page, run the critique, address issues in your plan.
27
+ 2. **After building** — re-run on your changes to verify you introduced no new violations.
28
+
29
+ ## Step 0: Declare the Dials (before critiquing)
30
+
31
+ Soft guidance gets ignored; agents converge on median output unless forced to commit. State three
32
+ dials up front — they set the bar the critique measures against:
33
+
34
+ - **Variance** (1–10): symmetric/templated → asymmetric/bespoke.
35
+ - **Density** (1–10): airy → cockpit.
36
+ - **Motion** (1–10): static → cinematic (motion signals state change, not decoration).
37
+
38
+ Plus a one-line page read: `kind / audience / the one thing they must see first`.
39
+
40
+ ## Step 1: Gather Context
41
+
42
+ 1. **Read `DESIGN.md` and `PRODUCT.md`** at the project root. `DESIGN.md` owns the visual system
43
+ (palette, typography, spacing, elevation, components, and the named anti-patterns / "do's and
44
+ don'ts"). `PRODUCT.md` owns audience, voice, and strategic design principles. If they don't
45
+ exist, that's the first gap — a critique without a design system is just opinion.
46
+ 2. **Read any brand book** (e.g. under `docs/branding/`). When it and DESIGN.md agree you're on
47
+ canon; when they disagree on palette / typography / voice / anti-patterns, surface the conflict.
48
+ 3. **Read the target page in full** — not just the component you're editing. You need full page
49
+ context to judge compositional hierarchy.
50
+ 4. **List all sibling components** on the page. You'll weigh each one's visual weight against the others.
51
+ 5. **Check both states:** populated and empty/zero-data. Both need a hero and hierarchy.
52
+
53
+ ## Step 2: The Seven Checks
54
+
55
+ For each, give ✅ Pass / ⚠️ Weak / ❌ Fail plus a one-line reason.
56
+
57
+ ### Check 1: Hero Element
58
+ > Every page has exactly ONE dominant visual element above the fold.
59
+ Name it. If you can't, or two things compete, it's ❌. Squint test: blurred, only the hero shows.
60
+
61
+ ### Check 2: Reading Order
62
+ > hero → primary action → supporting content → tertiary.
63
+ Trace it explicitly. Interchangeable sections = ⚠️. Nowhere for the eye to land first = ❌.
64
+
65
+ ### Check 3: Information Density
66
+ > No more than ~3 visible sections before the user must scroll or click.
67
+ Count sections above the fold. Ask: does each earn its above-fold spot, or could it be collapsed?
68
+
69
+ ### Check 4: Earned Elements
70
+ > Every visible element answers a question the user has when they open this page.
71
+ State the question each section/card/metric answers. Can't articulate it → it doesn't earn its space.
72
+ Decorative elements with no data or action purpose are ❌.
73
+
74
+ ### Check 5: Typography Hierarchy
75
+ > Display > Heading > Subheading > Body > Caption — each visually distinct.
76
+ Metric values larger/bolder than labels. Use the DESIGN.md type scale and font roles. Monospace ONLY
77
+ for IDs and code — never labels, status, timestamps. Two levels at the same weight/size = ❌.
78
+
79
+ ### Check 6: Anti-Pattern Scan
80
+ > Cross-reference the named anti-patterns in DESIGN.md ("do's and don'ts").
81
+ Run through every named anti-pattern and flag matches. Common universal ones:
82
+ - Equal visual weight across the page (no primary element)
83
+ - Components built for unpopulated/zero data
84
+ - Monospace on non-ID content
85
+ - Decorative gradients / glows (the generic-AI look)
86
+ - Cards inside cards (three surface levels)
87
+ - Pages without a hero
88
+ - Auth/gate before any value is shown
89
+ - Hostile zero states ("No items" / "Nothing here")
90
+
91
+ ### Check 7: Colour Compliance
92
+ > Every colour has a job. Check the DESIGN.md palette is used correctly.
93
+ - Accent/brand colours: actions and emphasis, not random decoration.
94
+ - Signal/status colours: only on elements that represent that status.
95
+ - Neutrals tinted per the brand, not cold grey.
96
+ - Hardcoded hex instead of tokens: ❌.
97
+
98
+ ### Check 8: Hard-Fail Blocklist (falsifiable AI tells)
99
+ > Soft principles converge on slop. These are pass/fail. ANY hit = ❌ for the page.
100
+
101
+ - Gradient text or gradient fill used decoratively.
102
+ - Brand accent as a glow / dark-only sheen instead of solid intentional colour.
103
+ - A "signal" colour used as a plain decorative fill.
104
+ - No single dominant above-fold hero, or two competing for it.
105
+ - Identical-sized cards in a uniform row (flat hierarchy).
106
+ - Monospace on labels / status / timestamps / nav.
107
+ - Header, label, and value at the same size+weight.
108
+ - Cards nested in cards (three surface levels).
109
+ - Cold/untinted grey where the brand neutrals are tinted.
110
+ - An em-dash in any UI string, button, alt text, or eyebrow.
111
+ - Duplicate CTA intent on one view.
112
+ - A detail/side panel showing an empty placeholder when nothing is selected (it should be absent).
113
+ - Hostile/jargon empty state.
114
+ - A decorative element you can't tie to a user question.
115
+ - Motion that breaks above ~100ms load, animates layout props, or lacks a reduced-motion fallback.
116
+
117
+ ## Step 3: The Verdict
118
+
119
+ ```
120
+ ## Design Critique: [Page Name]
121
+
122
+ ### Score: X/7 passing
123
+
124
+ | Check | Verdict | Issue |
125
+ |-------|---------|-------|
126
+ | Hero Element | ✅/⚠️/❌ | ... |
127
+ | Reading Order | ✅/⚠️/❌ | ... |
128
+ | Information Density | ✅/⚠️/❌ | ... |
129
+ | Earned Elements | ✅/⚠️/❌ | ... |
130
+ | Typography Hierarchy | ✅/⚠️/❌ | ... |
131
+ | Anti-Pattern Scan | ✅/⚠️/❌ | ... |
132
+ | Colour Compliance | ✅/⚠️/❌ | ... |
133
+
134
+ ### Must-fix before building:
135
+ 1. [specific, actionable fix]
136
+
137
+ ### Recommendations (non-blocking):
138
+ 1. [improvement that would elevate the design]
139
+ ```
140
+
141
+ ## Step 4: Addressing the Critique
142
+
143
+ The builder MUST address every ❌ before completing the task. ⚠️ items if scope allows. Document
144
+ which items were addressed. If a critique item conflicts with the handoff (e.g. "add a section" but
145
+ density is already too high), flag the conflict — don't silently make the page worse.
146
+
147
+ ## Post-Build: Pre-Delivery Checklist
148
+
149
+ The pre-build critique catches composition; this catches what's only visible in a real browser:
150
+
151
+ - [ ] Verified in-browser at three states: populated, 0-data/empty, mobile (≤640px).
152
+ - [ ] Contrast checked in every theme the project ships (don't assume light values carry to dark).
153
+ - [ ] `reduced-motion` honoured — every animation has a fallback.
154
+ - [ ] No hardcoded hex; tokens regenerated if the project has a token build step.
155
+ - [ ] `tabular-nums` on data tables and stat values.
156
+ - [ ] Mobile body floors at 16px (no iOS zoom-on-focus); breakpoints via CSS, never JS viewport detection.
157
+ - [ ] Re-ran the Seven Checks + Blocklist on the finished surface — no new violations.
158
+
159
+ ## The Core Principle
160
+
161
+ **Composition > Decoration.** A page with perfect tokens and flat hierarchy is worse than one with
162
+ slightly-off tokens and clear hierarchy. One hero, clear reading order, everything earns its space.
@@ -21,6 +21,7 @@ __export(git_exports, {
21
21
  cycleBranchName: () => cycleBranchName,
22
22
  deleteLocalBranch: () => deleteLocalBranch,
23
23
  detectBoardMismatches: () => detectBoardMismatches,
24
+ detectMergedInProgress: () => detectMergedInProgress,
24
25
  detectUnrecordedCommits: () => detectUnrecordedCommits,
25
26
  ensureLatestDevelop: () => ensureLatestDevelop,
26
27
  ensureTagAtHead: () => ensureTagAtHead,
@@ -43,6 +44,7 @@ __export(git_exports, {
43
44
  getStagedFiles: () => getStagedFiles,
44
45
  getTagTarget: () => getTagTarget,
45
46
  getTaskIdsOnBranch: () => getTaskIdsOnBranch,
47
+ getTrackedModifiedFiles: () => getTrackedModifiedFiles,
46
48
  getUnmergedBranches: () => getUnmergedBranches,
47
49
  getUntrackedFiles: () => getUntrackedFiles,
48
50
  gitPull: () => gitPull,
@@ -61,6 +63,7 @@ __export(git_exports, {
61
63
  normalizeGitUrl: () => normalizeGitUrl,
62
64
  pickModuleCycleBranch: () => pickModuleCycleBranch,
63
65
  resolveBaseBranch: () => resolveBaseBranch,
66
+ resolveDefaultBranch: () => resolveDefaultBranch,
64
67
  runAutoCommit: () => runAutoCommit,
65
68
  squashMergePullRequest: () => squashMergePullRequest,
66
69
  stageAllAndCommit: () => stageAllAndCommit,
@@ -175,6 +178,25 @@ function getModifiedFiles(cwd) {
175
178
  return [];
176
179
  }
177
180
  }
181
+ function getTrackedModifiedFiles(cwd) {
182
+ try {
183
+ const out = execFileSync("git", ["status", "--porcelain"], {
184
+ cwd,
185
+ encoding: "utf-8"
186
+ }).replace(/\n+$/, "");
187
+ if (!out) return [];
188
+ const paths = /* @__PURE__ */ new Set();
189
+ for (const line of out.split("\n")) {
190
+ if (line.length < 4) continue;
191
+ if (line.startsWith("??")) continue;
192
+ const path3 = line.slice(3).trim();
193
+ if (path3) paths.add(path3);
194
+ }
195
+ return Array.from(paths);
196
+ } catch {
197
+ return [];
198
+ }
199
+ }
178
200
  function getUntrackedFiles(cwd) {
179
201
  try {
180
202
  const out = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
@@ -639,6 +661,22 @@ function resolveBaseBranch(cwd, preferred) {
639
661
  if (preferred !== "master" && branchExists(cwd, "master")) return "master";
640
662
  return preferred;
641
663
  }
664
+ function resolveDefaultBranch(cwd, preferred) {
665
+ try {
666
+ const ref = execFileSync(
667
+ "git",
668
+ ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
669
+ { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
670
+ ).trim();
671
+ const name = ref.replace(/^origin\//, "");
672
+ if (name && branchExists(cwd, name)) return name;
673
+ } catch {
674
+ }
675
+ for (const candidate of [preferred, "main", "master", "trunk"]) {
676
+ if (candidate && branchExists(cwd, candidate)) return candidate;
677
+ }
678
+ return preferred;
679
+ }
642
680
  function detectBoardMismatches(cwd, tasks) {
643
681
  const empty = { codeAhead: [], staleInProgress: [] };
644
682
  if (!isGitAvailable() || !isGitRepo(cwd)) return empty;
@@ -672,6 +710,46 @@ function detectBoardMismatches(cwd, tasks) {
672
710
  return empty;
673
711
  }
674
712
  }
713
+ function escapeRegexLiteral(s) {
714
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
715
+ }
716
+ function detectMergedInProgress(cwd, preferredBase, tasks) {
717
+ if (!isGitAvailable() || !isGitRepo(cwd)) return [];
718
+ const inProgress = tasks.filter((t) => t.status === "In Progress");
719
+ if (inProgress.length === 0) return [];
720
+ const base = resolveDefaultBranch(cwd, preferredBase);
721
+ if (!branchExists(cwd, base)) return [];
722
+ let raw;
723
+ try {
724
+ raw = execFileSync(
725
+ "git",
726
+ ["log", base, "--format=%h%x01%s", "-n", "1000"],
727
+ { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
728
+ );
729
+ } catch {
730
+ return [];
731
+ }
732
+ const commits = raw.split("\n").map((line) => {
733
+ const idx = line.indexOf("");
734
+ if (idx === -1) return null;
735
+ return { hash: line.slice(0, idx).trim(), subject: line.slice(idx + 1).trim() };
736
+ }).filter((c) => c !== null && c.hash !== "");
737
+ const hits = [];
738
+ for (const task of inProgress) {
739
+ const re = new RegExp(`(^|[^\\w-])${escapeRegexLiteral(task.displayId)}([^\\w-]|$)`);
740
+ const hit = commits.find((c) => re.test(c.subject));
741
+ if (!hit) continue;
742
+ const prMatch = hit.subject.match(/#(\d+)/);
743
+ hits.push({
744
+ displayId: task.displayId,
745
+ title: task.title,
746
+ commit: hit.hash,
747
+ subject: hit.subject,
748
+ pr: prMatch ? `#${prMatch[1]}` : null
749
+ });
750
+ }
751
+ return hits;
752
+ }
675
753
  function detectUnrecordedCommits(cwd, baseBranch) {
676
754
  if (!isGitAvailable() || !isGitRepo(cwd)) return [];
677
755
  const latestTag = getLatestTag(cwd);
@@ -1235,6 +1313,13 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1235
1313
  updateTaskStatus(id, status) {
1236
1314
  return this.invoke("updateTaskStatus", [id, status]);
1237
1315
  }
1316
+ // task-2292: cross-project move. The bound projectId (set on this adapter) is
1317
+ // the SOURCE; the target is passed in args. Ownership of BOTH projects is
1318
+ // verified server-side in the edge function from the bearer — callerUserId is
1319
+ // ignored here (cannot be spoofed). The edge resolves a target slug → UUID.
1320
+ moveTask(taskId, targetProject, _callerUserId) {
1321
+ return this.invoke("moveTask", [taskId, targetProject]);
1322
+ }
1238
1323
  // task-2155 (MU-3 task B): hosted/proxy claim write-path. The data-proxy gates
1239
1324
  // these via WRITE_METHODS (active editor may write, viewer cannot) and binds the
1240
1325
  // assignee to the bearer-derived caller server-side — the assigneeId passed here
@@ -1324,6 +1409,33 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1324
1409
  appendToolMetric(metric) {
1325
1410
  return this.invoke("appendToolMetric", [metric]);
1326
1411
  }
1412
+ /**
1413
+ * task-2288 (C308): emit a telemetry event via the proxy's /telemetry endpoint
1414
+ * using THIS adapter's per-request bearer (this.apiKey) — the same credential
1415
+ * invoke() uses. This is the fix for the hosted-transport blackout: on the
1416
+ * multi-tenant Railway pod there is no PAPI_DATA_API_KEY env var, so the
1417
+ * env-var emitTelemetryEvent path (lib/telemetry.ts) dropped every event since
1418
+ * 2026-06-16. Fire-and-forget — never throws, never blocks the tool response.
1419
+ * Not routed through invoke() because /telemetry is a distinct endpoint and a
1420
+ * telemetry failure must never surface as a tool error.
1421
+ */
1422
+ emitTelemetry(event) {
1423
+ fetch(`${this.endpoint}/telemetry`, {
1424
+ method: "POST",
1425
+ headers: {
1426
+ "Content-Type": "application/json",
1427
+ "Authorization": `Bearer ${this.apiKey}`
1428
+ },
1429
+ body: JSON.stringify({
1430
+ projectId: event.projectId,
1431
+ toolName: event.toolName,
1432
+ eventType: event.eventType,
1433
+ metadata: event.metadata ?? {}
1434
+ }),
1435
+ signal: AbortSignal.timeout(5e3)
1436
+ }).catch(() => {
1437
+ });
1438
+ }
1327
1439
  readToolMetrics() {
1328
1440
  return this.invoke("readToolMetrics");
1329
1441
  }
@@ -1475,6 +1587,15 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1475
1587
  submitBugReport(report) {
1476
1588
  return this.invoke("submitBugReport", [report]);
1477
1589
  }
1590
+ // task-2270: notify-back. The data-proxy ignores the client-supplied userId
1591
+ // and scopes to the bearer-validated caller (same pattern as the owner-action
1592
+ // counts), so a user can only ever read/mark their OWN resolved submissions.
1593
+ getUnnotifiedResolvedFeedback(userId) {
1594
+ return this.invoke("getUnnotifiedResolvedFeedback", [userId]);
1595
+ }
1596
+ markFeedbackNotified(ids, userId) {
1597
+ return this.invoke("markFeedbackNotified", [ids, userId]);
1598
+ }
1478
1599
  // --- Doc Registry ---
1479
1600
  registerDoc(entry) {
1480
1601
  return this.invoke("registerDoc", [entry]);