---
name: project
description: "Portfolio- and project-level work management -- orientation, intent routing, active projects, milestones, tasks, notes, and resume context -- via the elevasis-sdk project:* CLI."
argument-hint: "[create | list | work | add | update | status | note | milestone | task | delete] [args]"
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
---

# Project Management (Portfolio + Per-Project)

`/project` is the primary work-tracking entrypoint for the template. It covers:

- **Portfolio orientation** — bare `/project` invocation shows the landscape of active work and suggests next actions.
- **Intent routing** — on an opening message that reads as "do work", decide new-vs-resume and route accordingly.
- **New-work flow** — when the user starts multi-step work without a project, ask: create project, link existing, or standalone.
- **CRUD surface** — projects, milestones, tasks, notes, checklists via `elevasis-sdk project:*`.

> **Note:** the template no longer ships a separate `/work` skill. Project tasks are the unit of work; use `/project task:*` operations for task lifecycle. Resume context is canonical in `prj_tasks.resume_context` and written via `project:task:save` (never via task-doc frontmatter).

**Usage:**

- `/project` — **Portfolio orientation** (see "Orientation Mode" below). List active/blocked projects, surface most-recently-touched project's `resume_context`, suggest next actions.
- `/project list` — Same as no-args orientation but just the project table, no suggestions.
- `/project work <query>` — Fuzzy-match a project/task and print a resume brief (wraps `project:work`).
- `/project status [<client>]` — Detailed status across all projects, or a single project.
- `/project create` — Guided project creation (QnA walkthrough).
- `/project add "Client Name" [--status active] [--value 5000]` — Quick create (flags only, no QnA).
- `/project update <client> --status on_track` — Update project fields.
- `/project milestone add <client> "Phase 1" [--due 2026-05-01]` — Add milestone.
- `/project milestone update <client> "Phase 1" --status completed` — Update milestone.
- `/project task add <client> "API docs" --milestone "Phase 1" [--type documentation]` — Add task.
- `/project task update <client> "API docs" --status approved` — Update task.
- `/project task save <task> --current-state "..."` — Persist `resume_context` (use this, never task-doc frontmatter).
- `/project note <client> "Kickoff call went well" [--type call_note]` — Add note.
- `/project notes <client>` — List notes for project.
- `/project checklist <client> "<milestone>"` — View checklist for a milestone.
- `/project delete <client>` — Delete project (cascades milestones, tasks, notes).

---

## Ambient Vibe Integration

This skill is the landing point for three of the seven vibe intent types. Agents arriving from the ambient layer should behave identically to a direct invocation — vibe is a classifier, not a different code path.

| Vibe intent    | What vibe detected                        | What to do here                                                                                           |
| -------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Capture**    | "add a task", "remember to", "track this" | Draft the task/note, confirm with user, then `project:task:create` or `project:note:create`               |
| **Transition** | "done", "stuck", "blocked", "finished"    | Resolve current task from session context, confirm status, then `project:task:update --status <new>`      |
| **Navigate**   | "focus on", "switch to", "back to"        | Resolve target via `project:resolve <query>` or `project:work <query>`, update scope, narrate new context |

**The full continuity loop** — how work flows across sessions:

```
vibe (Capture) → /project task:create → task in DB
                         ↓
                    work happens
                         ↓
              /save → project:task:save → prj_tasks.resume_context
                         ↓
              next session: /project work <query>
                         ↓
              project:work → resume brief (current state + next steps)
                         ↓
              agent picks up exactly where it left off
```

When a session begins with an ambiguous opening, check `project:work` before asking the user to re-explain. The resume brief is the canonical continuity payload — trust it.

---

## Prerequisites

**Run from the project root** (the directory containing `.elevasis`). Before issuing any other commands, run:

```bash
pnpm elevasis-sdk doctor
```

If `doctor` fails, **stop immediately and surface the error** — do not retry other commands. Fix the reported issue first (missing `.env`, bad API key, wrong directory, etc.), then re-run `doctor` before proceeding.

---

## Invocation Contract

All `elevasis-sdk` commands in this skill use the wrapper script form:

```bash
pnpm elevasis-sdk <subcommand> [flags]
```

This form is available from the **project root** — the directory that contains the `.elevasis` marker file. The `.elevasis` file is the project-root anchor: `elevasis-sdk` walks up from its invocation directory until it finds this file, then resolves `.env` and all relative paths from that location. After the project-root refactor, CWD within the project tree matters less — but you must still be somewhere inside the project directory tree.

The long form `pnpm -C operations exec elevasis-sdk <subcommand>` still works and is equivalent. Use the short wrapper form (`pnpm elevasis-sdk`) in all new automation and agent scripts.

**If the `.elevasis` marker is missing**, the CLI exits with:

```
Not inside an Elevasis project. Run this command from a project directory (an Elevasis project has a .elevasis file at its root).
```

If you see this error, confirm you are inside a project that was created from the template and has the `.elevasis` marker at its root.

---

## Transient Input Files (`tmp/`)

Agents that need to pass structured JSON to `request:submit` or `exec` should write the file to `<projectRoot>/tmp/` rather than to an arbitrary path. The `tmp/` directory is tracked in git (via `tmp/.gitkeep`) but its contents are gitignored, so transient files never pollute the commit history.

**Workflow:**

1. Write the input payload to `tmp/<descriptive-name>.json` from the project root.
2. Pass the path to the CLI command using the relative form — it resolves against the project root automatically:

   ```bash
   pnpm elevasis-sdk request:submit -f tmp/request-report.json --cleanup-input
   pnpm elevasis-sdk exec my-workflow -f tmp/exec-payload.json --cleanup-input
   ```

3. The `--cleanup-input` flag deletes request/exec input files automatically after a successful command. On failure the file is left intact for inspection.

**Safety guard:** `--cleanup-input` only deletes files that are under `<projectRoot>/tmp/`. If the resolved path is outside `tmp/`, the CLI prints a warning to stderr and leaves the file untouched. Files passed via inline JSON flags are never affected.

Checklist commands in the currently published SDK use inline `--checklist` JSON only. In Windows PowerShell 5.1, inline JSON is not reliable; use bash/PowerShell 7 for those updates, or update the checklist through the UI.

---

## Orientation Mode (bare `/project` invocation)

When invoked without a subcommand, enter **project mode**: present the portfolio landscape and offer next actions. This is the hub — distinct from `project:work <id>`, which is single-task resume.

### Flow

1. **List active work** — `--status` accepts exactly one value. Run two calls and merge the results:

   ```bash
   pnpm elevasis-sdk project:list --status active --pretty
   pnpm elevasis-sdk project:list --status blocked --pretty
   ```

   Show a compact table: client, status, last-touched.

2. **Surface most-recently-touched project's resume context** — identify the top row (most recent `updated_at`), then print its latest task's `resume_context` (via `project:task:list` → pick most recent in-progress task → `project:task:resume <id> --pretty`). Keep it short: current state + next steps, no files dump.

3. **List available operations** — a one-liner reminder of what the user can do from here:

   ```
   Available: /project status <client> | /project work <query> | /project task add | /project note | /project create
   ```

4. **Offer next-action suggestions** — 1-3 concrete follow-ups driven by what's on screen. Examples:
   - "3 active projects, 1 blocked (Acme — waiting on credentials). Resume Beta (most recent)?"
   - "No active projects. Create one with `/project create`?"
   - "Acme's Phase 2 has 2 overdue tasks. Open status?"

### Presentation template

```
Project Portfolio (2 active, 1 blocked)
=======================================
| Client       | Status   | Last touched |
|--------------|----------|--------------|
| Beta LLC     | on_track | 2h ago       |
| Acme Corp    | blocked  | 1d ago       |
| Gamma Inc    | active   | 4d ago       |

Most recent: Beta LLC — API integration
  Current: Endpoint wired, tests passing locally.
  Next:    Deploy to staging, verify webhook delivery.

Next? Resume Beta | Status Acme (why blocked?) | Create new project
```

---

## Intent Detection (opening-message routing)

Apply this heuristic on the user's first substantive message of a session, before branching into any other operation. The goal: distinguish **resume existing work** from **start new work**, and confirm before committing.

### Signals → classification

| Signal in opening message                                                                       | Classify as                        |
| ----------------------------------------------------------------------------------------------- | ---------------------------------- |
| References a specific file path, PR, commit, or existing task/milestone/project by name         | **Resume**                         |
| "Continue", "pick up", "finish", "the thing we were doing", "yesterday's work"                  | **Resume**                         |
| "Let's build X", "new Y", "I want to add Z", "start a new project", "help me with a new client" | **New work**                       |
| Single-file cosmetic ask: "fix typo in X", "rename Y", "update dep Z"                           | **Trivial** (skip new-work prompt) |
| Ambiguous / discussion only                                                                     | Ask before routing                 |

### Routing

- **Resume** — run `project:work <query>` with the extracted entity (file path stem, project name, task keywords). If one clean match, print the brief and ask "Continue here?". If multiple matches, show top 3 and ask which. If zero matches, fall back to orientation mode with a note ("No match for 'acme webhook'; here's the landscape").
- **New work** — enter the **New-Work Project Decision** flow (next section).
- **Trivial** — proceed directly; do not prompt about projects. Small single-file edits don't need portfolio tracking.
- **Ambiguous** — reply with orientation mode output and ask "Resume something, or start new?"

**Always confirm before branching.** A misclassified "new" routed as "resume" burns trust faster than a one-line confirmation.

---

## New-Work Project Decision

When intent detection classifies the opening ask as **new multi-step work** and the conversation has no established project context, ask this question before writing any code:

```
Scope this work under a project?
  a) Create new project "<proposed-slug>"     (recommended for multi-step work)
  b) Link to existing "<closest-match>"       (if fuzzy-search finds one)
  c) Standalone — no project tracking         (one-off / trivial)
```

### Rules

- **Default to (a)** when the ask is multi-step (multiple files, multiple sessions expected, client-facing deliverable).
- **Offer (b)** only when `project:list` fuzzy-matches produce a plausible candidate (name similarity, shared entity references). If nothing matches, omit the option — do not invent one.
- **Skip the prompt entirely** for trivial single-file edits — intent detection's "Trivial" branch handles this.
- **Propose a slug** by extracting the core noun from the ask. "Build a lead scraper for Acme" → `acme-lead-scraper`. User can override.

### After the decision

- **(a) Create** — invoke the guided `/project create` QnA flow (see below), pre-filling the name/description from the ask.
- **(b) Link** — run `project:resolve <query>` to confirm the match, then add a task to that project for the new work via `project:task:create`.
- **(c) Standalone** — proceed without project tracking. Do not create orphan task docs; standalone work leaves no artifact.

---

## Connection

### Environment

```bash
# .env (project root)
ELEVASIS_PLATFORM_KEY=sk_...
```

`ELEVASIS_PLATFORM_KEY` is read from the environment automatically. The SDK CLI walks up
directories to find `.env`, so it resolves correctly from both the project root and
`operations/`. `ELEVASIS_API_URL` defaults to `https://api.elevasis.io` (production). Set
`ELEVASIS_API_URL=http://localhost:5170` to target a local API instance, or set
`NODE_ENV=development` if the SDK CLI respects that convention.

No `--prod` flag is needed — the template targets production by default via `ELEVASIS_API_URL`.

### CLI Invocation

All project operations go through the `elevasis-sdk` CLI. Use the wrapper script form from the
project root:

```bash
pnpm elevasis-sdk project:<command> [args]
```

The long form `pnpm -C operations exec elevasis-sdk project:<command> [args]` is equivalent and
still works — use it if the wrapper script is not yet available in an older project.

Organization scoping is handled server-side via `ELEVASIS_PLATFORM_KEY`. No org header or
`--org` flag is required.

---

## Database Schema

### Tables

| Table            | Purpose                      | Key Columns                                                           |
| ---------------- | ---------------------------- | --------------------------------------------------------------------- |
| `prj_projects`   | Client projects/contracts    | name, kind, status, description, contract_value, start/end dates      |
| `prj_milestones` | Phases within projects       | name, status, due_date, sequence, completed_at, checklist             |
| `prj_tasks`      | Work items within milestones | name, status, type, due_date, milestone_id, resume_context, checklist |
| `prj_notes`      | Meeting notes, updates       | type, content, summary, occurred_at, task_id, milestone_id            |

### Project Kind

| Kind                | Description                      |
| ------------------- | -------------------------------- |
| `client_engagement` | External client delivery project |
| `internal`          | Internal project work            |
| `research`          | Research and investigation       |
| `other`             | Catch-all                        |

Default filter for `/project` operations is `kind=client_engagement` unless the user's intent
is clearly internal or `--kind` is specified explicitly.

### Status Values

| Entity    | Values                                                                                                                   |
| --------- | ------------------------------------------------------------------------------------------------------------------------ |
| Project   | `active`, `on_track`, `at_risk`, `blocked`, `completed`, `paused`                                                        |
| Milestone | `upcoming`, `in_progress`, `completed`, `overdue`, `blocked`                                                             |
| Task      | `planned`, `in_progress`, `blocked`, `completed`, `cancelled`, `submitted`, `approved`, `rejected`, `revision_requested` |
| Note type | `call_note`, `status_update`, `issue`, `blocker`, `agent_learning`                                                       |
| Task type | `documentation`, `code`, `report`, `design`, `refactor`, `feature`, `bug`, `research`, `other`                           |

### Checklist Shape

Both `prj_milestones.checklist` and `prj_tasks.checklist` store a JSONB array of objects with this shape:

```json
[
  { "id": "uuid", "label": "Item label", "completed": false },
  { "id": "uuid", "label": "Another item", "completed": true }
]
```

The CLI `--checklist` flag on `project:task:create`, `project:task:update`, and `project:milestone:update` performs a **full replace** where the command accepts checklist data. The entire array is replaced with the JSON you supply. There are no item-level add/toggle/remove flags. In Windows PowerShell 5.1, inline JSON is not reliable; use bash/PowerShell 7 for checklist CLI updates, or update the checklist through the UI. To mutate a single item:

1. Read the current checklist (via psql or `project:task:get` / `project:milestone:list`)
2. Mutate the array in memory (append, flip `completed`, filter out)
3. Write the entire array back via `--checklist '<json-array>'`

To clear a checklist: `--checklist '[]'`.

### Foreign Keys

- `prj_milestones.project_id` → `prj_projects.id` (CASCADE)
- `prj_tasks.project_id` → `prj_projects.id` (CASCADE)
- `prj_tasks.milestone_id` → `prj_milestones.id` (SET NULL)
- `prj_tasks.parent_task_id` → `prj_tasks.id` (SET NULL) — for subtasks
- `prj_notes.project_id` → `prj_projects.id` (CASCADE)
- `prj_notes.task_id` → `prj_tasks.id` (SET NULL)
- `prj_notes.milestone_id` → `prj_milestones.id` (SET NULL)
- `prj_projects.deal_id` → `acq_deals.id` (SET NULL)
- `prj_projects.client_id` → `clients.id` (SET NULL) — clients-hub link; set via `--client`
- `prj_projects.client_company_id` → `acq_companies.id` (SET NULL) — legacy direct-company link; set via `--client-company-id`

---

## Operations

### `list` — Portfolio Table (no orientation extras)

For a plain project table without resume context or next-action suggestions (use the bare `/project` orientation mode above when the user wants the full hub experience):

```bash
pnpm elevasis-sdk project:list --kind client_engagement --pretty

# Filter by client (accepts the client's name or UUID)
pnpm elevasis-sdk project:list --client "Acme" --pretty
```

Present as:

```
Project Portfolio
=================
| Client             | Status   | Kind               | Value   |
|--------------------|----------|--------------------|---------|
| Acme Corp          | on_track | client_engagement  | $5,000  |
| Beta LLC           | active   | client_engagement  | $3,500  |
```

For progress detail (milestones/tasks), follow up with `project:milestone:list` and
`project:task:list` per project.

### `work <query>` — Resume Brief

Wraps `project:work` for fuzzy-matched resume. Used by intent-detection "resume" classification.

```bash
pnpm elevasis-sdk project:work <query>
```

Prints the matched project/task brief with current `resume_context`. If multiple matches, show top 3 and ask which.

### `status [<client>]` — Detailed Status

If client specified, show full detail for that project. If not, show all.

**Resolve the project first** (see Client Inference below), then:

```bash
# Get project details
pnpm elevasis-sdk project:get <project-id>

# Milestones (ordered by sequence)
pnpm elevasis-sdk project:milestone:list --project <project-id> --pretty

# Tasks (grouped by milestone)
pnpm elevasis-sdk project:task:list --project <project-id> --pretty

# Recent notes
pnpm elevasis-sdk project:note:list --project <project-id> --pretty
```

Present as:

```
Acme Corp — On Track
=====================
Description: Automation project for AI-powered workflows
Contract: $5,000 | Started: 2026-04-01 | Target: 2026-06-01

Milestones (2/4 complete)
  [x] Phase 1: Discovery (completed)
  [>] Phase 2: Implementation (in_progress, due 2026-05-01)
  [ ] Phase 3: Testing (upcoming, due 2026-05-15)
  [ ] Phase 4: Launch (upcoming, due 2026-06-01)

Tasks (3/8 approved)
  Phase 2: Implementation
    [approved] Requirements doc (documentation)
    [in_progress] API integration (code, due 2026-05-01)
    [planned] User guide (documentation)

Recent Notes
  2026-04-05 [call_note] Weekly check-in — on track, API work started
  2026-04-01 [call_note] Kickoff — confirmed scope and timeline
```

#### Tenant Skill Bindings Footer

After the normal status output, append a short footer when the project kind, tags, name,
description, milestones, or active tasks map to a known tenant domain such as `prospecting`,
`crm`, `outreach`, `finance`, or `support`.

Resolve the best domain from explicit metadata first (`tags`, `kind`, domain fields), then from
project/task text. If no clear domain maps, omit the footer.

Footer shape:

```text
Related skill bindings
- Domain: <domain>
- Read or change business profile: /om <domain>
- Layering primer: node_modules/@elevasis/sdk/reference/spine/spine-primer.md
```

External projects do not expose monorepo-only architecture commands. Never suggest `/org-os`,
`/om-spine`, or monorepo paths in this footer.

### `create` — Guided Project Creation (QnA Flow)

Interactive walkthrough that collects project details step by step using `AskUserQuestion`,
then creates the project, milestones, and kickoff note in one go.

**Step 1: Project basics**

Ask via `AskUserQuestion` (single question, free-text via "Other"):

```
What's the project name and a brief description of the scope?
```

The user will reply with something like "Acme Corp automation -- building a lead gen pipeline
with email outreach". Parse the name and description from the response.

**Step 2: Deal/company linking**

Search for matching records using read-only psql (the psql role is SELECT-only).

Note: `SUPABASE_READONLY_URL` must be set in the template `.env` for psql queries. If not
set, skip this step and advise the user to link via the Command Center UI after creation.

If `SUPABASE_READONLY_URL` is available, query using `<ORG_ID>` from the platform key's
associated organization (you can retrieve it via `project:list` output which includes
`organization_id`):

```bash
source .env && psql "$SUPABASE_READONLY_URL" -c \
  "SELECT id, name, domain FROM acq_companies WHERE organization_id = '<ORG_ID>' AND name ILIKE '%<parsed_name>%' ORDER BY updated_at DESC LIMIT 5;"

source .env && psql "$SUPABASE_READONLY_URL" -c \
  "SELECT d.id, d.contact_email, d.cached_stage, c.name AS company_name
   FROM acq_deals d
   LEFT JOIN acq_contacts ct ON ct.id = d.contact_id
   LEFT JOIN acq_companies c ON c.id = ct.company_id
   WHERE d.organization_id = '<ORG_ID>'
     AND (d.contact_email ILIKE '%<parsed_name>%' OR c.name ILIKE '%<parsed_name>%')
   ORDER BY d.updated_at DESC LIMIT 5;"
```

If matches found, ask via `AskUserQuestion`:

```
Found matching records. Link this project?
- [Company: Acme Corp (acme.com)]
- [Deal: john@acme.com (closed_won)]
- No linking
```

If no matches, skip silently.

**Step 3: Contract details**

Ask via `AskUserQuestion`:

```
Contract details?
- Options with previews showing common structures:
  - "Fixed project" -- one-time fee
  - "Monthly retainer" -- recurring
  - "Hourly" -- time & materials
  - Other
```

Then follow up for the value, start date, and target end date. These can be collected in a
single question:

```
What's the contract value, start date, and target end date?
(e.g., "$5000, starting today, targeting June 1")
```

Parse natural language dates ("today", "next Monday", "June 1") into ISO format.

**Step 4: Milestone suggestions**

Based on the project description from Step 1, suggest 3-5 milestones that fit the scope. Use
your understanding of the project to propose relevant phases.

**Guidelines for milestone suggestions:**

- For automation/integration projects: Discovery, Implementation, Testing, Launch
- For content/marketing projects: Strategy, Content Creation, Review, Distribution
- For consulting/advisory: Assessment, Recommendations, Implementation Support
- Always include a Discovery/Kickoff phase first and a Launch/Handoff phase last
- Suggest due dates spaced evenly between start and target end date

Present via `AskUserQuestion` with previews:

```
Suggested milestones for "<project name>". Select which to include:
(multiSelect: true)

- Discovery (due <start + 2 weeks>)
- Implementation (due <midpoint>)
- Testing (due <end - 2 weeks>)
- Launch (due <end date>)
```

The user can select which ones to keep, modify via "Other", or skip entirely.

**Step 5: Kickoff note**

Ask via `AskUserQuestion`:

```
Add a kickoff note? This captures initial context (scope confirmed, key contacts, etc.)
- Yes, let me type one
- Skip for now
```

If yes, collect the note content.

**Step 6: Execute**

Create everything in sequence using the CLI:

```bash
# 1. Create project
# If the user mentioned a client by name, resolve it first:
#   pnpm elevasis-sdk client:resolve "<client name>"
# Then pass the name (or UUID) via --client
pnpm elevasis-sdk project:create \
  --name "<name>" \
  --kind client_engagement \
  --status active \
  --description "<desc>" \
  --client "<client-name-or-uuid>"

# 2. Create milestones
pnpm elevasis-sdk project:milestone:create \
  --project <project-id> \
  --name "<milestone-name>" \
  --status upcoming \
  --due-date <date>

# 3. Create kickoff note (if provided)
pnpm elevasis-sdk project:note:create \
  --project <project-id> \
  --content "<note-content>" \
  --type call_note
```

If `--company` or `--deal` linking was confirmed, use `project:update` after creation:

```bash
pnpm elevasis-sdk project:update <project-id> --client "<client-name-or-uuid>"
```

To remove a client link later:

```bash
pnpm elevasis-sdk project:update <project-id> --clear-client
```

Do NOT pass `--client ""` to clear — an empty string is rejected as ambiguous. Use `--clear-client` instead.

**Step 7: Summary**

Show the created project:

```
Project Created
===============
Name: Acme Corp Automation
Kind: client_engagement
Status: active
Timeline: 2026-04-07 → 2026-06-01

Milestones:
  1. Discovery (upcoming, due 2026-04-21)
  2. Implementation (upcoming, due 2026-05-08)
  3. Testing (upcoming, due 2026-05-22)
  4. Launch (upcoming, due 2026-06-01)

Kickoff Note: Confirmed scope -- lead gen pipeline with email outreach.

Next: /project status acme
```

---

### `add "<name>" [options]` — Quick Create Project (No QnA)

**Options:**

- `--status <status>` (default: `active`)
- `--value <number>` — contract value (informational, stored in description or notes)
- `--start <date>` — start date
- `--end <date>` — target end date
- `--description "<text>"` — scope description
- `--kind <kind>` (default: `client_engagement`)

```bash
pnpm elevasis-sdk project:create \
  --name "<name>" \
  --kind client_engagement \
  --status active \
  --description "<desc>"
```

**After creating:** Show the created project and suggest next steps (add milestones).

### `update <client> [options]` — Update Project

**Options:** `--status`, `--name`, `--description`, `--client <id-or-name>`, `--clear-client`, `--client-company-id <uuid>`

First resolve the client (see Client Inference below), then:

```bash
pnpm elevasis-sdk project:update <project-id> --status <status>
```

### `milestone add <client> "<name>" [options]` — Add Milestone

**Options:**

- `--status <status>` (default: `upcoming`)
- `--due <date>` — due date (ISO)
- `--description "<text>"`

```bash
pnpm elevasis-sdk project:milestone:create \
  --project <project-id> \
  --name "<name>" \
  --status upcoming \
  --due-date <date>
```

### `milestone update <client> "<milestone>" [options]` — Update Milestone

Resolve milestone by name (see Client Inference for project resolution, then use
`project:milestone:list` to find the milestone ID by partial name match):

```bash
# List milestones to find ID
pnpm elevasis-sdk project:milestone:list --project <project-id>

# Update status
pnpm elevasis-sdk project:milestone:update <milestone-id> --status completed

# Update checklist (full replace)
pnpm elevasis-sdk project:milestone:update <milestone-id> \
  --checklist '[{"id":"1","label":"Review scope doc","completed":false}]'
```

If status changes to `completed`, the API auto-sets `completed_at`.

**Options:** `--status`, `--due-date`, `--name`, `--checklist <json>`

**Checklist mutations:** The CLI performs a full replace. To add, toggle, or remove an item:

1. Read the current checklist via psql or `project:milestone:list`
2. Mutate the array in memory
3. Write back the full array via `--checklist '<json-array>'`

### `checklist <client> "<milestone>"` — View Checklist

Resolve the milestone, then read its `checklist` JSONB column via read-only psql.

Note: Requires `SUPABASE_READONLY_URL` in the template `.env`. If not set, advise the user
to view the checklist via the Command Center UI, or add `SUPABASE_READONLY_URL` to `.env`.

```bash
source .env && psql "$SUPABASE_READONLY_URL" -c \
  "SELECT m.name, m.checklist
   FROM prj_milestones m
   WHERE m.project_id = '<project_id>' AND m.name ILIKE '%<name>%'
   LIMIT 1;"
```

The `checklist` column is a JSONB array of objects:

```json
[
  { "id": "uuid", "label": "Set up dev environment", "completed": true },
  { "id": "uuid", "label": "Draft scope document", "completed": false }
]
```

Present as:

```
Onboarding & Scope — Checklist (2/3 complete)
  [x] Set up dev environment
  [x] Schedule kickoff call
  [ ] Draft scope document
```

### `task add <client> "<name>" [options]` — Add Task

**Options:**

- `--milestone "<name>"` — link to a milestone (resolve by name first)
- `--status <status>` (default: `planned`)
- `--type <type>` (default: `other`)
- `--due <date>`
- `--description "<text>"`
- `--checklist <json>` — initial checklist items

```bash
# Resolve milestone ID first if --milestone provided
pnpm elevasis-sdk project:milestone:list --project <project-id>

# Create task
pnpm elevasis-sdk project:task:create \
  --project <project-id> \
  --title "<name>" \
  --status planned \
  --type <type> \
  --milestone <milestone-id>

# Create task with initial checklist
pnpm elevasis-sdk project:task:create \
  --project <project-id> \
  --title "<name>" \
  --checklist '[{"id":"1","label":"Review scope doc","completed":false}]'
```

### `task update <client> "<task>" [options]` — Update Task

Resolve task by name within the project using `project:task:list`, then update by positional ID:

```bash
# Find the task ID
pnpm elevasis-sdk project:task:list --project <project-id>

# Update status (positional <id> comes first, then flags)
pnpm elevasis-sdk project:task:update <task-id> --status <status>

# Update checklist (full replace)
pnpm elevasis-sdk project:task:update <task-id> \
  --checklist '[{"id":"1","label":"Review scope doc","completed":false}]'

# Clear checklist
pnpm elevasis-sdk project:task:update <task-id> --checklist '[]'
```

**Options:** `--status`, `--title`, `--milestone`, `--description`, `--checklist <json>`

**Important:** The task ID is a **positional argument** — it comes immediately after `project:task:update`, before any flags. There is no `--task-id` flag.

If status changes to `approved`, the API auto-sets `completed_at`.

**Checklist mutations:** Same full-replace semantics as milestones. To add, toggle, or remove an item:

1. Read the current task via `project:task:get <task-id>`
2. Mutate the checklist array in memory
3. Write back the full array via `project:task:update <task-id> --checklist '<json-array>'`

### `note <client> "<content>" [options]` — Add Note

**Options:**

- `--type <type>` (default: `call_note`) — see Note Type Selection below
- `--task <task-id>` — attach to a specific task (UUID)
- `--milestone <milestone-id>` — attach to a milestone (UUID)

```bash
pnpm elevasis-sdk project:note:create \
  --project <project-id> \
  --content "<content>" \
  --type <type>

# Attach to a task
pnpm elevasis-sdk project:note:create \
  --project <project-id> \
  --content "<content>" \
  --type agent_learning \
  --task <task-id>
```

### `notes <client>` — List Notes

```bash
pnpm elevasis-sdk project:note:list --project <project-id> --pretty
```

### `delete <client>` — Delete Project

**ALWAYS confirm before deleting.** Show what will be deleted (milestone count, task count,
note count) using the list commands, then ask for confirmation.

```bash
pnpm elevasis-sdk project:delete <project-id>
```

Cascade deletes all milestones, tasks, and notes.

---

## Notes Management

Notes (`prj_notes`) are the durable record of project-scoped signals — conversation outcomes, blockers, status updates, and agent-captured learnings. They complement `resume_context` (which is task-scoped, temporal, and agent-facing) by providing a human-readable audit trail.

### When to Create a Note vs. Fall Through to Global Auto-Memory

**Create a project note** when all of the following hold:

- A specific project and (optionally) task is in scope in the current session
- The content is project-bound — it would only matter to someone working on this project
- The content is durable — it should be visible in future status views, not just the current session

**Fall through to global auto-memory** when:

- No active project or task can be resolved from session context (see Project-Scope Resolution below)
- The content is cross-project or platform-wide (e.g., a general TypeScript pattern, a reusable workflow insight)
- The signal is ephemeral and not worth a DB write (e.g., "reminder: run linter before committing")

When in doubt and no project context is resolvable, global auto-memory is the safe default.

### Note Type Selection Guide

| Type             | Use when                                                                                                                                                                                                                                                   |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `call_note`      | Transcribing or summarizing a live client / stakeholder call. Include key decisions and action items.                                                                                                                                                      |
| `status_update`  | Milestone-level progress worth flagging to a human operator: phase complete, substantial deliverable, direction change. Don't emit one every session turn.                                                                                                 |
| `issue`          | Bug, regression, unexpected failure, contract mismatch. Include reproduction context.                                                                                                                                                                      |
| `blocker`        | Work cannot proceed without external action: decision needed, credential missing, upstream fix required.                                                                                                                                                   |
| `agent_learning` | Project-bound knowledge the agent discovered: API quirks, client conventions, deploy gotchas, rate limits. Captures "things a future agent resuming this project should know" so it travels with the project instead of living only in global auto-memory. |

### Project-Scope Resolution

Before writing a note, the agent resolves which project (and optionally task) to attach it to:

1. **Explicit in session** — a project/task UUID was mentioned or returned by a recent `project:work <id>` or `project:task:*` invocation in this session. Use it directly.
2. **Implicit from task list** — if step 1 yields nothing, run (`--status` accepts one value; run both calls and merge):

   ```bash
   pnpm elevasis-sdk project:list --status active --pretty
   pnpm elevasis-sdk project:list --status blocked --pretty
   pnpm elevasis-sdk project:task:list --project <top-project-id> --status in_progress
   ```

   Use the most-recently-touched project's in-progress task if there is exactly one. If multiple in-progress tasks exist, prefer the one that most closely matches the current conversation topic.

3. **Prompt the user** — if steps 1 and 2 are both ambiguous or empty, ask once: "Which project should I attach this note to? (project UUID or slug, or 'skip' to use global auto-memory)."

4. **Fall through** — if the user says "skip" or no resolution is possible, write to global auto-memory and continue.

Resolution is **agent-side at call time** — there is no middleware or automatic routing layer. The agent infers context from the session.

### Agent Workflow: Capturing an `agent_learning`

When the agent recognizes a project-scoped learning (an API quirk, an undocumented constraint, a client convention discovered during implementation):

1. Recognize the signal — e.g., "Apify actor X rate-limits at 20 rps", "client requires ISO dates, not timestamps", "staging env needs VPN before webhook delivery works"
2. Resolve active project and task from session context (see above)
3. Write the note:

   ```bash
   pnpm elevasis-sdk project:note:create \
     --project <project-id> \
     --type agent_learning \
     --task <task-id> \
     --content "<concise, factual statement of what was learned>"
   ```

   Omit `--task` if no specific task is in scope (e.g., the learning is milestone-level or project-level).

4. If no project can be resolved, write the learning to global auto-memory instead.

**Content style for `agent_learning` notes:** write as a factual assertion another agent could act on — "Apify actor X rate-limits at 20 rps; add a 50ms delay between calls" rather than "I noticed that Apify seems slow." Keep it under 200 characters when possible.

---

## Status Transitions from Other Skills

The `/project` skill is the canonical reference for how lifecycle signals from other skills map to `project:task:update` status transitions. The behavioral hooks live in the emitting skills; this section documents the contract.

### Transition Map

| Signal                                   | Source skill   | Transition                             | CLI call                                           |
| ---------------------------------------- | -------------- | -------------------------------------- | -------------------------------------------------- |
| Successful `git push` via `/deploy`      | `deploy/`      | linked task → `submitted`              | `project:task:update <task-id> --status submitted` |
| `/save` catches "I'm stuck" / blocker    | `save/`        | linked task → `blocked` + blocker note | `project:task:update <task-id> --status blocked`   |
| User/agent says "I'm done" / "task done" | `/project` NLM | active task → `completed`              | `project:task:update <task-id> --status completed` |

### "I'm Done" Recognition (Natural Language Mode)

When the user or agent says any of the following in the context of a project task, treat it as a `completed` transition signal:

- "I'm done", "I'm finished", "done with this", "that's done"
- "task complete", "task finished", "mark as complete"
- "wrap this up", "close this task", "finalize this"

Resolve the active task from session context (see Project-Scope Resolution), confirm once ("Mark `<task name>` as completed?"), then fire:

```bash
pnpm elevasis-sdk project:task:update <task-id> --status completed
```

If no task resolves, ask the user which task to complete rather than silently skipping.

### Transition Rules (All Skills)

These rules apply to every skill that emits a transition:

- **Explicit task ID only** — use the UUID resolved from session context. Never guess.
- **Best-effort** — if the CLI returns non-2xx, emit a warning line and continue. Do not block the primary operation.
- **Idempotent** — if the task is already in the target status, the update is a no-op. Fire it anyway.
- **Confirm before `completed`** — always ask once before marking a task done, since this is hard to undo semantically.

For full implementation details, see the emitting skills: `deploy/SKILL.md` (Step 8) and `save/SKILL.md` (Steps 5, 5a).

---

## Client Inference

Resolve which project the user means using these rules (in priority order):

1. **Exact ID** — if the argument looks like a UUID, use it directly with `project:get`
2. **Name match** — list projects filtered by `client_engagement` kind, then match by name:
   ```bash
   pnpm elevasis-sdk project:list --kind client_engagement
   ```
   Filter results by partial name match against the user's input (case-insensitive).
3. **Single active project** — if only one non-completed `client_engagement` project exists,
   use it without asking
4. **Context from conversation** — if the conversation has been discussing a specific client,
   use that one
5. **Ambiguous** — if multiple match or none match, list projects and ask

---

## Natural Language Mode

When args don't match any command pattern, infer intent from natural language:

| User says                                                      | Inferred operation                                                                                                                                    |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| "how's acme doing"                                             | `status acme`                                                                                                                                         |
| "add a note: weekly call, everything on track"                 | `note <active-project> "weekly call, everything on track"`                                                                                            |
| "mark phase 2 complete for acme"                               | `milestone update acme "phase 2" --status completed`                                                                                                  |
| "approve the API docs task"                                    | `task update <project> "API docs" --status approved`                                                                                                  |
| "new client: Beta LLC, $3500/mo, starting may 1"               | `add "Beta LLC" --value 3500 --start 2026-05-01`                                                                                                      |
| "what's overdue?"                                              | List milestones where `due_date < now() AND status != 'completed'`                                                                                    |
| "block acme, waiting on client credentials"                    | `update acme --status blocked`                                                                                                                        |
| "show checklist for phase 2"                                   | Read and display checklist for milestone "phase 2" via psql or `project:milestone:list`                                                               |
| "add 'deploy staging' to task X's checklist"                   | Read task X's checklist → append `{id: uuid, label: "deploy staging", completed: false}` → `project:task:update <id> --checklist '<json-array>'` |
| "mark 'deploy staging' done on task X"                         | Read task X's checklist → flip `completed` on matching item → `project:task:update <id> --checklist '<json-array>'`                              |
| "clear the checklist on task X"                                | `project:task:update <task-id> --checklist '[]'`                                                                                                      |
| "add checklist item to onboarding milestone: review scope doc" | Read milestone checklist → append item → `project:milestone:update <id> --checklist '<json-array>'`                                      |
| "I'm done" / "task complete" / "done with this"                | Resolve active task → confirm → `project:task:update <task-id> --status completed`                                                                    |
| "save: Apify actor X rate-limits at 20rps"                     | Resolve active project/task → `project:note:create --project <id> --type agent_learning --task <task-id> "Apify actor X rate-limits at 20rps"`        |
| "remember: client requires ISO dates, not timestamps"          | Resolve active project/task → `project:note:create --project <id> --type agent_learning --task <task-id> "client requires ISO dates, not timestamps"` |

**Checklist note:** all checklist mutations use the read-modify-write pattern. The CLI has no item-level flags (`--add-item`, `--toggle`, `--remove-item` do not exist). Always read the current state, mutate the array, then write the full array back. Use bash/PowerShell 7 for generated checklist JSON; Windows PowerShell 5.1 native argument passing is not reliable for inline JSON.

---

## Bulk Operations

### Add Multiple Milestones

For creating a standard milestone set, run `project:milestone:create` in sequence:

```bash
for name in "Discovery" "Implementation" "Testing" "Launch"; do
  pnpm elevasis-sdk project:milestone:create \
    --project <project-id> \
    --name "$name" \
    --status upcoming
done
```

### Overdue Report

Query via read-only psql for milestones past their due date. Requires `SUPABASE_READONLY_URL`
in the template `.env`. Replace `<ORG_ID>` with the organization ID associated with your
`ELEVASIS_PLATFORM_KEY` (visible in `project:list` output as `organization_id`). If
`SUPABASE_READONLY_URL` is not configured, use `project:milestone:list` per project and filter
manually.

```bash
source .env && psql "$SUPABASE_READONLY_URL" -c \
  "SELECT
     p.name AS project,
     m.name AS milestone,
     m.due_date,
     m.status,
     (CURRENT_DATE - m.due_date::date) AS days_overdue
   FROM prj_milestones m
   JOIN prj_projects p ON p.id = m.project_id
   WHERE p.organization_id = '<ORG_ID>'
     AND m.due_date < CURRENT_DATE
     AND m.status NOT IN ('completed')
   ORDER BY m.due_date;"
```

---

## Client Linking

Projects can be linked to a client in the clients hub. When a user mentions a client by name, resolve it to an ID and use `--client` to wire the project.

### Two linking flags, two different columns

- `--client <name-or-uuid>` — links to a clients-hub record (`prj_projects.client_id`). Use this for new client linkage. Accepts a name (the CLI fuzzy-resolves it) or a UUID.
- `--client-company-id <uuid>` — legacy direct-company linkage (`prj_projects.client_company_id`); kept for back-compat. UUID only.

These are independent. Do not use `--client-company-id` when you mean `--client`.

### When the user mentions a client by name

1. Resolve the client name to a UUID first (optional but gives an explicit confirmation step):
   ```bash
   pnpm elevasis-sdk client:resolve "Acme"
   ```
2. Pass the name or UUID to `--client` — the CLI fuzzy-resolves names automatically:
   ```bash
   pnpm elevasis-sdk project:create --name "Acme Automation" --kind client_engagement --client "Acme"
   pnpm elevasis-sdk project:update <project-id> --client "Acme"
   ```
3. If the client name matches multiple records, the CLI returns an error listing candidates. In that case, run `client:resolve "Acme"` to disambiguate, then pass the UUID directly.

### Filtering projects by client

```bash
pnpm elevasis-sdk project:list --client "Acme" --pretty
```

### Removing a client link

```bash
pnpm elevasis-sdk project:update <project-id> --clear-client
```

`--client` and `--clear-client` are mutually exclusive on the same command call.

### Soft recommendation for client engagement projects

When the user creates a `client_engagement` project without mentioning a client, gently note that linking a client is recommended so the project appears in client lineage views. Do not block creation — the flag is optional.

### Client command reference

The full `client:*` surface (list, get, status, resolve) is available via `elevasis-sdk client:*`. The `client:resolve` command mirrors `project:resolve` in shape and is the canonical tool for name-to-ID translation.

---

## Safety Rules

1. **Always confirm deletes** — show what will be cascade-deleted before executing
2. **Organization scoping** — all API calls are automatically org-scoped via
   `ELEVASIS_PLATFORM_KEY`; every direct psql query MUST include an `organization_id` filter
   (use `<ORG_ID>` as a placeholder and confirm the value with the user if unknown)
3. **Read before write** — when updating, show current state before applying changes
4. **Validate status values** — reject invalid status strings before sending to the CLI
5. **Kind default** — default to `--kind client_engagement` when user intent is client
   delivery; use `--kind internal` for internal project work
6. **Template project assumption** — the template is a single-organization project. All
   `/project` operations operate within the organization scoped by `ELEVASIS_PLATFORM_KEY`.
   There is no cross-org capability.
7. **Checklist full-replace** — `--checklist` replaces the entire array. Always read
   the current checklist before writing to avoid losing existing items.
8. **Confirm task completion** — always ask once before marking a task `completed` via "I'm done"
   recognition. This status is semantically significant and warrants a confirmation step.

---

**Last Updated:** 2026-05-08
