Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7a1ceb0
Add /ux-design workflow with research, prototyping, evaluation, and h…
adalton Aug 21, 2026
7cc98a6
Refine ux-design workflow: bare-skill deps, refine loop, provenance
adalton Aug 21, 2026
7889a17
ux-design: ingest upstream context and reality-check the handoff
adalton Aug 24, 2026
c5fb55c
Address CodeRabbit PR #108 feedback: quality, safety, and consistency…
adalton Aug 24, 2026
791e233
Remove product-specific coupling from ux-design controller
adalton Aug 24, 2026
3018a57
Address new CodeRabbit feedback: quality and portability improvements
adalton Aug 24, 2026
a5465cb
Fix CodeRabbit feedback and CI: consistency, portability, and versioning
adalton Aug 25, 2026
e678df2
ux-design: explicitly capture source-repository root before skill inv…
adalton Aug 25, 2026
d56d0a7
ux-design: use git rev-parse and relative path for --project
adalton Aug 25, 2026
372266e
ux-design: revalidate replacement config values before saving
adalton Aug 25, 2026
f520b9d
ux-design: validate repo root before cd and use bare skill name
adalton Aug 25, 2026
1f7d12f
ux-design: move example paths into fenced code block
adalton Aug 25, 2026
5b47504
Merge branch 'main' into andalton/ux-design-workflow
adalton Aug 26, 2026
fa9c0f5
Bump prd and design to 0.9.1 per shared-file version cascade
adalton Aug 26, 2026
eb1dba4
Merge remote-tracking branch 'origin/main' into andalton/ux-design-wo…
adalton Aug 26, 2026
e98115b
Bump triage to 0.4.1 per shared-file version cascade
adalton Aug 26, 2026
68b7a50
AGENTS.md: document ux-design as a provenance consumer
adalton Aug 26, 2026
19b5cd9
Merge remote-tracking branch 'origin/main' into andalton/ux-design-wo…
adalton Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This repository contains reusable AI coding workflows that can be installed glob
- **implement** — Story-to-code workflow (ingest, plan, revise, code, validate, publish, respond)
- **kcs** — KCS Solution article workflow (gather, draft, validate, handoff)
- **prd** — Requirements-to-PRD workflow (ingest, clarify, draft, revise, publish, respond)
- **ux-design** — UX design workflow (ingest, research, prototype, evaluate, handoff, revise, publish, respond)
- **rebase-stack** — Rebase a stacked-branch chain with conflict guidance, per-branch validation, and push (start, continue, validate, push)
- **sizing** — Pre-cycle Feature sizing with T-shirt sizes and team effort breakdowns (ingest, assess, apply)
- **skill-reviewer** — Meta-workflow that audits AI skill directories
Expand Down Expand Up @@ -69,7 +70,7 @@ _shared/
validation-gate.md # Pre-commit build/test/lint discovery gate (used by bugfix)
```

Recipes are self-contained, parameterized procedures that workflows reference via relative path (e.g., `../../_shared/recipes/self-review-gate.md` from `skills/`). Workflows may also reference shared files from `guidelines.md`, `templates/`, `prompts/`, `scripts/`, and other behavioral markdown — all such references count as consumers for the shared-file cascade (see Workflow Versioning). The **prd** and **design** workflows use the provenance recipes on `/draft`, `/revise`, `/respond` (capture) and `/publish` plus docs-sync paths (render). See `_shared/provenance-schema.md` for the published footer format.
Recipes are self-contained, parameterized procedures that workflows reference via relative path (e.g., `../../_shared/recipes/self-review-gate.md` from `skills/`). Workflows may also reference shared files from `guidelines.md`, `templates/`, `prompts/`, `scripts/`, and other behavioral markdown — all such references count as consumers for the shared-file cascade (see Workflow Versioning). The **prd** and **design** workflows use the provenance recipes on `/draft`, `/revise`, `/respond` (capture) and `/publish` plus docs-sync paths (render). The **ux-design** workflow uses them on `/handoff`, `/revise`, `/respond` (capture) and `/publish` plus `/respond` (render). See `_shared/provenance-schema.md` for the published footer format.

### File Reference Conventions

Expand Down Expand Up @@ -183,6 +184,7 @@ ai-workflows/
│ ├── prompts/
│ └── scripts/
├── triage/
├── ux-design/
├── install.sh # Installer with auto-discovery
├── uninstall.sh # Removal script
├── AGENTS.md # AI assistant guidance (this file)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ Reusable AI coding workflows a team member can install globally or per-project,
- **Skill Reviewer** -- Meta-workflow that audits AI skill directories against eight quality dimensions.
See [skill-reviewer/README.md](skill-reviewer/README.md).

- **UX Design** -- UX design workflow: ingest a feature request, conduct user research, generate prototypes, run heuristic evaluation, and produce a validated design handoff for the `ui-design` workflow.
See [ux-design/README.md](ux-design/README.md).

## How It Works

Each workflow is a directory with a `SKILL.md` (the mandatory entry point), optional phase skills under `skills/`, and optional command wrappers under `commands/` -- all plain markdown, no IDE-specific syntax. Some workflows also include a `skills/controller.md` for phase dispatch, but this is an optional pattern. The installer auto-discovers every directory that contains a `SKILL.md`.
Expand Down
6 changes: 3 additions & 3 deletions _shared/recipes/capture-provenance-event.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: capture-provenance-event
version: 0.1.1
version: 0.1.2
---
# Recipe: Capture Provenance Event

Expand All @@ -11,9 +11,9 @@ phase mutates the planning document. See `../provenance-schema.md`.

| Parameter | Required | Description |
|-----------|----------|-------------|
| WORKFLOW | Yes | `prd` or `design` |
| WORKFLOW | Yes | `prd`, `design`, or `ux-design` |
| ISSUE_KEY | Yes | Full Jira issue key including project prefix (e.g., `PROJ-1234`, not `1234`) |
| PHASE | Yes | `draft`, `revise`, or `respond` |
| PHASE | Yes | `draft`, `revise`, or `respond` (ux-design also uses `handoff`) |
| AUTHORING_MODE | Yes | `skill` (default for phase skills) or `manual` |

## Procedure
Expand Down
4 changes: 2 additions & 2 deletions _shared/recipes/render-provenance-footer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: render-provenance-footer
version: 0.1.1
version: 0.1.2
---
# Recipe: Render Provenance Footer

Expand All @@ -11,7 +11,7 @@ Render the durable `## Provenance` footer into a docs-repo markdown file before

| Parameter | Required | Description |
|-----------|----------|-------------|
| WORKFLOW | Yes | `prd` or `design` |
| WORKFLOW | Yes | `prd`, `design`, or `ux-design` |
| ISSUE_KEY | Yes | Full Jira issue key including project prefix (e.g., `PROJ-1234`, not `1234`) |
| TARGET_FILE | Yes | Absolute path to the docs-repo file about to be committed |
| ALLOW_MISSING | No | Set to `yes` only after the user explicitly declines provenance |
Expand Down
68 changes: 55 additions & 13 deletions _shared/scripts/provenance.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Capture and render provenance for prd/design planning document workflows.
"""Capture and render provenance for prd/design/ux-design planning documents.

Exit codes:
0: Success (capture or render completed)
Expand All @@ -23,9 +23,29 @@
WORKFLOW_DOCS = {
"prd": "03-prd.md",
"design": "03-design.md",
"ux-design": "05-handoff.md",
}

AUTHORING_PHASES = frozenset({"draft", "revise", "respond", "manual-edit"})
# The phase that legitimately originates each workflow's document. prd/design
# originate from a template-checked /draft; ux-design assembles its handoff spec
# in /handoff (there is no template-from-origin step), so `handoff` is its
# origin. A first event other than this marks the phase history as untracked.
ORIGIN_PHASE = {
"prd": "draft",
"design": "draft",
"ux-design": "handoff",
}

AUTHORING_PHASES = frozenset(
{"draft", "handoff", "revise", "respond", "manual-edit"}
Comment thread
adalton marked this conversation as resolved.
)

# Per-workflow valid phases (for validation in capture_event)
WORKFLOW_PHASES = {
"prd": frozenset({"draft", "revise", "respond", "manual-edit", "commit"}),
"design": frozenset({"draft", "revise", "respond", "manual-edit", "commit"}),
"ux-design": frozenset({"handoff", "revise", "respond", "manual-edit", "commit"}),
}

DRIFT_FIELDS = (
"workflow_version",
Expand Down Expand Up @@ -56,10 +76,18 @@
COMMIT_ONLY_NOTE = (
"> Authoring phases not recorded this session (commit-time snapshot only)."
)
ORIGIN_UNTRACKED_NOTE = (
"> This document's phase history does not include an initial /draft — "
"structure was not verified against the template from origin."
)
def origin_untracked_note(workflow: str | None = None) -> str:
origin = ORIGIN_PHASE.get(workflow, "draft")
# ux-design has no template step; its /handoff assembles from scratch
if workflow == "ux-design":
return (
f"> This document's phase history does not include an initial /{origin} — "
"structure was not verified from origin."
)
return (
f"> This document's phase history does not include an initial /{origin} — "
"structure was not verified against the template from origin."
)


def repo_root(start: Path) -> Path | None:
Expand Down Expand Up @@ -260,12 +288,15 @@ def provenance_kind(events: list[dict[str, Any]]) -> str:
return "session"


def origin_untracked(events: list[dict[str, Any]]) -> bool:
def origin_untracked(
events: list[dict[str, Any]], workflow: str | None = None
) -> bool:
if not events:
return False
if provenance_kind(events) == "commit_only":
return False
return events[0].get("phase") != "draft"
origin = ORIGIN_PHASE.get(workflow, "draft")
return events[0].get("phase") != origin


def capture_event(
Expand All @@ -274,6 +305,14 @@ def capture_event(
phase: str,
authoring_mode: str,
) -> None:
# Validate phase is valid for this workflow
valid_phases = WORKFLOW_PHASES.get(workflow)
if valid_phases and phase not in valid_phases:
raise ValueError(
f"Phase '{phase}' is not valid for workflow '{workflow}'. "
f"Valid phases: {', '.join(sorted(valid_phases))}"
)

ai_root = ai_workflows_root()
ws_root = workspace_root()
path = provenance_path(workflow, issue)
Expand Down Expand Up @@ -352,6 +391,7 @@ def build_metrics_payload(data: dict[str, Any]) -> dict[str, Any]:
last = events[-1] if events else {}
drift = data.get("drift", {})
kind = provenance_kind(events)
workflow = data.get("workflow", "unknown")
return {
"schema_version": 1,
"provenance_kind": kind,
Expand All @@ -368,7 +408,7 @@ def build_metrics_payload(data: dict[str, Any]) -> dict[str, Any]:
{event.get("authoring_mode", "skill") for event in events}
),
"context_changed": drift.get("context_changed", False),
"origin_untracked": origin_untracked(events),
"origin_untracked": origin_untracked(events, workflow),
}


Expand Down Expand Up @@ -403,9 +443,9 @@ def build_footer(data: dict[str, Any]) -> str:
if len(phases) > 1:
lines.append(f"Phases: {', '.join(phases)}")

if origin_untracked(events):
if origin_untracked(events, workflow):
lines.append("")
lines.append(ORIGIN_UNTRACKED_NOTE)
lines.append(origin_untracked_note(workflow))

lines.append("")
lines.append(metrics_comment)
Expand Down Expand Up @@ -491,7 +531,9 @@ def render_footer(workflow: str, issue: str, target: Path, *, allow_missing: boo


def main() -> int:
parser = argparse.ArgumentParser(description="PRD/design provenance helper")
parser = argparse.ArgumentParser(
description="PRD/design/ux-design provenance helper"
)
sub = parser.add_subparsers(dest="command", required=True)

capture = sub.add_parser("capture", help="Append a provenance event")
Expand All @@ -500,7 +542,7 @@ def main() -> int:
capture.add_argument(
"--phase",
required=True,
choices=["draft", "revise", "respond", "manual-edit", "commit"],
choices=["draft", "handoff", "revise", "respond", "manual-edit", "commit"],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
capture.add_argument(
"--authoring-mode",
Expand Down
40 changes: 40 additions & 0 deletions _shared/scripts/test_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ def test_origin_untracked_false_when_all_events_are_commit(self) -> None:
self.assertEqual(provenance.provenance_kind(events), "commit_only")
self.assertFalse(provenance.origin_untracked(events))

def test_origin_untracked_false_for_ux_design_handoff_first(self) -> None:
# ux-design originates its document in /handoff (not /draft), so a
# handoff-first log is a tracked origin and must NOT be flagged.
events = [{"phase": "handoff"}, {"phase": "revise"}]
self.assertFalse(provenance.origin_untracked(events, "ux-design"))

def test_origin_untracked_true_for_ux_design_revise_first(self) -> None:
# ux-design entered at /revise with no prior /handoff is untracked.
events = [{"phase": "revise"}]
self.assertTrue(provenance.origin_untracked(events, "ux-design"))

def test_origin_untracked_true_for_prd_handoff_first(self) -> None:
# 'handoff' is not prd's origin phase, so a handoff-first prd log is
# still untracked -- the per-workflow origin must not leak across.
events = [{"phase": "handoff"}]
self.assertTrue(provenance.origin_untracked(events, "prd"))

def test_origin_untracked_note_names_workflow_origin_phase(self) -> None:
ux_note = provenance.origin_untracked_note("ux-design")
self.assertIn("/handoff", ux_note)
# ux-design has no template step, so note should not mention "template"
self.assertNotIn("template", ux_note)

prd_note = provenance.origin_untracked_note("prd")
self.assertIn("/draft", prd_note)
self.assertIn("template", prd_note) # prd/design DO have templates

self.assertIn("/draft", provenance.origin_untracked_note())

def test_workflow_phase_validation_rejects_invalid_combinations(self) -> None:
# prd/design don't have 'handoff' phase
with self.assertRaises(ValueError) as cm:
provenance.capture_event("prd", "TEST-123", "handoff", "skill")
self.assertIn("not valid for workflow 'prd'", str(cm.exception))

# ux-design doesn't have 'draft' phase
with self.assertRaises(ValueError) as cm:
provenance.capture_event("ux-design", "TEST-456", "draft", "skill")
self.assertIn("not valid for workflow 'ux-design'", str(cm.exception))

def test_build_metrics_payload_flags_origin_untracked(self) -> None:
data = {
"workflow": "prd",
Expand Down
2 changes: 1 addition & 1 deletion design/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: design
version: 0.9.0
version: 0.9.1
description: >-
Design-and-decompose workflow that takes a PRD, researches the problem space,
drafts a technical design document with a requirement-anchored testplan,
Expand Down
67 changes: 67 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,70 @@ ensure_repo_linked() {
echo " Linked $INSTALL_DIR -> $REPO_DIR"
}

UXD_REPO="https://github.com/rh-uxd/ai-helpers.git"
UXD_SHA="ad44b9c92c89730da5191487d0ff82af09b41366"
UXD_DIR="${HOME}/.uxd-ai-skills"
UXD_PLUGINS=(uxd-workshop)

# Install UXD AI Skills via git clone + symlinks (AI-agnostic; works for all
# tools). Called at the end of each install target when ux-design is in scope.
install_uxd_skills() {
local skills_dir="$1"

# Only install if ux-design is in the workflow set being installed
local has_ux_design=false
for wf in "${WORKFLOWS[@]}"; do
[[ "$wf" == "ux-design" ]] && has_ux_design=true
done
"$has_ux_design" || return 0

if [[ ! -d "$UXD_DIR" ]]; then
echo " Cloning UXD AI Skills repo (${UXD_SHA:0:7})..."
git clone "$UXD_REPO" "$UXD_DIR" 2>/dev/null || {
echo " Error: could not clone UXD AI Skills repo — ux-design workflow requires it" >&2
echo " Check network access to github.com and re-run install." >&2
return 1
}
git -C "$UXD_DIR" checkout "$UXD_SHA" 2>/dev/null || {
echo " Error: could not check out UXD AI Skills commit ${UXD_SHA:0:7}" >&2
return 1
}
else
# Existing install: make sure it is on the pinned SHA. Fetch first in case
# the local clone predates the pinned commit; a fetch failure is non-fatal
# only when the commit is already present locally.
if ! git -C "$UXD_DIR" cat-file -e "${UXD_SHA}^{commit}" 2>/dev/null; then
echo " Fetching UXD AI Skills updates (${UXD_SHA:0:7})..."
git -C "$UXD_DIR" fetch origin 2>/dev/null || {
echo " Error: could not fetch UXD AI Skills commit ${UXD_SHA:0:7}" >&2
echo " Check network access to github.com and re-run install." >&2
return 1
}
fi
git -C "$UXD_DIR" checkout "$UXD_SHA" 2>/dev/null || {
echo " Error: could not check out UXD AI Skills commit ${UXD_SHA:0:7}" >&2
return 1
}
fi

for plugin in "${UXD_PLUGINS[@]}"; do
local plugin_skills="${UXD_DIR}/plugins/${plugin}/skills"
[[ -d "$plugin_skills" ]] || continue
for skill_dir in "${plugin_skills}"/*/; do
[[ -d "$skill_dir" ]] || continue
local skill_name
skill_name="$(basename "$skill_dir")"
local target="${skills_dir}/${skill_name}"
if [[ -e "$target" && ! -L "$target" ]]; then
echo " Warning: ${target} exists and is not a symlink; skipping" >&2
continue
fi
ln -sfn "$skill_dir" "$target"
echo " Linked ${target} -> ${skill_dir} (uxd)"
done
done
}

install_shared() {
local target_dir="$1"
if [[ ! -d "${INSTALL_DIR}/_shared" ]]; then
Expand Down Expand Up @@ -203,6 +267,7 @@ install_cursor() {
echo " Linked ${SKILLS_DIR}/${wf} -> ${INSTALL_DIR}/${wf} ($SCOPE)"
done
generate_cursor_commands "$CMDS_DIR"
install_uxd_skills "$SKILLS_DIR"
}

install_claude() {
Expand Down Expand Up @@ -277,6 +342,7 @@ install_claude() {
echo " Removed stale commands symlink ${CMDS_DIR}/${wf} ($SCOPE)"
fi
done
install_uxd_skills "$SKILLS_DIR"
}

install_gemini() {
Expand All @@ -292,6 +358,7 @@ install_gemini() {
ln -sfn "${INSTALL_DIR}/${wf}" "${SKILLS_DIR}/${wf}"
echo " Linked ${SKILLS_DIR}/${wf} -> ${INSTALL_DIR}/${wf} ($SCOPE)"
done
install_uxd_skills "$SKILLS_DIR"
}

# Offer a daily systemd --user notifier (Linux desktop). Default: no.
Expand Down
2 changes: 1 addition & 1 deletion prd/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: prd
version: 0.9.0
version: 0.9.1
description: >-
Requirements-to-PRD workflow that ingests requirements from Jira, clarifies
ambiguities through iterative Q&A, drafts a Product Requirements Document,
Expand Down
2 changes: 1 addition & 1 deletion triage/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: triage
version: 0.5.0
version: 0.5.1
description: >-
Bulk-triage unresolved Jira bugs with AI-driven recommendations and an
interactive HTML report. Scan also loads recently resolved bugs for regression
Expand Down
Loading
Loading