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
6 changes: 3 additions & 3 deletions .agents/skills/spool-architecture/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ Create a mutation-batch JSON file:
]
```

Write the batch through a schema migration (short-lived branch + PR):
Write the batch through `mutate` (short-lived branch + PR):

- **MCP (Default)**: Call `spl_schema_migrate` with the current schema and `operations`.
- **MCP (Default)**: Call `spl_mutate` with the `operations` array.
- **CLI (Fallback)**:
```sh
spl schema migrate --schema schema.toml --batch arch-batch.json \
spl mutate --operations arch-batch.json \
--author "Architect <arch@example.com>" --message "Record transactional outbox architecture decision"
```

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/spool-engineering-standards/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ Create a mutation-batch JSON file:
]
```

Write the batch through a schema migration (short-lived branch + PR):
Write the batch through `mutate` (short-lived branch + PR):

- **MCP (Default)**: Call `spl_schema_migrate` with the current schema and `operations`.
- **MCP (Default)**: Call `spl_mutate` with the `operations` array.
- **CLI (Fallback)**:
```sh
spl schema migrate --schema schema.toml --batch standards-batch.json \
spl mutate --operations standards-batch.json \
--author "Staff Engineer <standards@example.com>" --message "Record RFC 7807 error standard and anti-pattern"
```

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/spool-product/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ Create a purely business-oriented mutation-batch JSON file:
]
```

Write the batch through a schema migration (short-lived branch + PR):
Write the batch through `mutate` (short-lived branch + PR):

- **MCP (Default)**: Call `spl_schema_migrate` with the current `schema.toml` (or inline TOML) and `operations`.
- **MCP (Default)**: Call `spl_mutate` with the `operations` array.
- **CLI (Fallback)**:
```sh
spl schema migrate --schema schema.toml --batch product-batch.json \
spl mutate --operations product-batch.json \
--author "Product Manager <pm@example.com>" --message "Record deferred billing address requirement"
```

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/spool-technical-implementation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,12 @@ Create a mutation-batch JSON file demonstrating cross-role synthesis and `Epheme
]
```

Write the batch through a schema migration (short-lived branch + PR):
Write the batch through `mutate` (short-lived branch + PR):

- **MCP (Default)**: Call `spl_schema_migrate` with the current schema and `operations`.
- **MCP (Default)**: Call `spl_mutate` with the `operations` array.
- **CLI (Fallback)**:
```sh
spl schema migrate --schema schema.toml --batch implementation-batch.json \
spl mutate --operations implementation-batch.json \
--author "Engineer <eng@example.com>" --message "Record ephemeral technical spec for outbox relay worker"
```

Expand Down
14 changes: 11 additions & 3 deletions .agents/skills/spool/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Spool includes a native MCP server via `spl mcp`.
| **Reads** | `spl_query_context` | `spl query-context --query <q>` | Replaces former `spl_context` / `spl context` query. |
| **Reads** | `spl_search_expand` | `spl search-expand --query <q>` | Seed retrieval + graph traversal. |
| **Graph** | `spl_graph` | `spl graph` | Full bound snapshot. |
| **Mutate** | `spl_mutate` | `spl mutate --operations <f>` | One node/edge ops batch → short-lived branch + PR. Bound-only. |
| **Merge** | `spl_merge_preview` | `spl merge preview --source <s> --target <t>` | File-graph three-way preview. |
| **Merge** | `spl_merge_apply` | `spl merge apply ...` | Clean apply → short-lived branch + PR. |
| **Merge** | `spl_merge_conflicts` | `spl merge conflicts --transaction <tx>` | Cache-backed conflict state. |
Expand Down Expand Up @@ -108,7 +109,14 @@ History and diff: use **stock git** on the context remote (`git log`, `git diff`

Context-management KEEP tools require `.spool/context.toml`. If unbound, fail closed and tell the user to run `spl context init --remote`.

Writes never push the protected branch. They open `spool/mcp/<stamp>-<nonce>` and a host PR. Identical schema writes are a no-op (no empty PR).
Writes never push the protected branch. They open `spool/mcp/<stamp>-<nonce>` and a host PR. Identical schema or mutate diffs are a no-op (no empty PR).

Routine node/edge writes (same `MutationOperation` shape as schema migrate):

- **MCP**: `spl_mutate(operations: [...], author, message)`.
- **CLI**: `spl mutate --operations mutations.json --author ... --message ...` (`--operations -` reads stdin).

Do not call removed `spl_add` / `spl_commit` / `spl_status`. There are no aliases to `add` / `commit` / `status` / `stage` / `write`. Use `schema migrate` only when changing `schema.toml`.

Before pruning ephemeral planning data:

Expand Down Expand Up @@ -136,9 +144,9 @@ Before pruning ephemeral planning data:
| Commands | Reference |
| :--- | :--- |
| `context init`, `context export` / `migrate-once` | [Working changes](references/working-changes.md) |
| Authoring mutation batches for `schema migrate` | [Batch authoring](references/batch-authoring.md) |
| Authoring mutation batches for `mutate` | [Batch authoring](references/batch-authoring.md) |
| History and diff (stock git); file-graph `merge` | [Branches and history](references/branches-and-history.md) |
| `schema migrate`, `validate` | [Schemas](references/schemas.md) |
| `mutate`, `schema migrate`, `validate` | [Schemas](references/schemas.md) |
| `resolve`, `search`, `filter`, `search-expand`, `query-context` | [Reading graphs](references/reading-graphs.md) |
| `merge` cycle | [Merges](references/merges.md) |
| `prune` | [Maintenance](references/maintenance.md) |
Expand Down
22 changes: 17 additions & 5 deletions .agents/skills/spool/references/batch-authoring.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Batch authoring

A mutation batch is a JSON array. Pass it to `spl schema migrate --batch` (or MCP
`spl_schema_migrate` `operations`) so the candidate graph is validated against the target schema
and written through a short-lived branch + PR. There is no public `spl add` / `spl commit`.
A mutation batch is a JSON array. Pass it to `spl mutate --operations` (or MCP `spl_mutate`
`operations`) so the bound context graph is written through a short-lived branch + PR.
There is no public `spl add` / `spl commit`. Use `schema migrate` only when changing
`schema.toml`.

```json
[
Expand All @@ -20,7 +21,18 @@ and written through a short-lived branch + PR. There is no public `spl add` / `s
```

```sh
spl schema migrate --schema schema.toml --batch mutations.json --message "Record rate limit"
spl mutate --operations mutations.json --message "Record rate limit"
cat mutations.json | spl mutate --operations - --message "Record rate limit"
```

MCP:

```json
{
"operations": [ ... ],
"author": "agent <agent@example.com>",
"message": "Record rate limit"
}
```

Each node represents one atomic idea: one fact, decision, requirement, question, or task. Its title
Expand Down Expand Up @@ -57,5 +69,5 @@ Use stable, descriptive IDs. Add edges when a relationship matters:
```

Properties are typed values: `null`, `bool`, `integer`, `float`, `string`, `list`, or `map`.
`list` and `map` values recursively contain typed values. Use `spl schema migrate --help` for the
`list` and `map` values recursively contain typed values. Use `spl mutate --help` for the
current operation contract before authoring unfamiliar fields.
21 changes: 20 additions & 1 deletion .agents/skills/spool/references/cli-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ spl merge --help
| `context export`, `context migrate-once` | Export leftover `.spl` into bound context git |
| `query-context` | Evidence-focused bounded graph context |
| `search`, `filter`, `search-expand`, `resolve`, `graph` | Query the bound checkout |
| `mutate` | Write node/edge mutations (one batch → branch+PR) |
| `schema migrate`, `validate` | Write a schema (branch+PR) and validate |
| `merge preview/apply/conflicts/resolve/finalize/abort` | File-graph merge |
| `prune` | Remove `Ephemeral` nodes and cascading edges |
Expand Down Expand Up @@ -63,6 +64,24 @@ context export / migrate-once

The `context` namespace is **not** the query verb. Use `query-context`.

## Mutate

```sh
spl mutate --operations mutations.json --message "Record requirement"
```

```text
mutate
--operations <path|-> JSON mutation-operation array (required; - reads stdin)
--author <text>
--message <text>
```

`mutate` is bound-only and refuses unbound workspaces. One ops batch becomes one git commit on a
short-lived branch plus pull request. Pass `-` to `--operations` to read stdin. Identical or empty
effective diffs do not open a PR. Use `schema migrate` when changing `schema.toml`. There are no
aliases to `add` / `commit` / `status` / `stage` / `write`.

## Reading graphs

```sh
Expand Down Expand Up @@ -172,4 +191,4 @@ spl completion bash
spl help query-context
```

`mcp` exposes the KEEP tool set (19 tools), matching `spl --help`.
`mcp` exposes the KEEP tool set (20 tools), matching `spl --help`.
3 changes: 2 additions & 1 deletion .agents/skills/spool/references/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ spl validate
```

`schema migrate` validates the candidate graph against the target schema before opening a PR.
Identical schema content is a no-op (no empty PR). There is no separate `spl commit`.
Identical schema content is a no-op (no empty PR). Routine node/edge writes without a schema change
use `spl mutate` / `spl_mutate`. There is no public `spl add` / `spl commit`.

`validate` checks the bound checkout against `schema.toml`.
10 changes: 7 additions & 3 deletions .agents/skills/spool/references/working-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ Rack or `.spl` as source of truth.

## Graph writes

There is no public `spl add` / `spl commit`. Bound writes go through KEEP commands that open a
short-lived `spool/mcp/<stamp>-<nonce>` branch and a pull request:
There is no public `spl add` / `spl commit`. Routine node and edge writes use `spl mutate`
(MCP: `spl_mutate`). Bound writes open a short-lived `spool/mcp/<stamp>-<nonce>` branch and a
pull request:

```sh
spl mutate --operations mutations.json --message "Record requirement"
spl schema migrate --schema schema.toml --batch mutations.json
spl asset add --file docs/architecture.md --title "Architecture notes"
spl prune --author alice --message "Prune transient plan"
```

Identical schema content is a no-op (no empty PR).
`mutate` is bound-only and refuses unbound workspaces. Use `schema migrate` when changing
`schema.toml`. Identical mutate or schema content is a no-op (no empty PR). There are no aliases
to `add` / `commit` / `status` / `stage` / `write`.

## History and diff

Expand Down
18 changes: 14 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ generated from commits since the preceding `v*` tag. Commits prefixed with `docs

### Added

- Dedicated KEEP `spl mutate` / MCP `spl_mutate` for routine bound node and
edge writes. CLI: `spl mutate --operations <file|->` (stdin via `-`). MCP:
`{ "operations": [...], "author"?, "message"? }`. One mutation-operation batch
(same shape as schema migrate / private Stage) becomes one git commit on a
short-lived `spool/mcp/<stamp>-<nonce>` branch plus pull request via existing
`ctxgit` Stage/Commit. Bound-only (`FindBind` fail-closed). Identical or empty
effective diffs do not open a PR. Result aligns with `WriteResult` (`branch`,
`commit`, `pullRequest`, `written`, `deleted`). Schema changes still use
`schema migrate`. No aliases to `add` / `commit` / `status` / `stage` / `write`.
- Documented `.spool/context.toml` bind format and the N-code-repos → one
context git remote as the only durable SoT (`docs/context-bind.md`).
- `spl context init --remote` seeds `CodeRepository` nodes from explicit binds
Expand All @@ -35,10 +44,11 @@ generated from commits since the preceding `v*` tag. Commits prefixed with `docs
- `spl prune` / `spl_prune` is bound graph cleanup of `Ephemeral` nodes and
cascading edges (short-lived branch + PR). It is not pack/CAS garbage collection.
Unbound workspaces are refused.
- Identical schema migrations are a no-op (no empty commit or PR).
- MCP advertises the KEEP tool set only. `spl --help` matches that surface. A golden KEEP/REMOVE
list fails `make check` if a removed CLI name or MCP tool reappears (including `spl_context` and
Spool VCS wrappers `add`/`status`/`commit`/`branch`/`switch`).
- Identical schema migrations and identical mutate diffs are a no-op (no empty commit or PR).
- MCP advertises the KEEP tool set only (including `spl_mutate`). `spl --help` matches that
surface. A golden KEEP/REMOVE list fails `make check` if a removed CLI name or MCP tool
reappears (including `spl_context` and Spool VCS wrappers `add`/`status`/`commit`/`branch`/
`switch`).
- **Sunset stop-list for solution context:** Spool-as-VCS, Rack sync, `.spl` as
durable SoT, and pack wire-compat are stopped. Bind + stock git is the **only**
durable SoT. Every agent write is a short-lived branch + PR (not push-clean to
Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ spl validate

Use stock git on the context remote for history and diff. Spool does not wrap `git log` or `git diff`.

Write routine node and edge mutations (one JSON batch → short-lived branch + PR):

```sh
spl mutate --operations mutations.json --message "Record requirement"
```

`mutate` requires `.spool/context.toml` and refuses unbound workspaces. Identical or empty
effective diffs do not open a PR. Use `schema migrate` when changing `schema.toml`.

## Context bind, export, and migrate-once

```sh
Expand All @@ -93,7 +102,13 @@ VCS commands. `migrate-once` skips the write when titles are already present.

The `context` namespace is **only** init/export/migrate-once. Graph queries use `query-context`.

## Schema, assets, merge, and prune
## Mutate, schema, assets, merge, and prune

Write routine node and edge mutations as one JSON batch. Bound-only; opens a short-lived branch + PR:

```sh
spl mutate --operations mutations.json --author alice --message "Record requirement"
```

Author a schema in TOML and apply conforming graph mutations:

Expand Down Expand Up @@ -156,7 +171,7 @@ when it deletes ephemeral nodes and cascading edges.

Spool includes a native Model Context Protocol server built on the official Go SDK
([`github.com/modelcontextprotocol/go-sdk`](https://github.com/modelcontextprotocol/go-sdk)).
`spl mcp` exposes the **KEEP** tool set (19 tools) over stdio. The tool list matches `spl --help`.
`spl mcp` exposes the **KEEP** tool set (20 tools) over stdio. The tool list matches `spl --help`.

### Client configuration

Expand Down Expand Up @@ -186,6 +201,7 @@ The installed surface, including generated help, is documented in
| `context export`, `context migrate-once` | Export leftover `.spl` into bound context git |
| `query-context` | Evidence-focused bounded graph context (replaces query-`context`) |
| `search`, `search-expand`, `filter`, `resolve`, `graph` | Bound checkout reads |
| `mutate` | Routine node/edge writes (one batch → branch+PR) |
| `schema migrate`, `validate` | Schema write (branch+PR) and validation |
| `asset add`, `asset read` | Reference assets on the bound checkout |
| `merge preview/apply/conflicts/resolve/finalize/abort` | File-graph merge |
Expand Down
72 changes: 72 additions & 0 deletions cmd/spl/commands/mutate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package commands

import (
"encoding/json"
"fmt"
"io"
"os"
"strings"

"github.com/autonomous-bits/spool/internal/ctxgit"
"github.com/autonomous-bits/spool/internal/repository"
"github.com/spf13/cobra"
)

// NewMutateCommand writes one node/edge mutation batch via a short-lived branch + PR.
func NewMutateCommand(opts ctxgit.Options) *cobra.Command {
var operationsPath, author, message string
command := &cobra.Command{
Use: "mutate",
Short: "Mutate the bound context graph",
Long: "Apply one JSON mutation-operation batch (nodes and edges) to the bound context checkout and open a short-lived branch + PR. Unbound workspaces are refused. Identical or empty diffs do not open a PR. Use schema migrate when changing schema.toml.",
Example: " spl mutate --operations mutations.json --message \"Record requirement\"\n cat mutations.json | spl mutate --operations - --message \"Record requirement\"",
Args: cobra.NoArgs,
SilenceUsage: true,
RunE: func(command *cobra.Command, _ []string) error {
operations, err := readMutationOperations(command, operationsPath)
if err != nil {
return err
}
session, err := startBoundSession(command, opts)
if err != nil {
return err
}
result, err := session.Mutate(command.Context(), ctxgit.MutateRequest{
Operations: operations,
Author: author,
Message: message,
})
if err != nil {
return err
}
return json.NewEncoder(command.OutOrStdout()).Encode(result)
},
}
command.Flags().StringVar(&operationsPath, "operations", "", "JSON mutation-operation array (file path, or - for stdin)")
command.Flags().StringVar(&author, "author", "", "git author")
command.Flags().StringVar(&message, "message", "", "commit/PR message")
_ = command.MarkFlagRequired("operations")
return command
}

func readMutationOperations(command *cobra.Command, path string) ([]repository.MutationOperation, error) {
path = strings.TrimSpace(path)
var data []byte
var err error
if path == "-" {
data, err = io.ReadAll(command.InOrStdin())
if err != nil {
return nil, fmt.Errorf("read mutation operations from stdin: %w", err)
}
} else {
data, err = os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read mutation operations: %w", err)
}
}
var operations []repository.MutationOperation
if err := json.Unmarshal(data, &operations); err != nil {
return nil, fmt.Errorf("decode mutation operations: %w", err)
}
return operations, nil
}
Loading
Loading