Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 71 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[![release](https://img.shields.io/github/v/release/SmartAndPoint/ProjectStore?label=release)](https://github.com/SmartAndPoint/ProjectStore/releases) [![license](https://img.shields.io/github/license/SmartAndPoint/ProjectStore?label=license)](./LICENSE) [![Star on GitHub](https://img.shields.io/badge/%E2%AD%90-star_us-yellow?logo=github)](https://github.com/SmartAndPoint/ProjectStore/stargazers)

A [Claude Code](https://claude.com/claude-code) plugin.
A project workflow plugin for [Claude Code](https://claude.com/claude-code) and [OpenAI Codex](https://developers.openai.com/codex/) (experimental).

---

Expand Down Expand Up @@ -88,6 +88,28 @@ npx projectstore-claude install --project "$PWD"

The same tree is published to npm as [`projectstore`](https://www.npmjs.com/package/projectstore) — one source package carrying every harness's manifest — and `projectstore-claude` is its Claude Code shell: the core pinned at the same version and bundled inside, the harness fixed, so the one command has the same shape on every harness. It registers the plugin with Claude Code: it writes a small local marketplace of its own under your Claude home, then drives `claude plugin marketplace add` / `plugin install` **at local scope**, so the registration lands in this checkout's `.claude/settings.local.json` and nowhere else. Every host command is printed before it runs; naming the harness is the confirmation. Restart Claude Code afterwards. A git-marketplace copy already enabled for the checkout is silenced there (not globally) so the plugin does not load twice; `uninstall` turns it back on. Pin or upgrade with `npx projectstore-claude@<version> upgrade --project "$PWD"` — the version you name is the version you run. The core's low-level form, `npx projectstore <verb> --harness claude-code …`, is exactly what the shell runs. bun works the same on the packed bin.

**Codex uses its own rendered plugin root and the same one-command shape.**
Codex support is **experimental** (see [`docs/harnesses.md`](./docs/harnesses.md)
for what has been measured and what has not), and its shell is not published
yet. From this checkout, exercise the exact npx path against the built tarball:

```sh
npm run shells:build -- --only projectstore-codex --dev --out dist
npx --package "./dist/projectstore-codex-$(node -p 'require("./package.json").version').tgz" projectstore-codex install --project "$PWD"
```

The Codex shell carries a canonical portable manifest, namespaced workflow and
role skills, lifecycle hooks, and the exact bundled core. It stages a stable
marketplace under `CODEX_HOME`, drives `codex plugin marketplace add` and
`codex plugin add`, then reads the installation back and verifies its version
and payload digest. Restart Codex, approve the hooks, and run
`$projectstore-bind <vault-path>`. After the first explicit npm publication,
the shorter command is
`npx projectstore-codex@<version> install --project "$PWD"`; use its `upgrade`
verb for later releases. Because Codex's
plugin registry is user-global, ordinary uninstall removes only the project's
agents block; `uninstall --global` is the explicit machine-wide removal.

The package also carries a `bin`. Without a session — in CI, or in a shell — the same core answers token-free, with a `--json` envelope on every verb:

```
Expand Down Expand Up @@ -119,20 +141,43 @@ npx projectstore bind ~/vaults/my-project
npx projectstore init ~/vaults/new-project --language ru
```

`projectstore-claude`, `projectstore-codex` and `projectstore-opencode` on npm are this package's per-harness shells — the core pinned and bundled, the harness fixed; the Codex and opencode shells publish once their plugin roots are rendered. The other `projectstore-*` names are reserved placeholders pointing back here. One source package, one version, N published tarballs.
`projectstore-claude`, `projectstore-codex` and `projectstore-opencode` are this package's per-harness shells — the core pinned and bundled, the harness fixed. Codex's shell is experimental and stays private until a live Codex session has exercised its hooks from an installed release ([`docs/harnesses.md`](./docs/harnesses.md)). The opencode shell publishes after its plugin root is rendered. The other `projectstore-*` names are reserved placeholders pointing back here. One source package, one version, N tarballs.
</details>

## Upgrading

`/plugin update` (or auto-update) and a restart is the whole procedure. What
an existing project sees afterwards, and why:

- **The status line keeps rendering.** A launcher written by an earlier
version still works, but it now carries no file stamp and its embedded
fallback root is frozen at the old version; the startup line says so at
every session start until you run the fix — `/projectstore:doctor --fix` — which
re-stamps it. Nothing rewrites that file behind your back any more: first
wiring and refresh are `install`'s, behind a preview.
`/plugin update` (or auto-update) and a restart, then one command per project
bound before 0.28. What an existing project sees afterwards, and why:

- **The project's files move to `.projectstore/`.** `.claude/projectstore.json`
and `.claude/.projectstore/` become `.projectstore/projectstore.json`,
`.projectstore/harness/claude-code.json` and `.projectstore/state/`. Nothing
breaks before you move them: every reader falls back to the old paths through
0.29, and the startup line names the command until the move is done. Close
every Claude Code session in the project, run that command from a terminal,
then restart. The command depends on how you installed:
- **From the git marketplace** (every 0.27.x install): the installed copy's
own `bin/projectstore.mjs`, with its path spelled out in the startup line —
`node "<plugin cache>/bin/projectstore.mjs" upgrade --harness claude-code --no-register --project "$PWD"`.
`--no-register` leaves your plugin registration as it is.
- **From npm**: `npx projectstore-claude@<version> upgrade --no-register --project "$PWD"`.

Both forms move only the project's files, so neither touches your plugin
registration. The same run re-stamps the status-line launcher at its new path
and re-registers the agents block, whose template is now v4. A plain
`npx projectstore-claude upgrade` on a git-marketplace install does more: it
also registers the plugin from npm for this checkout and turns the
git-marketplace copy off here, so `/plugin update` stops reaching the
checkout. If that already happened,
`npx projectstore-claude@<version> uninstall --surface plugin --project "$PWD"`
turns the git-marketplace copy back on. Restart, then run
`/projectstore:doctor --fix` in the new session, which re-stamps the status
line against that copy.
- **The status line keeps rendering.** A launcher written by an earlier version
still works, but it carries no file stamp and its embedded fallback root is
frozen at the old version; the move above re-stamps it. Nothing rewrites that
file behind your back any more: first wiring and refresh are `install`'s,
behind a preview.
- **`/projectstore:status` and `/projectstore:search` answer differently:**
facts from artifact frontmatter and the derived views' freshness instead
of an `mtime` walk; a literal, bounded, grouped search instead of a shell
Expand All @@ -144,9 +189,20 @@ an existing project sees afterwards, and why:
a crash.
- **The plugin registers an MCP server** (eight read-only tools over the
vault). Claude Code may ask you to approve it once.
- **Rolling back** to an earlier version works; that version's first session
overwrites the stamped launcher, and coming forward again costs the same
one `--fix`.
- **The agents block stays where Claude Code reads it.** In a project with an
`AGENTS.md`, the block goes there and `CLAUDE.md` carries a one-line
`@AGENTS.md` import; otherwise the block goes into `CLAUDE.md`. A project with
an `AGENTS.md` and no `CLAUDE.md` now gains that one-line `CLAUDE.md`.
- **The passive skills are published under the `projectstore-` prefix**
(`projectstore-decision-detector`, `projectstore-peer-reviewer`,
`projectstore-story-completion`, `projectstore-vault-communication`). Nothing
in a project names them; only a skill listing shows the new names.
- **Rolling back** to 0.27.x after the move: 0.27.x looks for its binding under
`.claude/`, finds none and offers `bind`. Do not accept. A re-bind writes
`.claude/projectstore.json` again, and 0.28's `install` and `upgrade` refuse
while two bindings exist. To come forward again, delete
`.claude/projectstore.json`, then run the command the startup line names
once more: a 0.27.x session writes its welcome marker back under `.claude/`.
- **Installed from npm?** Then `/plugin update` has nothing to fetch: the
registration is refreshed by the package itself — from a terminal outside
the session, `npx projectstore-claude@<version> upgrade --project "$PWD"`
Expand Down Expand Up @@ -193,7 +249,7 @@ The deep dive — real session files, measured payloads, how every mechanism wor

## Uninstalling

`/plugin uninstall projectstore@SmartAndPoint` for a git-marketplace install; `npx projectstore-claude uninstall --project "$PWD"` (from a terminal) for an npm one — it forgets the registration for this checkout, turns a silenced git copy back on, and removes the local marketplace directory only when no other checkout uses it. Your vault is yours — plain markdown, untouched. One leftover of the `/plugin` path: the agents block in `CLAUDE.md`/`AGENTS.md`. Before uninstalling, run `/projectstore:agents unregister` (which runs the core's `uninstall --surface agents_block` for this harness), or delete everything between `<!-- projectstore:agents … -->` and `<!-- /projectstore:agents -->` by hand.
`/plugin uninstall projectstore@SmartAndPoint` for a Claude git-marketplace install; `npx projectstore-claude uninstall --project "$PWD"` for its npm registration. For Codex, `npx projectstore-codex uninstall --project "$PWD"` removes only project-owned wiring; add `--global` only to remove the user-global Codex plugin and marketplace. Your vault is yours — plain markdown, untouched. One leftover of a host-managed plugin path can be the agents block in `CLAUDE.md`/`AGENTS.md`; remove it with the harness's agents unregister skill, or delete everything between `<!-- projectstore:agents … -->` and `<!-- /projectstore:agents -->` by hand. `uninstall` leaves a block that lives in `AGENTS.md`, because that file is read by other coding agents too, and removes the `CLAUDE.md` import when that file holds nothing else; `uninstall --surface agents_block` removes the block as well.

## Extending

Expand Down
59 changes: 59 additions & 0 deletions adapters/codex/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/hooks/session-start.mjs\""
},
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/hooks/session-rules.mjs\""
}
]
}
],
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/scripts/touch-session.mjs\""
}
]
}
],
"PostToolUse": [
{
"matcher": "apply_patch",
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/scripts/touch-session.mjs\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/hooks/session-stop.mjs\""
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "node \"${PLUGIN_ROOT}/node_modules/projectstore/hooks/pre-compact.mjs\""
}
]
}
]
}
}
76 changes: 76 additions & 0 deletions adapters/codex/skills/projectstore-adr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: projectstore-adr
description: "Create a new Architecture Decision Record (ADR) in the bound vault. Arguments: <title>."
---

## Runtime path

Resolve paths from this skill's own directory, never from the checkout or a
remembered cache path. The plugin root is two directories above this SKILL.md;
the bundled core is `<plugin-root>/node_modules/projectstore`. Before running
any ProjectStore command, export `PROJECTSTORE_CORE_ROOT` to that bundled-core
path in its own shell statement, then use `node "${PROJECTSTORE_CORE_ROOT}/…"`.
Do not prefix the command with the assignment: a shell expands the quoted path
before that inline assignment takes effect. If the bundled core is missing, stop
and report a broken plugin install; do not fetch a different version from npm.

## User arguments

The source command's host-substituted argument token is rendered here as
`<user-arguments>` (or `<user-arguments-without-fix>`). Before executing a
shown command, replace that token with the actual arguments from the user's
request and shell-quote values safely. Never pass the angle-bracket token
literally and never treat it as a shell variable.

You are creating a new ADR.

Steps:

1. **Check config**: `test -f .projectstore/projectstore.json` — if missing, tell user to run `$projectstore-bind <path>` and stop.

2. **Render draft** by running:

```bash
node "${PROJECTSTORE_CORE_ROOT}/scripts/draft.mjs" adr "<user-arguments>"
```

The script outputs JSON with shape `{ kind, path, content, index, collision, warnings, vars }`. Capture stdout.

3. **Show user a preview**: print the target `path`, then the first ~30 lines of `content` in a code block. State the slug (it IS the identity — ADR-010). If `index` is non-null, print `index.line` — the exact row that will appear in the folder index (rendered by the regeneration's own rules, so it is what lands, not an approximation), unless the index step reports a failure and no row lands at all. Render every entry in `warnings` as a `⚠️` line. If `collision` is non-null, surface it as a **topic collision**: `⚠️ "<identity>" already exists as <with> — same topic, two artifacts.` Ask whether to open/extend the existing artifact, pick a genuinely different slug (a deliberate `-2` suffix is a distinct identity and stays legal), or cancel. Do not write over a collision without an explicit user decision.

4. **Approval**: use the harness's user-input mechanism with options:
- **Yes** — write the file as-is
- **Edit before saving** — let the user describe a change; you regenerate accordingly (e.g., adjust title, status, add tags) and re-preview
- **No** — abort

This is the only gate: **Yes** covers both the artifact and its index row.
Disclose in the question that the folder's whole managed index table is
regenerated from vault state at write time, so the update may also repair
a stale row for another artifact.

5. **Post-approval race re-check** (Layer 1 — multi-session safety): re-run `draft.mjs adr "<user-arguments>"` and re-read `collision` — a plain `test -e` cannot see normalized cross-era collisions, and another session may have created the same topic while you waited on approval. If `collision` is now non-null (or changed), show it as in step 3 and re-ask. The slug is derived from the title, so the re-render is byte-identical otherwise — never expect a "fresh number".

6. **On Yes** (path free): use the file-writing tool to write `content` to `path`.

7. **Index update** (only if `index` field is non-null — skip silently otherwise):
apply through the core — never the Write/file-editing tools, and do not ask again:

```bash
node "${PROJECTSTORE_CORE_ROOT}/bin/projectstore.mjs" reconcile --write --only indexes=<index.folder>
```

The index row is derived state: the regeneration renders it in canonical
order (date, then number/slug), replaces the file atomically, and preserves
manual prose outside the managed table. Never substitute the class-wide
`indexes` selector — that would regenerate every folder index.

The artifact is already on disk, so a failure here is a warning, never a
failed creation. Two shapes, both reported naming the folder:
- **stderr, no stdout JSON** — the named target was rejected before any
write (README absent, or its index header matches no registered form).
Suggest fixing the header (`$projectstore-doctor`) or restoring the
README; the row lands on the next reconcile.
- **JSON with a per-target `error`, nonzero exit** — an I/O failure during
the write. Suggest `$projectstore-reconcile`.

8. **Final message**: print the file path, a reminder to fill `Context`, `Decision`, `Rationale`, and a hint to commit the new ADR if the vault is git-tracked.
50 changes: 50 additions & 0 deletions adapters/codex/skills/projectstore-agents/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: projectstore-agents
description: "Register or remove ProjectStore's shared agents block, inspect its Codex model overlay, or configure per-role models. Arguments: <register | unregister | status | configure>."
---

## Runtime path

Resolve paths from this skill's own directory, never from the checkout or a
remembered cache path. The plugin root is two directories above this SKILL.md;
the bundled core is `<plugin-root>/node_modules/projectstore`. Before running
any ProjectStore command, export `PROJECTSTORE_CORE_ROOT` to that bundled-core
path in its own shell statement, then use `node "${PROJECTSTORE_CORE_ROOT}/…"`.
Do not prefix the command with the assignment: a shell expands the quoted path
before that inline assignment takes effect. If the bundled core is missing, stop
and report a broken plugin install; do not fetch a different version from npm.

## User arguments

The source command's host-substituted argument token is rendered here as
`<user-arguments>` (or `<user-arguments-without-fix>`). Before executing a
shown command, replace that token with the actual arguments from the user's
request and shell-quote values safely. Never pass the angle-bracket token
literally and never treat it as a shell variable.

You are managing ProjectStore's Codex agent integration. Require a bound project.

## register / unregister

Preview the requested change and ask for explicit approval. On approval, run the
core's `install` or `uninstall` verb with `--harness codex --surface
agents_block --project "$PWD"`. Print its output verbatim. Never edit the
managed block by hand.

## status

Run `plan --json --harness codex --surface agents_block --project "$PWD"`,
then `agents show --json --project "$PWD"`. Report the block state and each
role's resolved model and source. The active overlay path returned by the core
is authoritative.

## configure

Ask for a default model and optional per-role model ids. Use actual Codex model
ids supplied by the user or visible in the current host; do not translate model
names from another harness. Preview the exact argv, ask for approval, then run
`agents configure --harness codex [--default <model>] [--agent
<role>=<model> ...] --project "$PWD"`. The core is the only writer. Do not pass
an effort override: per-role effort is outside this integration's current
contract. A configured model is consumed by the role-orchestration skills on
their next spawn; no restart is needed.
Loading
Loading