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
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ go install github.com/itseffi/productize/cmd/productize@latest
### 2. Install the skills into your agent

```bash
productize setup
productize setup --agent codex
```

`setup` detects your installed AI tools and installs the full skill catalog into each — it supports **40+ agents and editors** (Claude Code, Codex, Cursor, Droid, OpenCode, Pi, Gemini, Copilot, Windsurf, Amp, Continue, Goose, Roo Code, Cline, and more). Installing skills is separate from *executing* through a runtime: to run `tasks run`, `reviews fix`, or `exec` you need an ACP-capable runtime (see the table below). Common options:
`setup` installs the skill catalog into the agent/editor you choose — it supports **40+ agents and editors** (Claude Code, Codex, Cursor, Droid, OpenCode, Pi, Gemini, Copilot, Windsurf, Amp, Continue, Goose, Roo Code, Cline, and more). Non-interactive installs require an explicit target so Productize does not spray `.claude`, `.agents`, `.codex`, or other tool directories without permission. Installing skills is separate from *executing* through a runtime: to run `tasks run`, `reviews fix`, or `exec` you need an ACP-capable runtime (see the table below). Common options:

```bash
productize setup --list # show everything that would be installed
productize setup --doctor --agent codex --format json
productize setup --core-only # install only the core lifecycle skills
productize setup --global # install for all projects (~/.productize) instead of just this one
productize setup --agent claude --agent codex --yes
productize setup --all-agents --yes
```

### 3. Adopt existing project context
Expand Down Expand Up @@ -134,8 +136,13 @@ Beyond skills, Productize ships **reusable agents** — named, purpose-built sub
```bash
productize agents list
productize agents inspect <name>
productize exec --agent productize-operator "build X"
```

`productize-operator` is the first-stop agent for routing Productize work. It reads
project knowledge, checks workflow artifacts, identifies missing PRD/TechSpec/task
steps, and recommends the exact next command before mutating files or running agents.

See [docs/reusable-agents.md](docs/reusable-agents.md).

### ACP runtimes (execution backends)
Expand Down Expand Up @@ -173,6 +180,7 @@ productize runs watch <run-id> # stream a running job
|---------|--------------|
| `productize init existing` | Adopt an existing repo into `.productize/project/` knowledge docs. |
| `productize setup` | Install the skill catalog and reusable agents into your AI agent(s). |
| `productize setup --doctor` | Inspect setup targets, paths, and drift without installing. |
| `productize exec [prompt]` | Run one ad-hoc prompt through an ACP runtime (headless). |
| `productize tasks run <slug>` | Execute a task workflow via the daemon. |
| `productize tasks validate` | Validate task-workflow metadata. |
Expand Down
5 changes: 3 additions & 2 deletions agents/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Bundled reusable agents live in subdirectories of this package.

The package currently ships no bundled reusable agents. This file keeps the
embedded filesystem root readable without embedding Go source files.
The package ships `productize-operator`, the first-stop agent for routing
Productize work through project adoption, PRD, TechSpec, task execution, and
review/fix workflows.
2 changes: 1 addition & 1 deletion agents/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import "embed"

// FS holds the bundled reusable-agent fixtures installed by `productize setup`.
//
//go:embed README.md
//go:embed README.md productize-operator/AGENT.md
var FS embed.FS
58 changes: 58 additions & 0 deletions agents/productize-operator/AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Productize Operator
description: Routes Productize work, selects the next workflow step, and keeps tactical skills behind one operator-facing interface.
---

# Productize Operator

You are the Productize operator agent. Your job is to turn a user request into the
next safe Productize workflow action.

## Operating Contract

Always inspect existing context before recommending a route:

1. Read `.productize/project/context.md` when it exists.
2. Read `.productize/project/conventions.md` when it exists.
3. Read `.productize/project/decisions.md` when it exists.
4. Inspect `.productize/tasks/` for active workflow artifacts.
5. Treat tactical skills as internal implementation details unless the user asks
for a specific skill by name.

## Route Selection

For `/productize build <request>` or `build <request>`:

- If `.productize/project/context.md` is missing, route first to
`productize init existing`.
- If project context exists but PRD, TechSpec, or task files are missing, route
through `/create-prd`, `/create-techspec`, then `/create-tasks`.
- If task files exist, route to `productize tasks run <workflow>`.
- If review artifacts exist, route to review or fix commands.

## Output Format

Return exactly these sections:

```text
Route selected:
1. ...

Why:
...

Project context read:
...

Missing artifacts:
...

Next command:
...

Approval needed:
...
```

Ask for approval before any route step that writes files, runs an implementation
agent, changes git state, or starts a long-running daemon task.
15 changes: 15 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,26 @@ productize setup [flags]
| `--global`, `-g` | `false` | Install to user directory instead of project |
| `--copy` | `false` | Copy files instead of symlinking |
| `--list`, `-l` | `false` | List setup assets without installing |
| `--doctor` | `false` | Inspect setup targets and drift without installing |
| `--format` | `text` | Output format for `--doctor`: `text` or `json` |
| `--yes`, `-y` | `false` | Skip confirmation prompts |
| `--core-only` | `false` | Install only core workflow, lifecycle, and gate skills |
| `--no-tactical` | `false` | Alias for `--core-only` |
| `--all-agents` | `false` | Install to every supported agent/editor destination |
| `--all` | `false` | Deprecated: setup installs all skills by default; also skips prompts |

Non-interactive installs require an explicit target:

```bash
productize setup --agent opencode --yes
productize setup --all-agents --yes
productize setup --doctor --agent opencode --format json
```

Use `--doctor` to see the detected agents, selected targets, install paths,
catalog path, stale or missing assets, and extension conflicts before changing
anything.

## `productize upgrade`

Upgrade Productize to the latest release.
Expand Down
6 changes: 6 additions & 0 deletions docs/reusable-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,14 @@ That automatic host injection is what lets optional extension skills such as `id
productize agents list # list resolved agents
productize agents inspect reviewer # inspect one definition + validation status
productize exec --agent reviewer "Review the staged changes"
productize exec --agent productize-operator "build X"
```

Productize ships `productize-operator` as the default reusable agent for driving
the workflow. It reads project knowledge, inspects workflow artifacts, selects the
next Productize route, and asks for approval before file writes, agent runs, or git
changes.

Example `inspect` output (paths omitted):

```text
Expand Down
9 changes: 8 additions & 1 deletion docs/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ Skills are split into two tiers so you can choose how much to install:
By default, `productize setup` installs **everything** (the full-power default). Narrow it with:

```bash
productize setup --agent codex # explicit target for non-interactive installs
productize setup --core-only # core tier only
productize setup --skill create-prd --skill create-tasks # specific skills
productize setup --list # preview the resolved catalog
productize setup --doctor --agent codex --format json
```

## The effective catalog
Expand All @@ -58,7 +60,12 @@ This catalog is what your agents read to discover available skills. It's plain J

## How agents use skills

Productize doesn't run a skill router. Once installed, skills live in your agent's own skills directory (symlinked by default, or copied with `--copy`). Your agent (Claude Code, Codex, Cursor, …) discovers the `SKILL.md` files and exposes them — typically as named commands you invoke during a session. Productize's job is to install, resolve, and keep the catalog coherent; your agent does the invoking.
Productize provides one front-door routing skill, `/productize`, and keeps tactical
skills behind that operator surface for normal users. Once installed, skills live
in your agent's own skills directory (symlinked by default, or copied with
`--copy`). Your agent (Claude Code, Codex, Cursor, …) discovers the `SKILL.md`
files and exposes them. Start with `/productize build <request>` unless you know
the exact lifecycle skill you need.

## Writing your own skill

Expand Down
97 changes: 93 additions & 4 deletions internal/cli/agents_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import (

reusableagents "github.com/itseffi/productize/internal/core/agents"
"github.com/itseffi/productize/internal/core/agents/mcpserver"
"github.com/itseffi/productize/internal/setup"
"github.com/spf13/cobra"
)

type reusableAgentRegistryFactory func() *reusableagents.Registry

type agentsListCommandState struct {
simpleCommandBase
newRegistry reusableAgentRegistryFactory
newRegistry reusableAgentRegistryFactory
listBundledReusableAgents func() ([]setup.ReusableAgent, error)
verifyReusableAgents func(setup.ReusableAgentVerifyConfig) (setup.ReusableAgentVerifyResult, error)
}

type agentsInspectCommandState struct {
Expand All @@ -44,6 +47,13 @@ type inspectAgentReport struct {
ValidationError error
}

type bundledReusableAgentListItem struct {
Agent setup.ReusableAgent
Scope setup.InstallScope
State setup.VerifyState
TargetPath string
}

func newAgentsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "agents",
Expand All @@ -68,7 +78,11 @@ Workspace agents live under .productize/agents/<name>/ and override same-name ag
}

func newAgentsListCommand() *cobra.Command {
state := &agentsListCommandState{newRegistry: func() *reusableagents.Registry { return reusableagents.New() }}
state := &agentsListCommandState{
newRegistry: func() *reusableagents.Registry { return reusableagents.New() },
listBundledReusableAgents: setup.ListBundledReusableAgents,
verifyReusableAgents: setup.VerifyReusableAgents,
}
return &cobra.Command{
Use: "list",
Short: "List resolved reusable agents",
Expand Down Expand Up @@ -131,7 +145,11 @@ func (s *agentsListCommandState) run(cmd *cobra.Command, _ []string) error {
if err != nil {
return withExitCode(2, fmt.Errorf("discover reusable agents: %w", err))
}
if err := writeAgentsListText(cmd.OutOrStdout(), catalog); err != nil {
bundledAgents, err := s.bundledReusableAgentStatus()
if err != nil {
return withExitCode(2, fmt.Errorf("inspect bundled reusable agents: %w", err))
}
if err := writeAgentsListText(cmd.OutOrStdout(), catalog, bundledAgents); err != nil {
return withExitCode(2, fmt.Errorf("write agents list: %w", err))
}
return nil
Expand All @@ -144,6 +162,53 @@ func (s *agentsListCommandState) registry() *reusableagents.Registry {
return reusableagents.New()
}

func (s *agentsListCommandState) bundledReusableAgentStatus() ([]bundledReusableAgentListItem, error) {
listBundledReusableAgents := s.listBundledReusableAgents
if listBundledReusableAgents == nil {
listBundledReusableAgents = setup.ListBundledReusableAgents
}
bundledAgents, err := listBundledReusableAgents()
if err != nil {
return nil, err
}
if len(bundledAgents) == 0 {
return nil, nil
}

verifyReusableAgents := s.verifyReusableAgents
if verifyReusableAgents == nil {
verifyReusableAgents = setup.VerifyReusableAgents
}
verified, err := verifyReusableAgents(setup.ReusableAgentVerifyConfig{
ResolverOptions: currentResolverOptions(),
ReusableAgents: bundledAgents,
ScopeHint: setup.InstallScopeProject,
})
if err != nil {
return nil, err
}

verifiedByName := make(map[string]setup.VerifiedReusableAgent, len(verified.Agents))
for i := range verified.Agents {
verifiedByName[verified.Agents[i].ReusableAgent.Name] = verified.Agents[i]
}

items := make([]bundledReusableAgentListItem, 0, len(bundledAgents))
for i := range bundledAgents {
item := bundledReusableAgentListItem{
Agent: bundledAgents[i],
Scope: verified.Scope,
State: setup.VerifyStateMissing,
}
if resolved, ok := verifiedByName[bundledAgents[i].Name]; ok {
item.State = resolved.State
item.TargetPath = resolved.TargetPath
}
items = append(items, item)
}
return items, nil
}

func (s *agentsInspectCommandState) run(cmd *cobra.Command, args []string) error {
ctx, stop := signalCommandContext(cmd)
defer stop()
Expand Down Expand Up @@ -260,7 +325,11 @@ func buildInspectAgentReport(catalog reusableagents.Catalog, name string) (inspe
)
}

func writeAgentsListText(out io.Writer, catalog reusableagents.Catalog) error {
func writeAgentsListText(
out io.Writer,
catalog reusableagents.Catalog,
bundledAgents []bundledReusableAgentListItem,
) error {
if len(catalog.Agents) == 0 {
if _, err := fmt.Fprintln(
out,
Expand Down Expand Up @@ -288,6 +357,26 @@ func writeAgentsListText(out io.Writer, catalog reusableagents.Catalog) error {
}
}

if len(bundledAgents) > 0 {
if _, err := fmt.Fprintf(out, "\nsetup-managed reusable agents: %d\n", len(bundledAgents)); err != nil {
return err
}
for i := range bundledAgents {
agent := &bundledAgents[i]
if _, err := fmt.Fprintf(
out,
"\n%s\n source: bundled\n status: %s\n scope: %s\n target: %s\n description: %s\n",
agent.Agent.Name,
agent.State,
agent.Scope,
blankFallback(agent.TargetPath),
blankFallback(agent.Agent.Description),
); err != nil {
return err
}
}
}

if len(catalog.Problems) == 0 {
return nil
}
Expand Down
Loading