@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.
- package/design-assets/agents/frontend-design-engineer.md +126 -0
- package/design-assets/hooks/frontend-design-guard.sh +52 -0
- package/design-assets/skills/design-critique/SKILL.md +162 -0
- package/dist/backfill-cycle-metrics.js +121 -0
- package/dist/index.js +1110 -358
- package/package.json +3 -2
- package/skills/papi-cycle/papi-advanced/SKILL.md +5 -10
|
@@ -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]);
|