project · git:20260915.8cd7829 · 2026-09-15 · sha256 7b409f97c5bc7315
project git:20260915.8cd7829A
Immutable. This exact content is served forever at /api/v1/blob/7b409f97c5bc7315.
---
name: project
description: This skill should be used when the user asks "which project", wants to "create a project", "start a new project", "switch project", "mark this project complete", "archive a project", "unregister a project", "install the task rule", or "uninstall AIDA from this repository". It works out which project owns the current directory, creates one, switches to another, ends one, or cleans one up, and runs the project check every time.
disable-model-invocation: true
argument-hint: "[create | switch <name-or-path> | list | state <name-or-path> <active|complete|archived> | set-code-path <name-or-path> [<new-code-path>] | set-frameworks <name-or-path> <framework>... | git-init <name-or-path> | add-source <name-or-path> <kind> <folder|catalog> | subscribe-playbook <name-or-path> <framework> <set-id> | unsubscribe-playbook <name-or-path> <framework> <set-id> | unregister <name-or-path> | task-rule <name-or-path> [--remove | --decline] | uninstall <name-or-path>]"
arguments: [action, target]
allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/skills/project/scripts/project-actions.sh *) Bash(${CLAUDE_PLUGIN_ROOT}/scripts/detect-framework.sh *)
---
# Project
A project ties one code path to AIDA's own work on it. This skill answers every question about
one: which project owns this directory, make one, switch to another, end one, or clean one up.
Read the argument once and follow the matching section below. Every section that changes what a
project is runs the check before it finishes and shows the whole report. Two do not, because
they change nothing the check reads: unregistering and uninstalling.
Every command below runs one of two scripts, `project-actions.sh` or `detect-framework.sh`. Both
are named in this skill's own grant, so they run without asking, in both run modes. Any other
Bash command, such as canonicalizing a path by hand, is not covered by that grant and still asks
for approval.
## Determine the run mode
A task states its own run mode. This skill carries none of its own.
Look for a stated run mode on the task active in this conversation. Found, and it says
`autonomous`: act autonomously through this whole invocation, passing `--run-mode autonomous`
on every call to the scripts below. Anything else, including no active task: act interactively,
the safe default. Decide this once, at the start, so nothing mid-flow has to ask again.
## No arguments: report
Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh report
```
Read the first line, `CASE: 1`, `CASE: 2`, or `CASE: 4`. This is ideal/project.md's "Picking up
work", in the order it names, case 3 being case 1 winning when both would otherwise apply.
The exit code is the check's at cases 1 and 2, and 0 at case 4.
**`CASE: 1` or `CASE: 2`.** A `project:` line follows, naming the project, its state, its code
path and its folder, then the check's own report. Show the report as described in "Reading the check's report" below. Stop here; this
already touched `lastAccessed` and, for case 2, this is the remembered choice winning because
nothing else answers.
A file named `reminders.md` beside `project.json` is printed at every session start, under a
line naming its path. A person writes it by hand, so a standing note for this project reaches
every window.
**`CASE: 4`.** Neither the directory nor a remembered choice resolves to a project. The output
then carries `DECLINED: true` or `DECLINED: false`, then zero or more `V5:` lines. Then comes
`PROJECTS:`, followed by one `project:` line per registered project, most recently used first
(empty when none are registered yet).
A `V5:` line names a version 5 folder under the projects base whose code path is this directory.
When no base is recorded yet, the scan looks under the base that version 5 recorded in
`~/.claude/ai-dev-assistant/active_projects.json`, so a first run finds them. When one or more
are present, offer the switch first, in one line: "This directory is the code
path of the version 5 project <folder>. Pick it up? (runs switch)". Yes runs "switch" below with
that folder. No falls through to the create offer below. Autonomously, do not ask: say the folder
exists and that `switch <folder>` picks it up, and continue.
- **`DECLINED: false`, interactive.** Offer to create a project here, in one line: setting one up
gives findings and decisions somewhere to live past this session. Wait for a plain yes or no.
- **Yes.** Run the "create" section below with no facts already known, so it proposes and asks
for whatever it needs.
- **No.** Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh record-declined "$(pwd -P)"
```
- **`DECLINED: false`, autonomous.** Nobody is present to answer, so do not ask and do not record
anything. Silence is not a decline: recording one would suppress the offer forever on the
strength of nobody having been there. Say that the directory is not set up, and continue. This
is what version 5 does, and it is the rule in the specification.
- **`DECLINED: true`.** Do not offer again. Only an explicit `create` overrides it.
Either way, once the offer is settled (or was never made), show the `PROJECTS:` list if it is
non-empty: "Here is what you have registered, most recent first," each project's name, code
path, and state. Ask which one to switch to, if any; picking one runs "switch" below with that
name. An empty list needs no further comment beyond having said the directory is not set up.
Autonomously, do not ask which one. Show the list, say that no project owns this directory and
that none was chosen, and continue. Choosing one for the person would bind every later step to a
guess, and no later step is blocked by having no project: the one that needs one says so.
## `create`
Creation needs three facts: the code path, the name, and the frameworks. It proposes what it
can and confirms; only the name and, when nothing can be proposed or detected, the code path and
the frameworks are asked outright.
**1. Code path.** Given explicitly (an argument, or already known from context): canonicalize it
by running `cd "<path>" 2>/dev/null && pwd -P`, then confirm it in one line before moving on
("Use `<path>` as the code path?"). Autonomous: accept it without asking, recording that this run
made its own confirmation.
Not given: run
```
"${CLAUDE_PLUGIN_ROOT}"/scripts/detect-framework.sh "$(pwd -P)"
```
Exit 0: the current directory looks like code. Propose it, naming what was detected, and confirm
the same way as an explicit path. Its output is also this step's framework detection; carry the
list forward into step 3 rather than detecting twice.
Exit 1 or 2: nothing to propose from here. Interactive: ask "Where does the code live?" and wait.
Autonomous: **halt.** Report that the code path is missing and stop; this is one of the three
facts creation cannot guess.
**2. Name.** Always asked, whether or not one was already mentioned, because deriving it from the
code folder saves one question and buys a collision problem the moment two projects share a
folder name (ideal/project.md, "Considered and rejected"). Check against
`^[a-z][a-z0-9_]*$`; on a mismatch, say so and ask again. Autonomous with no name given: **halt**,
report that the name is missing, stop.
**3. Frameworks.** Skip this step's own detection when step 1 already ran it against this exact
code path; otherwise, and only when the code path names a directory that exists, run
```
"${CLAUDE_PLUGIN_ROOT}"/scripts/detect-framework.sh "<codePath>"
```
Each line it prints is `<name>: <file>`, the framework and the file that proved it. Union the
names with any framework names already given or mentioned, keeping the given ones first. Nothing
given and nothing detected, or the code path does not exist yet: interactive asks "What is the
stack?"; autonomous **halts**, reporting that the frameworks are missing.
**4. The projects-folder base.** Run
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh read-projects-base
```
Exit 0: a base is already recorded. Use it silently; this is never asked a second time.
Exit 1, interactive: ask once, "Where should project folders be stored? Default:
`~/.claude/aida/projects`." Accept the default on an empty answer, or a typed absolute path.
Exit 1, autonomous: use the default silently; this is not one of the three facts that halts.
**5. Write it.** Once all four are known, run, with the run mode set as decided above:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
create --name "<name>" --path "<codePath>" --projects-home "<base>" \
--framework "<fw1>" [--framework "<fw2>" ...]
```
It writes every file, makes the first commit, adds the registry row, records the projects-home
base the first time only, and runs the check itself. Show the whole output.
Exit code 2 from the check here is normal, not a problem: the code path was just set from what
was confirmed, and a brand-new project can go without code for a while. Say so and move on.
Exit code 5 means the code path named a refused location (see "Reading the check's report"); the
script has already removed everything it wrote before reporting this, so nothing is left half
registered. Say why it was refused and ask for a different code path.
**6. Offer the task rule, once.** A new project has a code path, so there is a repository to
write into. Say what the task rule is: a short block added to that repository's own `CLAUDE.md`
saying work goes through a task. Ask for a plain yes or no.
Yes runs the `task-rule` section below for this project. No records the refusal, so it is never
offered again:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
task-rule "<name>" --decline
```
Autonomously, do not ask and do not record anything. The rule writes into a repository the person
owns, so nobody's silence stands for a yes, and an unrecorded question is offered again next time
while a recorded no is not. Say that the offer is waiting, and continue.
**7. Offer a playbook source, once.** Playbooks are the rules every role that writes or judges
code follows, and a new project has none. For each framework known at step 3, find the catalog
sets the way `subscribe-playbook` below finds them when no set id is given. Offer, in one
question: the sets found, by id; a local folder through `add-source <name> playbooks <folder>`;
or neither. A yes runs the matching section below. The offer is made once, because `create` runs
once, and nothing is recorded. Autonomously, do not search and do not ask. Say that the
playbook offer is waiting, and continue, the same as step 6.
## `switch <name-or-path>`
Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> switch "<target>"
```
Looks the target up by its exact name or its exact code path, never by ancestry. Not found: say
so and stop. Found: it may print a `NOTE:` line first, when this directory already belongs to
another project by its own code path. That note means this switch applies to this conversation
only and is not remembered, because a code-path match always wins over a remembered choice
(ideal/project.md, "Picking up work", case 3). No note: the choice is now remembered for this
directory, and a later plain `report` from here finds this project again on its own. Either way
it then prints a `project:` line, the project file's path, and the check's report. Show the
report; read the project file only when a field is needed. Two fields are needed here:
`playbookSubscriptions` and `sources`. A project with no subscription and no source of kind
`playbooks` subscribes to no catalog set and declares no playbooks source. Then name
`subscribe-playbook` and `add-source <name-or-path> playbooks <folder>` as its two answers, in
one sentence, and do not ask.
`switch <path>` on a version 5 folder, one holding `project_state.md` and no `project.json`,
registers it, writes a bare project file, and runs the check. Only the `**Path:**` and
`**Code path:**` lines are read, and the folder name becomes the project name. The folder's
parent becomes the projects base when none is recorded yet. The project file gets `state` and,
when the detector recognises the code path, `frameworks`. The check reports every other field
missing, and each field's own producer fills it in later, which is the design. Each `LEGACY:`
line after `PICKED UP:` names a task still in version 5's own folder, untouched by the pickup.
Name them. Then name `/aida:next` as the step that moves the one the person picks.
The pickup writes two files into the folder: `project.json`, and the check's own record at
`records/check-project.json`. To undo a pickup, run `unregister` below and remove those two
files by hand; nothing else was written. A folder left with its `project.json` is picked up again
by `rebuild-registry`, since the base was recorded at the pickup.
When the check still reports `frameworks` missing, interactive asks "What is the stack?" once and
runs `set-frameworks` below with the answer. Autonomous **halts**, reporting that the frameworks
are missing, the same rule as create's step 3.
A version 5 folder is usually not a git repository, and the check names `git init` there as the
repair. Interactive: offer it once, in one line, and on yes run
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh git-init "<name>"
```
It makes the folder a repository, commits the files already there, and runs the check again.
Autonomous: name the repair and continue.
`TASK_RULE: version 5` after `PICKED UP:` means the code repository's `CLAUDE.md` holds the
task rule version 5 wrote, which names `/ai-dev-assistant:` commands that no longer exist.
Interactive: after the `git-init` offer, offer once to rewrite it, in one line. Yes runs the
`task-rule` section below, which replaces that block in place. No records the refusal with
`task-rule "<name>" --decline`, the same as create's step 6. The block stays as it is.
Autonomous: say the offer is waiting and continue, as at create.
## `list [active|complete|archived]...`
Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh list [state...]
```
Prints one `project:` line per registered project, most recently used first. Each carries
`exists=`. With no state named, every project prints, whatever its state. Naming one or
more states filters to those. Use it for "show me my projects" and for cleanup. A project whose
`exists=` is `false` names a code path that moved or was deleted. Name that project to
the person rather than acting on it alone.
## `state <name-or-path> <active|complete|archived>`
Ending a project sets one of its three states. Every transition is reversible, so reopening a
complete or archived project is just this same call with `active`.
Ask for the reason first, in one line, unless it is already obvious from what was just said; it
becomes the commit's own `Why`. Autonomous: nobody is present to answer; use the reason already
said, if any, or record it as `(autonomous run, no reason given)`, and continue. Then run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
state "<target>" <active|complete|archived> -- <reason...>
```
It writes the new state into the project file and its registry copy, commits the change with that
reason, and reports what it found for `OPEN TASKS`. Show that line as-is: it says what is still
open when a task system exists, or says plainly that none has been built yet in this project when
none does. Either way this call never refuses to close a project over what it finds there; it
only reports it and lets the person decide. Finish by showing the check's report.
## `set-code-path <name-or-path> [<new-code-path>]`
Changing the code path detects and proposes a candidate the same way creation does, not only at
creation.
Given explicitly: canonicalize it by running `cd "<path>" 2>/dev/null && pwd -P`, then confirm it
in one line ("Use `<path>` as the code path?"). Autonomous: accept it without asking, recording
that this run made its own confirmation.
Not given: run
```
"${CLAUDE_PLUGIN_ROOT}"/scripts/detect-framework.sh "$(pwd -P)"
```
Exit 0: the current directory looks like code. Propose it, naming what was detected, and confirm
the same way as an explicit path. Exit 1 or 2: nothing to propose from here. Interactive: ask
"Where does the code live now?" and wait. Autonomous: **halt.** Report that no new code path was
given or could be proposed, and stop.
Once the new path is known, run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
set-code-path "<target>" "<newCodePath>"
```
Looks the target up the same way `switch` does. Not found: say so and stop. The same path as
before: prints `UNCHANGED` and stops there. Otherwise it writes the new code path into the
project file and the registry copy, commits the change, and runs the check. Exit code 5 here
means the same refused location it means at creation. This action undoes the same way. The
script restores the old code path in both places before it reports the
refusal, so nothing is left pointing at a location that was never accepted. Show the whole
output either way.
## `set-frameworks <name-or-path> <framework>...`
Sets the stack by hand, for a project whose code path the detector did not recognise. Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
set-frameworks "<target>" <fw1> [<fw2> ...]
```
Looks the target up the same way `switch` does. Not found: say so and stop. Otherwise it writes
the list into the project file, commits the change, and runs the check. Show the whole output.
## `add-source <name-or-path> <kind> <folder | catalog>`
Declares one folder, or the hosted catalog, as where this project's content of one kind comes
from. The kind is one of `guides`, `playbooks`, `processRecipes`, `agenticRecipes` or
`toolingRecipes`. A project that declares nothing for a kind gets the catalog for it. A project
that declares a folder for a kind has named its source. The catalog then answers for that kind
only when it is declared too, with the word `catalog` in place of the folder. Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
add-source "<target>" <kind> "<folder>"
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
add-source "<target>" <kind> catalog
```
Each new source for a kind takes the next rank, so the order they are declared in is the order
a stage asks them. A second call for the same folder adds the kind to that entry rather than
writing a second one. It commits the change and runs the check. Nothing is fetched; a stage
reads the folder the first time it needs something. Show the whole output. For `playbooks`, a
catalog set is the other answer to the same question, and `subscribe-playbook` below declares one.
For `processRecipes`, the folder holds `process-recipes/<framework>/<phase>.md`, where the phase
is the word a stage asks for. The phases are `research`, `design`, `implement`, `test-authoring`,
`test-execution`, `review`, `worktree-environment`, `e2e-setup` and `visual-regression`. A stage
reads the file there and does not ask the navigator. A phase with no file in any declared folder
is a phase with no recipe, and the stage takes its no-recipe path. To keep the catalog behind
your folder, declare it too: `add-source <target> processRecipes catalog`. A person can copy a
catalog recipe into that layout and edit it. For `toolingRecipes`, the folder holds
`tooling-recipes/<framework>/<tool>.md`, and the tool skill reads it the same way.
## `subscribe-playbook <name-or-path> <framework> <set-id>`
Subscribes the project to one catalog playbook set for one framework it declares. The set id is
`<framework>/best-practices/<author>`. A local folder of plays is the other answer to the same
question, and `add-source <name-or-path> playbooks <folder>` above declares one.
Framework not given: run `list`, take the `project:` line whose name or code path is the
target, and read `frameworks` from `<path>/project.json`. One framework: take it and say so.
Several: ask which, one question. Autonomous with several: **halt** rather than guess, as
create's steps 1 to 3 do. None: name `set-frameworks` and stop.
Set id not given: invoke the `dev-guides-navigator` skill through the Skill tool, in its
identify mode. Search the words `<framework> best practices`, with the framework as the filter.
Its report is `{query, framework, matches: [{kind, name, description, url, sha}], searched,
unavailable}`. Keep the matches whose `url` path is `<framework>/best-practices/<author>/`, and
offer them by set id, one question. No match, with `unavailable` empty: say the catalog lists no
playbook set for that framework, and stop. Name `add-source <name-or-path> playbooks <folder>`
as the other answer. No match, with a catalog in `unavailable`: the search was incomplete, so
say the catalog could not be reached and stop. Autonomous with no set id: **halt.** A
playbook is "a set of rules a person wants followed" (ideal/playbooks.md), and nobody present
chose one.
Before writing, invoke the `dev-guides-navigator` skill
through the Skill tool, in its `playbook <set-id>` mode. `not-a-playbook` or `no-topic` refuses:
name the topic, say a playbook topic carries `playbook: true` in the catalog, and stop.
`listing-unreachable` or `fetch-failed` does not refuse. Say the catalog could not be reached,
say the loader tries again at the next task's research, and continue. A subscription to a
topic with no plays loads nothing, silently, every task, which is why the check runs before the
write. Then run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> \
subscribe-playbook "<target>" <framework> <set-id>
```
Exit 1 with a framework this project never declared: say so, name `set-frameworks`, and stop.
It writes the id under that framework, commits the change, and runs the check.
`unsubscribe-playbook` takes the same three arguments and removes the id, with no such check.
Show the whole output.
## `unregister <name-or-path>`
Run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh unregister "<target>"
```
Drops the registry row only. It prints both folders it names, the project folder and the code
path, and it touches neither. This is recoverable: create a fresh registration pointed at the
same project folder. To recover every unregistered project at once, use `rebuild-registry`
below.
## `task-rule <name-or-path> [--remove | --decline]`
**This is opt-in and never runs without being asked for.** It writes a marker-delimited block
into `<codePath>/CLAUDE.md`, in the user's own repository, saying that work producing findings or
decisions belongs in a task and that a small fix does not need one. A `SessionStart` message is
context; `CLAUDE.md` is an instruction the harness tells the model it must follow, which is the
whole reason this exists as a separate, deliberate write.
Confirm before writing: show what will change (a new block, or a refreshed one if already
present) and ask for a plain yes or no. Autonomous: nobody is present to answer; invoking
`task-rule` at all is itself the request, so skip the confirmation, record that this run made
its own confirmation, and continue. On yes, or under an autonomous run, run:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh --run-mode <interactive|autonomous> task-rule "<target>" -- <reason...>
```
Refuses when the project has no code path. It refuses too when the code path names a directory
that does not exist yet. Either way there is no repository to write into. Say so and stop. Do
not ask again later in the same turn.
`--remove` takes the block back out and leaves the rest of the file untouched:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh task-rule-remove "<target>"
```
## `uninstall <name-or-path>`
Cleaning up removes AIDA's own instructions from the code repository, and nothing else: never
tests, never test configuration, never any tooling. Confirm before running, since this touches
the user's own repository. Autonomous: nobody is present to answer; invoking `uninstall` at all
is itself the request, so skip the confirmation, record that this run made its own confirmation,
and continue:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh uninstall "<target>"
```
Removes the task-rule block when one was installed.
## `rebuild-registry [projectsHome]`
The registry is an index, derivable from the project folders themselves. Run this after a
corrupted or lost registry file, or after unregistering something by mistake:
```
"${CLAUDE_PLUGIN_ROOT}"/skills/project/scripts/project-actions.sh rebuild-registry ["<base>"]
```
Walks every immediate subdirectory of the projects-folder base and reads each one's project file.
With no base given, it walks the base recorded when the first project was created, or the
built-in `~/.claude/aida/projects` when no project was ever created. Replaces the whole registry
with what it found.
`declinedOffers` and `directoryChoices` cannot be recovered this way and start empty again; say so
plainly rather than letting it pass unremarked.
## Reading the check's report
Every path above that touches a project ends by running the check and showing what it printed.
The check also writes its report to `records/check-project.json` in the project folder.
Read its exit code to decide what happens next, never its text alone:
| Exit code | What it means | What this skill does |
|---|---|---|
| 0 | Everything checked passed. | Nothing further; the report already said so. |
| 1 | A project-file field is missing or the wrong shape. | Name each missing field and its producer as the report printed them. Also name any repair the report printed beside them, such as `git init` for a folder that is not a git repository. One exit code carries only the highest condition, so a lower one shows only in the text. |
| 2 | The code path does not exist on disk. | Right after `create`, this is expected; say so and move on. Elsewhere, only the project's owner can say where the code went, and nothing here fixes it. Say that plainly and stop. |
| 3 | The check itself could not run. | Show the error text and stop. |
| 4 | The registry disagrees with the project file, has no row for it, or two rows share a name. | The project file is authoritative; say what the report found and that nothing was changed. A missing or wrong row can be fixed with `rebuild-registry` above; a shared name needs a person to rename one project. |
| 5 | The code path names a refused location: a system root, the home directory, or anything above it. | Say why it was refused. Right after `create` or `set-code-path`, the script has already undone the change; elsewhere, ask for a corrected code path. |
| 6 | The project folder is not yet a git repository, or holds uncommitted work. | Say which. Not a git repository yet only happens on a project that predates this check; running `git init` there is the repair, and this skill does not do it silently. Uncommitted work is worth showing before starting anything else on top of it. |
The check never asks a question, in either mode. When the run is autonomous and a non-zero exit
code came back, the report already says `Autonomous run: ... Recorded, not performed.`; this
skill does not repeat that offer.