---
name: pstack-add-project
preamble-tier: 3
version: 0.3.0
description: |
  Guided setup for the user-level project registry at
  `~/.pstack/config.json`. Walks any user (tech or non-tech) through
  registering a repo + GitHub Project + per-Project defaults, so
  subsequent pstack skills can resolve "which project" without a cwd.
  Idempotent — re-running against an existing nickname offers update.
  Detects missing `gh` CLI and falls back to manual entry. Use when
  asked to "set up a project", "register a project",
  "/pstack-add-project", or "configure pstack for a new repo".
triggers:
  - /pstack-add-project
  - register a project
  - set up a project
  - configure pstack
  - add a project to pstack
allowed-tools:
  - Bash
  - Read
  - Write
  - Edit
  - AskUserQuestion
---

# pstack-add-project


## Preamble — pstack upgrade check

Before doing the skill's actual work, run:

```bash
"${CLAUDE_SKILL_DIR}/../../scripts/pstack-update-check.sh" 2>/dev/null || true
```

Silent unless a pstack upgrade is available. When it fires it prints one
line ("ℹ pstack vX is available...") that the user can act on at their
convenience — it does not block the rest of this skill.

Populate the user-level project registry at `~/.pstack/config.json`. See
`docs/architecture/project-registry.md` for the schema and resolution
chain this skill writes for.

**Audience:** every pstack user. Walks both tech and non-tech users
through registering a project. Never asks for an opaque ID the user
can't find from the GitHub UI.

**Requires GitHub MCP server** for repo validation. `gh` CLI is a
soft dependency — used to list Projects when present; falls back to
manual URL entry when absent.

## Args

`/pstack-add-project` — fully interactive.

`/pstack-add-project <nickname>` — skip the nickname prompt; jump to
detecting/confirming repo. If `<nickname>` already exists, treat as
update.

## Steps

### 1. Load or create the registry file

```bash
CONFIG_DIR="$HOME/.pstack"
CONFIG_FILE="$CONFIG_DIR/config.json"

mkdir -p "$CONFIG_DIR"
if [ ! -f "$CONFIG_FILE" ]; then
  cat > "$CONFIG_FILE" <<'JSON'
{
  "$schema_version": "0.3.0",
  "projects": {},
  "default": null
}
JSON
  echo "Created empty registry at $CONFIG_FILE"
fi
```

The file is the source of truth. Read it now and keep the parsed
object in working state through the rest of the flow.

### 2. Pick a nickname

If a nickname was passed as an arg, use it. Otherwise prompt:

> "What's a short nickname for this project? (lowercase, dashes ok —
> e.g., `clarity`, `pstack`, `acme-web`)"

Validate: `^[a-z0-9][a-z0-9-]*$`. Re-prompt on invalid input with the
rule above.

**If the nickname already exists in `projects`:** This is an update,
not an error. Tell the user:

> "`<nickname>` is already registered (repo: `<owner>/<repo>`). Update
> it? (y/n)"

If yes, prefill subsequent prompts with the existing values. Empty
input at any prompt keeps the current value.

### 3. Detect or ask for the repo

**If `cwd` is a git repo with a GitHub remote:**

```bash
REMOTE=$(git remote get-url origin 2>/dev/null || true)
# Parse owner/repo from https://github.com/<owner>/<repo>(.git) or
# git@github.com:<owner>/<repo>(.git)
```

Show the parsed `owner/repo` and ask:

> "Use this repo: `<owner>/<repo>`? (y/n)"

**Otherwise, prompt:**

> "Which repo? Enter `owner/repo` — e.g., `periant-llc/clarity`.
>
> *How to find this:* open the repo on github.com and copy the part
> after `github.com/`. The URL `https://github.com/periant-llc/clarity`
> means `owner/repo` is `periant-llc/clarity`."

Validate format (`^[^/]+/[^/]+$`), then call
**`mcp__github__search_repositories`** with `query: "<owner>/<repo>"` (or
`mcp__github__get_file_contents` on the repo root) to confirm the user
has access. On failure, surface the GitHub error verbatim and re-prompt
— don't paper over auth issues.

### 4. Pick a GitHub Project (optional)

Try to list Projects accessible to the active gh auth context:

```bash
if command -v gh >/dev/null 2>&1; then
  # Try org Projects first, then user Projects
  gh project list --owner "$OWNER" --format json 2>/dev/null \
    || gh project list --format json 2>/dev/null
fi
```

**If `gh` is present and returns Projects:** Use `AskUserQuestion` to
present them. Show `<title> (#<number>) — <url>` for each, plus
`(none — skip Project integration)` as the last option.

**If `gh` is missing OR returns no Projects:**

Surface a one-line note about why we couldn't enumerate (gh missing /
no Projects visible / API error), then offer a manual entry path:

> "Paste the Project URL — e.g.,
> `https://github.com/orgs/periant-llc/projects/5`. Press enter to
> skip Project integration.
>
> *How to find this:* on github.com, click `Projects` in your org or
> profile, open the one you want, and copy the URL."

If the user pastes a URL, parse `owner` and `number` from it. Resolve
the Project's node id by calling `gh project view` (if available) or by
asking the user to paste the `PVT_...` id (with help text:
*"You can find this by running `gh project view <number> --owner
<owner> --format json` or by enabling the GitHub MCP's project tool."*).

**If `gh` is missing entirely** and the user can't supply a node id,
write the Project entry with `id: null` and surface:

> "Saved without the Project node id. Install `gh` CLI and re-run
> `/pstack-add-project <nickname>` to fill it in. Issue filing will
> still work; Project auto-population will not."

This keeps the skill working end-to-end without `gh`.

### 5. Defaults — only for single-select fields

If a Project was picked (and we have its id), fetch its field schema.
Via `gh`:

```bash
gh project field-list <number> --owner <owner> --format json
```

For each `ProjectV2SingleSelectField`, ask:

> "Default `<Field Name>`? Options: `<opt-1>`, `<opt-2>`, … Press enter
> to skip."

Only write defaults the user picks an option for. Skip free-text,
date, number, and iteration fields entirely — defaults are out of
scope for those in v0.3.

If `gh` is not available, skip this step. Tell the user:

> "Skipped defaults — needs `gh` CLI to enumerate Project fields.
> Re-run after installing `gh` to add them."

### 5.5. Structural posture — ask, don't assume

Pstack is right-sized to each project. Ask three explicit yes/no
questions and store the answers in the entry's `structure` block. See
[`docs/architecture/project-registry.md`](../../../../docs/architecture/project-registry.md)
for the full schema and consumer behavior.

Use one `AskUserQuestion` per dimension (or a single multi-select if the
host supports it):

> 1. **Does this project use GitHub Milestones for strategic goals**
>    (e.g., "MVP", "Q3 push")? — `use_milestones`
> 2. **Does this project track sprints via the Project's Iteration
>    field**? — `use_iterations`
> 3. **Does this project use multi-sprint Epics**? Only `yes` if scope
>    genuinely spans sprints; small projects file work as standalone
>    User Stories instead. — `use_epics`

Default for each: `false`. Frame it as "say yes only if you actually
use this," not "say no if you don't know."

**Inference shortcut on re-runs:** if updating an existing entry that
already has a `structure` block, prefill each prompt with the existing
value. Empty input keeps the current value.

**Inference shortcut from the Project schema:** when a Project was
picked in Step 4 and its field list is available, you may infer:

- No Iteration field present → suggest `use_iterations: false` as the
  default answer (user can override).
- No "Milestone" single-select field present → no behavior change
  (Milestones are a GitHub-repo feature, not a Project field).
- No Epic-friendly indicator → no inference (Epic posture is an
  intent question, not a schema one).

Inference is a hint, not a substitute for asking on first-run.

The resulting block lands in the entry as:

```json
"structure": {
  "use_milestones": <bool>,
  "use_iterations": <bool>,
  "use_epics": <bool>
}
```

### 6. Set as default?

Check the registry's `default` field after the new entry is added.

- **If no default is currently set:** ask "Set `<nickname>` as your
  default project? (Y/n)" — default Y.
- **If a different nickname is the current default:** ask "`<current>`
  is currently your default. Replace with `<nickname>`? (y/N)" —
  default N. Don't quietly steal the default.
- **If the user is updating an existing entry that's already the
  default:** skip the question.

### 7. Write the file

Assemble the entry and write atomically:

```bash
# Write to a tempfile next to config.json, then rename
TMP=$(mktemp "$CONFIG_DIR/.config.json.XXXXXX")
# ...emit JSON to $TMP using jq...
mv "$TMP" "$CONFIG_FILE"
```

Use `jq` to merge — never hand-roll JSON edits:

```bash
jq --arg nick "$NICK" \
   --argjson entry "$ENTRY_JSON" \
   '.projects[$nick] = $entry' \
   "$CONFIG_FILE" > "$TMP"
```

If `default` was set/changed, apply that too:

```bash
jq --arg nick "$NICK" '.default = $nick' "$TMP" > "$TMP.2" && mv "$TMP.2" "$TMP"
```

Then `mv "$TMP" "$CONFIG_FILE"`.

**Atomic write is mandatory** — a crash mid-write must not corrupt the
registry.

### 8. Echo + next steps

Print the new (or updated) entry pretty-printed:

```bash
jq ".projects.\"$NICK\"" "$CONFIG_FILE"
```

Then a next-step hint:

> "Saved `<nickname>` → `<owner>/<repo>`<, Project #N if set>.
>
> Try it: `/pstack-issue --type=bug --project <nickname>` or just
> `/pstack-issue` if this is your default."

## Idempotency contract

Re-running the skill against an existing nickname:

- **Never errors.** Update flow always available.
- **Never drops fields silently.** Each prompt prefills with the
  existing value; empty input keeps it. This applies equally to the
  `structure` block — re-runs show the existing booleans as the
  pre-filled answer.
- **Atomic on success.** A failed write leaves the previous file
  untouched.
- **Backfills missing blocks on re-run.** An entry written before the
  `structure` block existed (or with the block missing) gets prompted
  on next run; user can accept the all-`false` default to leave it
  effectively unchanged.

## Resolution decisions this skill makes

This skill **writes** the registry. It does not consume the resolution
chain documented in `docs/architecture/project-registry.md` — that's
for `/pstack-issue`, `/pstack-plan`, `/pstack-brief`, and future
registry-aware skills.

## Do not

- Do not ask for opaque IDs (`PVT_...`, field IDs, option IDs) before
  trying to enumerate them via `gh` or `mcp__github__*`. Manual entry
  is a fallback, not the first ask.
- Do not write the registry without atomic rename. A partial write
  breaks every consuming skill.
- Do not silently overwrite an existing default. Always ask.
- Do not validate Project membership of single-select option values
  against stale cached field lists — re-fetch from `gh` when the user
  changes defaults.
- Do not call `gh auth login` on the user's behalf. If `gh` is present
  but unauthenticated, surface the error and let the user run
  `gh auth login` themselves.
