diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
new file mode 100644
index 0000000..4ad6713
--- /dev/null
+++ b/.agents/plugins/marketplace.json
@@ -0,0 +1,20 @@
+{
+ "name": "fluencyloop",
+ "interface": {
+ "displayName": "FluencyLoop"
+ },
+ "plugins": [
+ {
+ "name": "fluencyloop",
+ "source": {
+ "source": "local",
+ "path": "./plugins/fluencyloop"
+ },
+ "policy": {
+ "installation": "AVAILABLE",
+ "authentication": "ON_INSTALL"
+ },
+ "category": "Productivity"
+ }
+ ]
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cfd3d15..8bc7920 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,13 +14,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: ShellCheck (scripts, CLI, installer, plugin launcher)
+ - name: ShellCheck (bundled runtime and Claude launcher)
# shellcheck is preinstalled on the ubuntu runner. -P SCRIPTDIR lets it follow the
# `# shellcheck source=common.sh` directives so sourced helpers are analysed too.
- run: shellcheck -x -P SCRIPTDIR scripts/bash/*.sh fluencyloop install.sh bin/fluencyloop
+ run: shellcheck -x -P SCRIPTDIR plugins/fluencyloop/scripts/bash/*.sh plugins/fluencyloop/fluencyloop bin/fluencyloop
- name: Ban eval (shell=True-equivalent footgun)
run: |
- if grep -rnE '(^|[^[:alnum:]_])eval[[:space:]]' scripts/bash fluencyloop install.sh bin/fluencyloop; then
+ if grep -rnE '(^|[^[:alnum:]_])eval[[:space:]]' plugins/fluencyloop/scripts/bash plugins/fluencyloop/fluencyloop bin/fluencyloop; then
echo "::error::eval is banned in FluencyLoop shell — it runs arbitrary strings as code."
exit 1
fi
@@ -86,11 +86,10 @@ jobs:
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
- name: PSScriptAnalyzer
run: |
- $set = 'scripts/powershell/PSScriptAnalyzerSettings.psd1'
+ $set = 'plugins/fluencyloop/scripts/powershell/PSScriptAnalyzerSettings.psd1'
$r = @()
- $r += Invoke-ScriptAnalyzer -Path scripts/powershell -Recurse -Settings $set -Severity Warning, Error
- $r += Invoke-ScriptAnalyzer -Path fluencyloop.ps1 -Settings $set -Severity Warning, Error
- $r += Invoke-ScriptAnalyzer -Path install.ps1 -Settings $set -Severity Warning, Error
+ $r += Invoke-ScriptAnalyzer -Path plugins/fluencyloop/scripts/powershell -Recurse -Settings $set -Severity Warning, Error
+ $r += Invoke-ScriptAnalyzer -Path plugins/fluencyloop/fluencyloop.ps1 -Settings $set -Severity Warning, Error
if ($r) { $r | Format-Table -AutoSize | Out-String | Write-Host; throw "PSScriptAnalyzer found $($r.Count) issue(s)" }
Write-Host 'PSScriptAnalyzer: clean'
- name: Pester
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0bd446..649a37a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@
All notable changes to FluencyLoop are documented here.
+## Unreleased
+
+### Changed
+
+- FluencyLoop is now distributed as both a Claude Code marketplace plugin and a Codex marketplace
+ plugin. The canonical runtime lives in `plugins/fluencyloop/`.
+- Retired `install.sh`, `install.ps1`, and `fluencyloop self upgrade`; agent plugin managers now
+ own installation and updates.
+
## 0.2.0
### Added
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6d03e50..c9cba79 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,10 +7,10 @@ actively dogfooded, so expect rough edges and fast-moving changes.
The scripts switch branches and write files in your repo, so they're tested. CI runs
[`shellcheck`](https://www.shellcheck.net/) + a [`bats`](https://github.com/bats-core/bats-core)
-suite on every push and PR; run them locally with `shellcheck -x -P SCRIPTDIR scripts/bash/*.sh`
-and `bats tests`.
+suite on every push and PR; run them locally with
+`shellcheck -x -P SCRIPTDIR plugins/fluencyloop/scripts/bash/*.sh` and `bats tests`.
-> **Distribution roadmap:** today it's clone + `install.sh`. Packaging the skills as a Claude
-> Code **plugin/marketplace** entry (one-click install for others) and publishing the CLI
-> (homebrew/npm) are the next distribution steps — not required to use.
+> **Distribution:** FluencyLoop ships through its Claude Code and Codex marketplace plugins.
+> The canonical runtime lives in `plugins/fluencyloop/`; do not add a machine-wide installer or
+> copy skills into a user's agent directory.
diff --git a/MANIFESTO.md b/MANIFESTO.md
index 863fcea..31e996a 100644
--- a/MANIFESTO.md
+++ b/MANIFESTO.md
@@ -126,24 +126,21 @@ file skeletons that a script can create, and does not repeatedly ask a question
private preferences. The journal is more reliable because its structure is deterministic; the
model spends its tokens on the reasoning a script cannot supply.
-## Command surface
-
-```bash
-fluencyloop init # scaffold FluencyLoop in this repository
-fluencyloop plan "split the monolith" # optional initiative map
-fluencyloop feature "add rate limiting" # branch + design + active feature state
-fluencyloop session "wire Redis store" # open a build session
-fluencyloop review # assemble the reviewer-facing view
-fluencyloop backfill # reconstruct skipped work after merge
-fluencyloop check # inspect state and un-journaled drift
-fluencyloop calibration init # initialise private teaching calibration
-fluencyloop calibration show # inspect the profile
-fluencyloop version # print the installed release
-fluencyloop self upgrade # refresh an installed copy from its source checkout
-```
+## Agent command surface
+
+FluencyLoop is activated through its agent plugins, not a global installer. Start an explicit
+stage command in the agent you use:
+
+| Goal | Claude Code | Codex |
+|---|---|---|
+| Plan a large initiative | `/fluencyloop:plan` | `$fluencyloop-plan` |
+| Build a feature | `/fluencyloop:feature` | `$fluencyloop-feature` |
+| Assemble its PR view | `/fluencyloop:review` | `$fluencyloop-review` |
+| Backfill skipped work | `/fluencyloop:backfill` | `$fluencyloop-backfill` |
-The CLI is useful on its own. Agent skills supply the interactive loop and are installed through
-the relevant agent's distribution mechanism.
+The skills invoke the bundled deterministic CLI for `init`, feature/session scaffolding, review
+assembly, and private calibration. The CLI is deliberately not a separate machine-wide install:
+the agent's plugin manager owns installation and updates.
## What FluencyLoop refuses to be
@@ -173,11 +170,11 @@ The design has real costs.
## Status: 0.2.0
-0.2.0 is a working cross-platform loop, not a manifesto-only concept: the Bash and PowerShell
-CLIs scaffold and inspect state, construct feature and session records, assemble review context,
-maintain private calibration, and support self-upgrade. The next measure of success is not a more
-elaborate process; it is whether real contributors stay more fluent with a cost they are willing to
-pay.
+0.2.0 is a working cross-agent loop, not a manifesto-only concept: its Claude Code and Codex
+plugins ship the same deterministic runtime to scaffold and inspect state, construct feature and
+session records, assemble review context, and maintain private calibration. The next measure of
+success is not a more elaborate process; it is whether real contributors stay more fluent with a
+cost they are willing to pay.
## Standing principles
diff --git a/README.md b/README.md
index c495466..7dd65c2 100644
--- a/README.md
+++ b/README.md
@@ -41,9 +41,9 @@ principles from real decisions. Nothing gates a merge — work that skips the lo
**after** merge by `backfill`.
**Requires:** a coding agent ([Claude Code](https://claude.com/claude-code) or
-[Codex](https://developers.openai.com/codex/)) plus `git`, and a
-shell to run the CLI — `bash` (macOS/Linux/Git Bash/WSL) or **PowerShell 7** (Windows). The
-`fluencyloop` CLI runs standalone; the interactive skills need the agent.
+[Codex](https://developers.openai.com/codex/)), `git`, and `bash` (macOS/Linux/Git Bash/WSL).
+The deterministic CLI is bundled inside the agent plugins; there is no separate machine-wide
+installer or project skill vendoring step.
## Teaches to your level
@@ -74,91 +74,35 @@ The plugin includes the interactive skills and a bundled `fluencyloop` command f
Bash tool. Its skills are intentionally namespaced, for example
`/fluencyloop:feature`, so they cannot collide with another plugin's skills.
-### Codex or a standalone terminal CLI
+### Codex
-Clone and install the CLI. Use `--agent codex` to install the current Codex skills as well; omit
-that flag (or pass `--no-skills`) when you only want the terminal command.
+Install FluencyLoop from the same repository marketplace:
```bash
-git clone https://github.com/baokhang83/fluencyloop && cd fluencyloop
-./install.sh --agent codex
+codex plugin marketplace add baokhang83/fluencyloop
+codex plugin add fluencyloop@fluencyloop
```
-This copies the tool into `~/.fluencyloop/lib` and puts `fluencyloop` on your PATH
-(`~/.local/bin` — make sure that's on your `$PATH`).
-
-### Initialise a project
-
-Inside a repository you want to use FluencyLoop on:
-
-```bash
-fluencyloop init
-```
-
-This scaffolds that repo's `.fluencyloop/` state (scripts, templates, a constitution stub) and
-adds the calibration `.gitignore` guard.
-
-### On Windows
-
-Two ways to run it:
-
-**Native PowerShell CLI** ([PowerShell 7](https://aka.ms/powershell)). Use this when you want the
-CLI outside Claude Code. From a clone:
-
-```powershell
-./install.ps1 -NoSkills
-```
-
-This copies the tool into `%USERPROFILE%\.fluencyloop\lib` and adds it to your user PATH. Then
-`fluencyloop ` works from PowerShell **and** cmd (via a `.cmd` shim), with the same verbs
-and `--json` output as the bash CLI — `fluencyloop version` and `fluencyloop self upgrade`
-included. Use `./install.ps1 -Agent codex` for Codex; install Claude Code through the marketplace
-above.
-
-**Git Bash / WSL.** The bash tool also runs unchanged in **Git Bash** (bundled with
-[Git for Windows](https://git-scm.com/download/win)) or **WSL** — use `./install.sh` there.
-
-Both shells are verified on a Windows CI runner: the bash suite via Git Bash, and the PowerShell
-port via `PSScriptAnalyzer` + a `Pester` suite that mirrors the bash tests.
+The plugin makes the `$fluencyloop-*` skills available. Its bundled CLI stays private to the
+plugin and is run by those skills, so it never needs to be copied onto your PATH.
## Quickstart
-From inside the repository you want to work on:
-
-```bash
-# Scaffold the project once.
-fluencyloop init
+Inside the repository you want to work on, invoke the workflow stage in your installed agent.
+The plan and feature stages initialise `.fluencyloop/` automatically when needed.
-# Choose one path. For a large initiative, make its architecture and roadmap first.
-fluencyloop plan "revamp the checkout flow"
-
-# For a normal-sized change, start a feature: this creates its branch, design stub, and session journal.
-fluencyloop feature "add rate limiting to the API"
-```
-
-Use **one** of the last two commands: `plan` is optional and is for work too large for one
-feature; it creates an architecture + roadmap under `docs/fluencyloop/plans/`. Build each roadmap
-item as a feature. `feature` creates the `feature/add-rate-limiting` branch and drops a design doc
-and a session journal under `docs/fluencyloop/`. As you build, your agent teaches the *why* of each
-real decision at the slice boundary and records it in the journal. When you're ready to open a PR:
-
-```bash
-fluencyloop review
-```
-
-…assembles the reviewer-facing PR view straight from those journals — no manual linking,
-because a feature *is* its branch. Shipped something without the loop? `fluencyloop backfill`
-reconstructs the journal after merge.
-
-### Useful commands
+| Goal | Claude Code | Codex |
+|------|-------------|-------|
+| Plan a large initiative — architecture + roadmap | `/fluencyloop:plan revamp the checkout flow` | `$fluencyloop-plan revamp the checkout flow` |
+| Build a normal-sized feature — design → build + teach | `/fluencyloop:feature add rate limiting to the API` | `$fluencyloop-feature add rate limiting to the API` |
+| Assemble the feature's PR view | `/fluencyloop:review` | `$fluencyloop-review` |
+| Document merged work that skipped the loop | `/fluencyloop:backfill` | `$fluencyloop-backfill` |
-```bash
-fluencyloop check # inspect the active feature and un-journaled drift
-fluencyloop version # print the installed release
-fluencyloop self upgrade # refresh an installed copy from its source checkout
-fluencyloop calibration show # inspect your private teaching profile
-fluencyloop calibration edit # adjust it yourself, if you want to
-```
+Use **plan** only for work too large for one feature branch. It creates an architecture + roadmap
+under `docs/fluencyloop/plans/`; build each roadmap item as a feature. A feature creates its
+branch, design, and session journal under `docs/fluencyloop/`, teaches the *why* of each real
+decision at the slice boundary, and records it. Review assembles the reviewer-facing view from
+those journals, because a feature *is* its branch.
### Calibration
@@ -177,18 +121,6 @@ slice diff rather than whole files, asks only what the calibration profile does
records rationale at the moment it is still grounded in the change. See [the efficiency
principle](MANIFESTO.md#efficiency-is-a-product-principle).
-## Use it
-
-| Step | Claude Code | Codex |
-|------|-------------|-------|
-| *(optionally)* Plan a big chunk — architecture + roadmap | `/fluencyloop:plan` | `$fluencyloop-plan` |
-| Build a feature — design → build + teach *(per feature)* | `/fluencyloop:feature` | `$fluencyloop-feature` |
-| Review — the PR view assembles itself *(per feature)* | `/fluencyloop:review` | `$fluencyloop-review` |
-| Backfill — document work that skipped the loop *(post-merge)* | `/fluencyloop:backfill` | `$fluencyloop-backfill` |
-
-You can also describe the task naturally, but invoking the stage skill explicitly makes the
-workflow unmistakable.
-
The **skills** carry the interactive, calibrated behaviour (teaching at slice boundaries,
one-question-at-a-time constitution authoring). The **scripts** carry the deterministic
plumbing (branches, files, PR-view assembly) so the journal is reliable rather than
@@ -197,16 +129,11 @@ left to the model.
## Layout
```
-install.sh / install.ps1 machine install (bash / PowerShell): CLI on PATH; `--agent codex` adds Codex skills
.claude-plugin/ Claude Code plugin manifest + self-hosted marketplace catalog
+.agents/plugins/ Codex marketplace catalog
claude-skills/ Claude-only aliases: `plan`, `feature`, `review`, `backfill`
bin/ the plugin's bundled `fluencyloop` launchers
-fluencyloop{,.ps1,.cmd} CLI dispatcher — verbs: init / plan / feature / session / decision / review / check / slice-context / calibration / version / self upgrade
-VERSION the current version (0.2.0); `fluencyloop version` prints it
-scripts/bash/ deterministic plumbing — bash (the reference implementation)
-scripts/powershell/ the same plumbing, ported to PowerShell (Windows-native)
-templates/ .fluencyloop state templates (constitution, design, session)
-skills/ the interactive skills (activated for Claude Code or Codex)
+plugins/fluencyloop/ Codex plugin and canonical runtime: CLI, skills, scripts, templates
tests/ bats suite (bash) + tests/powershell Pester suite (mirror)
MANIFESTO.md the why
```
diff --git a/bin/fluencyloop b/bin/fluencyloop
index c266b71..08527f5 100755
--- a/bin/fluencyloop
+++ b/bin/fluencyloop
@@ -3,4 +3,4 @@
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-exec "$ROOT/fluencyloop" "$@"
+exec "$ROOT/plugins/fluencyloop/fluencyloop" "$@"
diff --git a/bin/fluencyloop.cmd b/bin/fluencyloop.cmd
index 62d92e1..f473837 100644
--- a/bin/fluencyloop.cmd
+++ b/bin/fluencyloop.cmd
@@ -1,2 +1,2 @@
@echo off
-pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\fluencyloop.ps1" %*
+pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\plugins\fluencyloop\fluencyloop.ps1" %*
diff --git a/claude-skills/feature/SKILL.md b/claude-skills/feature/SKILL.md
index 61ea400..8950131 100644
--- a/claude-skills/feature/SKILL.md
+++ b/claude-skills/feature/SKILL.md
@@ -20,8 +20,9 @@ permission to bury a real question in prose.
## 0. Preconditions
-Confirm `.fluencyloop/` exists (`fluencyloop check` reports it). If it does not, tell
-the user to run `fluencyloop init` first, and stop.
+Confirm `.fluencyloop/` exists (`fluencyloop check --json` reports it). If it is absent, run
+`fluencyloop init` yourself, say that the repository is now initialised, and continue. Only stop
+if `init` itself fails (for example, because the directory is not a Git repository).
**Read the loop state.** If `.fluencyloop/state.json` exists, read it *first* — it is the loop's
single source of truth for the active feature (`feature` slug, `branch`, `stage`, `last_session`,
diff --git a/claude-skills/plan/SKILL.md b/claude-skills/plan/SKILL.md
index 7fd4d1a..2a66638 100644
--- a/claude-skills/plan/SKILL.md
+++ b/claude-skills/plan/SKILL.md
@@ -20,8 +20,9 @@ create issues, write a settled choice, or advance the workflow until the develop
## 0. Preconditions
-Confirm `.fluencyloop/` exists (`fluencyloop check` reports it). If not, tell the user
-to run `fluencyloop init` first, and stop.
+Confirm `.fluencyloop/` exists (`fluencyloop check --json` reports it). If it is absent, run
+`fluencyloop init` yourself, say that the repository is now initialised, and continue. Only stop
+if `init` itself fails (for example, because the directory is not a Git repository).
**Read the constitution up front** — `docs/fluencyloop/constitution.md`, and **if it's a pointer**
(a `Source of truth:` line naming another file, e.g. `.specify/memory/constitution.md`), read
diff --git a/install.ps1 b/install.ps1
deleted file mode 100644
index 05fac4d..0000000
--- a/install.ps1
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env pwsh
-# install.ps1 — install FluencyLoop onto a Windows machine (the PowerShell twin of install.sh).
-# Copies the tool into %USERPROFILE%\.fluencyloop\lib, puts the `fluencyloop` CLI on PATH, installs
-# the interactive skills user-wide for the selected coding agent, and records VERSION/SOURCE for self upgrade.
-#
-# Usage: ./install.ps1 [-Agent claude|codex|both] [-NoSkills]
-
-Set-StrictMode -Version Latest
-$ErrorActionPreference = 'Stop'
-
-# $IsWindows is a read-only automatic var in PowerShell 7 but undefined in Windows PowerShell 5.x,
-# so derive a portable flag (don't assign to the automatic var).
-$onWindows = if (Test-Path variable:IsWindows) { $IsWindows } else { $env:OS -eq 'Windows_NT' }
-
-$SRC = $PSScriptRoot
-$installSkills = $true
-$skillsAgent = 'claude'
-for ($i = 0; $i -lt $args.Count; $i++) {
- if ($args[$i] -eq '-NoSkills' -or $args[$i] -eq '--no-skills') { $installSkills = $false }
- elseif ($args[$i] -eq '-Agent' -or $args[$i] -eq '--agent') {
- $i++; if ($i -ge $args.Count) { throw '-Agent needs claude, codex, or both' }; $skillsAgent = [string]$args[$i]
- }
-}
-if ($skillsAgent -notin @('claude', 'codex', 'both')) { throw "-Agent must be claude, codex, or both (got '$skillsAgent')" }
-
-$base = if ($env:FLUENCYLOOP_HOME) { $env:FLUENCYLOOP_HOME } else { Join-Path $HOME '.fluencyloop' }
-$LIB = Join-Path $base 'lib'
-
-function WriteTextLf([string]$path, [string]$text) {
- $dir = Split-Path -Parent $path
- if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
- [System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))
-}
-
-# 1. Copy the tool into the lib dir (idempotent: refresh in place).
-New-Item -ItemType Directory -Force -Path $LIB | Out-Null
-foreach ($d in 'scripts', 'templates', 'skills') {
- $t = Join-Path $LIB $d
- if (Test-Path -LiteralPath $t) { Remove-Item -Recurse -Force $t }
-}
-Copy-Item -Recurse -Force -Path (Join-Path $SRC 'scripts') -Destination $LIB
-Copy-Item -Recurse -Force -Path (Join-Path $SRC 'templates') -Destination $LIB
-Copy-Item -Recurse -Force -Path (Join-Path $SRC 'skills') -Destination $LIB
-Copy-Item -Force -Path (Join-Path $SRC 'fluencyloop.ps1') -Destination $LIB
-Copy-Item -Force -Path (Join-Path $SRC 'fluencyloop.cmd') -Destination $LIB
-Copy-Item -Force -Path (Join-Path $SRC 'VERSION') -Destination $LIB
-WriteTextLf (Join-Path $LIB 'SOURCE') "$SRC`n" # where `fluencyloop self upgrade` pulls from
-
-# 2. Put the CLI on the PATH (Windows: add lib to the user PATH; the .cmd shim resolves the verb).
-$pathAdded = $false
-if ($onWindows) {
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if (-not $userPath) { $userPath = '' }
- if (($userPath -split ';') -notcontains $LIB) {
- [Environment]::SetEnvironmentVariable('Path', ($LIB + ';' + $userPath), 'User')
- $pathAdded = $true
- }
-}
-
-# 3. Install skills for the selected agent. Claude remains the default for backwards compatibility.
-$claudeSkillsDest = Join-Path $HOME '.claude/skills'
-$codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' }
-$codexSkillsDest = Join-Path $codexHome 'skills'
-if ($installSkills) {
- $skillsDests = switch ($skillsAgent) {
- 'claude' { @($claudeSkillsDest) }
- 'codex' { @($codexSkillsDest) }
- 'both' { @($claudeSkillsDest, $codexSkillsDest) }
- }
- foreach ($skillsDest in $skillsDests) {
- New-Item -ItemType Directory -Force -Path $skillsDest | Out-Null
- Copy-Item -Recurse -Force -Path (Join-Path $SRC 'skills/*') -Destination $skillsDest
- }
-}
-
-$version = (Get-Content -LiteralPath (Join-Path $SRC 'VERSION') -Raw).Trim()
-Write-Output "FluencyLoop $version installed."
-Write-Output " lib: $LIB"
-Write-Output " cli: $(Join-Path $LIB 'fluencyloop.cmd') (run as: fluencyloop)"
-if ($installSkills) {
- Write-Output " skills: $skillsAgent ($($skillsDests -join ', '))"
-}
-Write-Output ''
-if ($onWindows) {
- if ($pathAdded) { Write-Output "Added $LIB to your user PATH - open a new terminal for fluencyloop to resolve." }
-} else {
- Write-Output "Non-Windows shell: add $LIB to your PATH to run fluencyloop (or use the bash install.sh)."
-}
-Write-Output "Next: cd into a project and run 'fluencyloop init'."
diff --git a/install.sh b/install.sh
deleted file mode 100755
index 0561d78..0000000
--- a/install.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/usr/bin/env bash
-# install.sh — install FluencyLoop onto this machine (run once, from a clone of this repo).
-#
-# It does three things:
-# 1. copies the tool into ~/.fluencyloop/lib (scripts, templates, skills, CLI)
-# 2. symlinks the `fluencyloop` CLI onto your PATH (~/.local/bin by default)
-# 3. installs the interactive skills user-wide for the selected coding agent
-#
-# After this, `fluencyloop init` works in any repo, and your coding agent sees the skills
-# everywhere. Per-project state still lives in each repo's .fluencyloop/ (via `fluencyloop init`).
-#
-# Usage: ./install.sh [--bin-dir ] [--agent ] [--no-skills]
-
-set -euo pipefail
-
-SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-LIB="${FLUENCYLOOP_HOME:-$HOME/.fluencyloop}/lib"
-BIN_DIR="$HOME/.local/bin"
-INSTALL_SKILLS=true
-SKILLS_AGENT="claude"
-
-while [ "$#" -gt 0 ]; do
- case "$1" in
- --bin-dir) shift; BIN_DIR="${1:?--bin-dir needs a value}" ;;
- --agent) shift; SKILLS_AGENT="${1:?--agent needs claude, codex, or both}" ;;
- --no-skills) INSTALL_SKILLS=false ;;
- -h|--help) sed -n '2,16p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
- *) echo "Unknown option: $1" >&2; exit 1 ;;
- esac
- shift
-done
-
-case "$SKILLS_AGENT" in claude|codex|both) ;; *)
- echo "--agent must be claude, codex, or both (got '$SKILLS_AGENT')" >&2; exit 1 ;;
-esac
-
-# 1. Copy the tool into the lib dir (idempotent: refresh in place).
-mkdir -p "$LIB"
-rm -rf "$LIB/scripts" "$LIB/templates" "$LIB/skills"
-cp -R "$SRC/scripts" "$SRC/templates" "$SRC/skills" "$LIB/"
-cp "$SRC/fluencyloop" "$LIB/fluencyloop"
-cp "$SRC/VERSION" "$LIB/VERSION"
-# Record where this install came from, so `fluencyloop self upgrade` knows what to pull.
-printf '%s\n' "$SRC" > "$LIB/SOURCE"
-chmod +x "$LIB/fluencyloop" "$LIB/scripts/bash/"*.sh
-
-# 2. Put the CLI on the PATH.
-mkdir -p "$BIN_DIR"
-ln -sf "$LIB/fluencyloop" "$BIN_DIR/fluencyloop"
-
-# 3. Install skills for the selected coding agent. Claude remains the default for backwards
-# compatibility; `--agent both` is explicit for people who use both tools.
-CLAUDE_SKILLS_DEST="$HOME/.claude/skills"
-CODEX_SKILLS_DEST="${CODEX_HOME:-$HOME/.codex}/skills"
-if $INSTALL_SKILLS; then
- install_skills() { mkdir -p "$1"; cp -R "$SRC/skills/." "$1/"; }
- case "$SKILLS_AGENT" in
- claude) SKILLS_DESTS="$CLAUDE_SKILLS_DEST"; install_skills "$CLAUDE_SKILLS_DEST" ;;
- codex) SKILLS_DESTS="$CODEX_SKILLS_DEST"; install_skills "$CODEX_SKILLS_DEST" ;;
- both) SKILLS_DESTS="$CLAUDE_SKILLS_DEST, $CODEX_SKILLS_DEST"; install_skills "$CLAUDE_SKILLS_DEST"; install_skills "$CODEX_SKILLS_DEST" ;;
- esac
-fi
-
-VERSION="$(cat "$SRC/VERSION" 2>/dev/null || echo unknown)"
-echo "FluencyLoop $VERSION installed."
-echo " lib: $LIB"
-echo " cli: $BIN_DIR/fluencyloop -> $LIB/fluencyloop"
-if $INSTALL_SKILLS; then
- echo " skills: $SKILLS_AGENT ($SKILLS_DESTS)"
-fi
-echo
-if ! command -v fluencyloop >/dev/null 2>&1; then
- echo "Add the CLI to your PATH (then restart your shell):"
- echo " echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.zshrc"
-fi
-echo "Next: cd into a project and run 'fluencyloop init'."
diff --git a/plugins/fluencyloop/.codex-plugin/plugin.json b/plugins/fluencyloop/.codex-plugin/plugin.json
new file mode 100644
index 0000000..0afb00d
--- /dev/null
+++ b/plugins/fluencyloop/.codex-plugin/plugin.json
@@ -0,0 +1,38 @@
+{
+ "name": "fluencyloop",
+ "version": "0.2.0",
+ "description": "Stay fluent in the code your AI agent writes with a per-feature design, build, teaching, and review loop.",
+ "author": {
+ "name": "baokhang83",
+ "url": "https://github.com/baokhang83"
+ },
+ "homepage": "https://github.com/baokhang83/fluencyloop#readme",
+ "repository": "https://github.com/baokhang83/fluencyloop",
+ "license": "Apache-2.0",
+ "keywords": [
+ "developer-education",
+ "documentation",
+ "planning",
+ "code-review",
+ "workflow"
+ ],
+ "skills": "./skills/",
+ "interface": {
+ "displayName": "FluencyLoop",
+ "shortDescription": "Stay fluent in AI-assisted code changes.",
+ "longDescription": "Design features before building, teach the important decisions at each slice, journal the rationale, and assemble a reviewer-ready PR view.",
+ "developerName": "baokhang83",
+ "category": "Productivity",
+ "capabilities": [
+ "Interactive",
+ "Read",
+ "Write"
+ ],
+ "websiteURL": "https://github.com/baokhang83/fluencyloop#readme",
+ "defaultPrompt": [
+ "Start a FluencyLoop feature for this change",
+ "Plan this large initiative with FluencyLoop",
+ "Prepare this FluencyLoop feature for review"
+ ]
+ }
+}
diff --git a/VERSION b/plugins/fluencyloop/VERSION
similarity index 100%
rename from VERSION
rename to plugins/fluencyloop/VERSION
diff --git a/fluencyloop b/plugins/fluencyloop/fluencyloop
similarity index 58%
rename from fluencyloop
rename to plugins/fluencyloop/fluencyloop
index 3309b77..3d33ce2 100755
--- a/fluencyloop
+++ b/plugins/fluencyloop/fluencyloop
@@ -2,8 +2,8 @@
# fluencyloop — the FluencyLoop CLI dispatcher.
#
# Deterministic commands run directly; the interactive stages (constitution, feature,
-# review, backfill) are driven by the skills your coding agent loads from .claude/skills or
-# .codex/skills.
+# review, backfill) are driven by the skills supplied by the installed Claude Code or Codex
+# plugin.
# This dispatcher scaffolds a project and exposes the plumbing so the skills — or you — can
# call it.
#
@@ -19,13 +19,11 @@
# fluencyloop calibration your knowledge profile + its ledger
# fluencyloop migrate [--dry-run] move docs from .fluencyloop/ to docs/fluencyloop/
# fluencyloop version print the installed version
-# fluencyloop self upgrade pull the latest distribution and re-install
# fluencyloop help
set -euo pipefail
-# Resolve this script's real location, following symlinks (so a symlinked `fluencyloop` on the
-# PATH still finds its own scripts). Portable: macOS `readlink` has no -f.
+# Resolve this script's real location, following symlinks. Portable: macOS `readlink` has no -f.
resolve_self() {
local src="${BASH_SOURCE[0]}" dir
while [ -h "$src" ]; do
@@ -37,17 +35,13 @@ resolve_self() {
}
SELF="$(resolve_self)"
-# Version + upgrade-source markers live next to this script: at the repo root when running from a
-# checkout, or in the install lib when installed (install.sh copies VERSION and writes SOURCE
-# there). SOURCE records the distribution checkout `self upgrade` pulls from.
+# Version lives beside the bundled CLI.
VERSION_FILE="$SELF/VERSION"
-SOURCE_FILE="$SELF/SOURCE"
read_version() { [ -f "$1" ] && cat "$1" || echo "unknown"; }
-# Find the scripts dir: the distribution checkout / install lib, or the current repo's
-# vendored copy after `fluencyloop init`.
+# Find the bundled scripts dir, or the current project's vendored copy after `fluencyloop init`.
if [ -d "$SELF/scripts/bash" ]; then
- BIN="$SELF/scripts/bash" # running from the fluencyloop checkout or install lib
+ BIN="$SELF/scripts/bash" # running from the bundled plugin runtime
elif [ -d "$SELF/scripts" ] && [ -f "$SELF/scripts/common.sh" ]; then
BIN="$SELF/scripts" # running from an installed .fluencyloop/
else
@@ -73,38 +67,6 @@ case "$cmd" in
calibration) exec bash "$BIN/calibration.sh" "$@" ;;
migrate) exec bash "$BIN/migrate.sh" "$@" ;;
version|--version|-v) read_version "$VERSION_FILE" ;;
- self)
- action="${1:-}"; shift || true
- if [ "$action" != "upgrade" ]; then
- echo "Unknown command: fluencyloop self ${action:-}" >&2; usage >&2; exit 1
- fi
- # Locate the distribution checkout to pull from: the path install.sh recorded, or this
- # script's own dir if we're running straight from a checkout.
- if [ -f "$SOURCE_FILE" ]; then
- SRC="$(cat "$SOURCE_FILE")"
- elif [ -d "$SELF/.git" ]; then
- SRC="$SELF"
- else
- SRC=""
- fi
- if [ -z "$SRC" ] || [ ! -d "$SRC/.git" ]; then
- echo "Can't find a git checkout to upgrade from (source: ${SRC:-none})." >&2
- echo "Re-clone and re-install:" >&2
- echo " git clone https://github.com/baokhang83/fluencyloop && cd fluencyloop && ./install.sh" >&2
- exit 1
- fi
- OLD="$(read_version "$VERSION_FILE")"
- echo "Upgrading from $SRC ..."
- git -C "$SRC" pull --ff-only
- NEW="$(read_version "$SRC/VERSION")"
- bash "$SRC/install.sh" "$@"
- echo
- if [ "$OLD" = "$NEW" ]; then
- echo "FluencyLoop is up to date ($NEW)."
- else
- echo "FluencyLoop upgraded: $OLD -> $NEW"
- fi
- ;;
help|-h|--help) usage ;;
*) echo "Unknown command: $cmd" >&2; usage >&2; exit 1 ;;
esac
diff --git a/fluencyloop.cmd b/plugins/fluencyloop/fluencyloop.cmd
similarity index 100%
rename from fluencyloop.cmd
rename to plugins/fluencyloop/fluencyloop.cmd
diff --git a/fluencyloop.ps1 b/plugins/fluencyloop/fluencyloop.ps1
similarity index 63%
rename from fluencyloop.ps1
rename to plugins/fluencyloop/fluencyloop.ps1
index afd5760..44818ae 100644
--- a/fluencyloop.ps1
+++ b/plugins/fluencyloop/fluencyloop.ps1
@@ -6,14 +6,13 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
-$SELF = $PSScriptRoot # this script's dir: the install lib, or the repo/checkout root
+$SELF = $PSScriptRoot # this script's bundled plugin directory
$VersionFile = Join-Path $SELF 'VERSION'
-$SourceFile = Join-Path $SELF 'SOURCE'
function ReadVersion([string]$p) { if (Test-Path -LiteralPath $p) { (Get-Content -LiteralPath $p -Raw).Trim() } else { 'unknown' } }
-# Find the scripts dir: the checkout / install lib, the installed .fluencyloop/, or the current
-# repo's vendored copy after `fluencyloop init`.
+# Find the bundled scripts dir, the installed .fluencyloop/ copy, or the current repository's
+# vendored copy after `fluencyloop init`.
if (Test-Path -LiteralPath (Join-Path $SELF 'scripts/powershell')) {
$BIN = Join-Path $SELF 'scripts/powershell'
} elseif ((Test-Path -LiteralPath (Join-Path $SELF 'scripts/common.ps1'))) {
@@ -27,7 +26,7 @@ $usage = @'
fluencyloop — the FluencyLoop CLI dispatcher.
Deterministic commands run directly; the interactive stages (constitution, feature, review,
-backfill) are driven by the skills your coding agent loads from .claude/skills or .codex/skills.
+backfill) are driven by the skills supplied by the installed Claude Code or Codex plugin.
Usage:
fluencyloop init scaffold .fluencyloop/ state + docs/fluencyloop/
@@ -41,7 +40,6 @@ Usage:
fluencyloop calibration your knowledge profile + its ledger
fluencyloop migrate [--dry-run] move docs from .fluencyloop/ to docs/fluencyloop/
fluencyloop version print the installed version
- fluencyloop self upgrade pull the latest distribution and re-install
fluencyloop help
'@
@@ -67,28 +65,6 @@ switch -Regex ($cmd) {
'^calibration$' { Run 'calibration.ps1' }
'^migrate$' { Run 'migrate.ps1' }
'^(version|--version|-v)$' { [Console]::Out.Write((ReadVersion $VersionFile) + "`n"); exit 0 }
- '^self$' {
- $action = if ($rest.Count -ge 1) { [string]$rest[0] } else { '' }
- if ($action -ne 'upgrade') { [Console]::Error.WriteLine("Unknown command: fluencyloop self $action"); [Console]::Error.WriteLine($usage); exit 1 }
- if (Test-Path -LiteralPath $SourceFile) { $src = (Get-Content -LiteralPath $SourceFile -Raw).Trim() }
- elseif (Test-Path -LiteralPath (Join-Path $SELF '.git')) { $src = $SELF }
- else { $src = '' }
- if (-not $src -or -not (Test-Path -LiteralPath (Join-Path $src '.git'))) {
- [Console]::Error.WriteLine("Can't find a git checkout to upgrade from (source: $(if ($src) { $src } else { 'none' })).")
- [Console]::Error.WriteLine('Re-clone and re-install:')
- [Console]::Error.WriteLine(' git clone https://github.com/baokhang83/fluencyloop; cd fluencyloop; ./install.ps1')
- exit 1
- }
- $old = ReadVersion $VersionFile
- [Console]::Out.Write("Upgrading from $src ...`n")
- & git -C $src pull --ff-only
- $new = ReadVersion (Join-Path $src 'VERSION')
- $after = @(); if ($rest.Count -gt 1) { $after = @($rest[1..($rest.Count - 1)]) }
- & (Join-Path $src 'install.ps1') @after
- if ($old -eq $new) { [Console]::Out.Write("`nFluencyLoop is up to date ($new).`n") }
- else { [Console]::Out.Write("`nFluencyLoop upgraded: $old -> $new`n") }
- exit 0
- }
'^(help|-h|--help)$' { [Console]::Out.Write($usage + "`n"); exit 0 }
default { [Console]::Error.WriteLine("Unknown command: $cmd"); [Console]::Error.WriteLine($usage); exit 1 }
}
diff --git a/scripts/bash/add-decision.sh b/plugins/fluencyloop/scripts/bash/add-decision.sh
similarity index 100%
rename from scripts/bash/add-decision.sh
rename to plugins/fluencyloop/scripts/bash/add-decision.sh
diff --git a/scripts/bash/assemble-pr-view.sh b/plugins/fluencyloop/scripts/bash/assemble-pr-view.sh
similarity index 100%
rename from scripts/bash/assemble-pr-view.sh
rename to plugins/fluencyloop/scripts/bash/assemble-pr-view.sh
diff --git a/scripts/bash/calibration.sh b/plugins/fluencyloop/scripts/bash/calibration.sh
similarity index 100%
rename from scripts/bash/calibration.sh
rename to plugins/fluencyloop/scripts/bash/calibration.sh
diff --git a/scripts/bash/check.sh b/plugins/fluencyloop/scripts/bash/check.sh
similarity index 100%
rename from scripts/bash/check.sh
rename to plugins/fluencyloop/scripts/bash/check.sh
diff --git a/scripts/bash/common.sh b/plugins/fluencyloop/scripts/bash/common.sh
similarity index 100%
rename from scripts/bash/common.sh
rename to plugins/fluencyloop/scripts/bash/common.sh
diff --git a/scripts/bash/init.sh b/plugins/fluencyloop/scripts/bash/init.sh
similarity index 100%
rename from scripts/bash/init.sh
rename to plugins/fluencyloop/scripts/bash/init.sh
diff --git a/scripts/bash/migrate.sh b/plugins/fluencyloop/scripts/bash/migrate.sh
similarity index 100%
rename from scripts/bash/migrate.sh
rename to plugins/fluencyloop/scripts/bash/migrate.sh
diff --git a/scripts/bash/new-feature.sh b/plugins/fluencyloop/scripts/bash/new-feature.sh
similarity index 100%
rename from scripts/bash/new-feature.sh
rename to plugins/fluencyloop/scripts/bash/new-feature.sh
diff --git a/scripts/bash/new-plan.sh b/plugins/fluencyloop/scripts/bash/new-plan.sh
similarity index 100%
rename from scripts/bash/new-plan.sh
rename to plugins/fluencyloop/scripts/bash/new-plan.sh
diff --git a/scripts/bash/new-session.sh b/plugins/fluencyloop/scripts/bash/new-session.sh
similarity index 100%
rename from scripts/bash/new-session.sh
rename to plugins/fluencyloop/scripts/bash/new-session.sh
diff --git a/scripts/bash/slice-context.sh b/plugins/fluencyloop/scripts/bash/slice-context.sh
similarity index 100%
rename from scripts/bash/slice-context.sh
rename to plugins/fluencyloop/scripts/bash/slice-context.sh
diff --git a/scripts/powershell/PSScriptAnalyzerSettings.psd1 b/plugins/fluencyloop/scripts/powershell/PSScriptAnalyzerSettings.psd1
similarity index 100%
rename from scripts/powershell/PSScriptAnalyzerSettings.psd1
rename to plugins/fluencyloop/scripts/powershell/PSScriptAnalyzerSettings.psd1
diff --git a/scripts/powershell/add-decision.ps1 b/plugins/fluencyloop/scripts/powershell/add-decision.ps1
similarity index 100%
rename from scripts/powershell/add-decision.ps1
rename to plugins/fluencyloop/scripts/powershell/add-decision.ps1
diff --git a/scripts/powershell/assemble-pr-view.ps1 b/plugins/fluencyloop/scripts/powershell/assemble-pr-view.ps1
similarity index 100%
rename from scripts/powershell/assemble-pr-view.ps1
rename to plugins/fluencyloop/scripts/powershell/assemble-pr-view.ps1
diff --git a/scripts/powershell/calibration.ps1 b/plugins/fluencyloop/scripts/powershell/calibration.ps1
similarity index 100%
rename from scripts/powershell/calibration.ps1
rename to plugins/fluencyloop/scripts/powershell/calibration.ps1
diff --git a/scripts/powershell/check.ps1 b/plugins/fluencyloop/scripts/powershell/check.ps1
similarity index 100%
rename from scripts/powershell/check.ps1
rename to plugins/fluencyloop/scripts/powershell/check.ps1
diff --git a/scripts/powershell/common.ps1 b/plugins/fluencyloop/scripts/powershell/common.ps1
similarity index 100%
rename from scripts/powershell/common.ps1
rename to plugins/fluencyloop/scripts/powershell/common.ps1
diff --git a/scripts/powershell/init.ps1 b/plugins/fluencyloop/scripts/powershell/init.ps1
similarity index 100%
rename from scripts/powershell/init.ps1
rename to plugins/fluencyloop/scripts/powershell/init.ps1
diff --git a/scripts/powershell/migrate.ps1 b/plugins/fluencyloop/scripts/powershell/migrate.ps1
similarity index 100%
rename from scripts/powershell/migrate.ps1
rename to plugins/fluencyloop/scripts/powershell/migrate.ps1
diff --git a/scripts/powershell/new-feature.ps1 b/plugins/fluencyloop/scripts/powershell/new-feature.ps1
similarity index 100%
rename from scripts/powershell/new-feature.ps1
rename to plugins/fluencyloop/scripts/powershell/new-feature.ps1
diff --git a/scripts/powershell/new-plan.ps1 b/plugins/fluencyloop/scripts/powershell/new-plan.ps1
similarity index 100%
rename from scripts/powershell/new-plan.ps1
rename to plugins/fluencyloop/scripts/powershell/new-plan.ps1
diff --git a/scripts/powershell/new-session.ps1 b/plugins/fluencyloop/scripts/powershell/new-session.ps1
similarity index 100%
rename from scripts/powershell/new-session.ps1
rename to plugins/fluencyloop/scripts/powershell/new-session.ps1
diff --git a/scripts/powershell/slice-context.ps1 b/plugins/fluencyloop/scripts/powershell/slice-context.ps1
similarity index 100%
rename from scripts/powershell/slice-context.ps1
rename to plugins/fluencyloop/scripts/powershell/slice-context.ps1
diff --git a/skills/fluencyloop-backfill/SKILL.md b/plugins/fluencyloop/skills/fluencyloop-backfill/SKILL.md
similarity index 96%
rename from skills/fluencyloop-backfill/SKILL.md
rename to plugins/fluencyloop/skills/fluencyloop-backfill/SKILL.md
index 6947494..2e8f3b4 100644
--- a/skills/fluencyloop-backfill/SKILL.md
+++ b/plugins/fluencyloop/skills/fluencyloop-backfill/SKILL.md
@@ -18,6 +18,13 @@ components the work touched — the fluency the missing real-time loop never gav
don't just draft and ask "ok?"; you *show them the shapes, rendered,* map each decision onto
them, and confirm decision-by-decision.
+## Bundled CLI (Codex)
+
+Before invoking a deterministic command, set `FLUENCYLOOP_SKILL_DIR` to the absolute path of
+this loaded `skills/fluencyloop-backfill` directory. The bundled command is then
+`"$FLUENCYLOOP_SKILL_DIR/../../fluencyloop"`. Every `fluencyloop …` command below means that
+bundled command; do not require a globally installed CLI.
+
## Question delivery — preserve the pause
For a real answer, choice, or confirmation, use **`AskUserQuestion` in Claude Code**. Codex has
diff --git a/skills/fluencyloop-feature/SKILL.md b/plugins/fluencyloop/skills/fluencyloop-feature/SKILL.md
similarity index 97%
rename from skills/fluencyloop-feature/SKILL.md
rename to plugins/fluencyloop/skills/fluencyloop-feature/SKILL.md
index 14b635b..ba590d1 100644
--- a/skills/fluencyloop-feature/SKILL.md
+++ b/plugins/fluencyloop/skills/fluencyloop-feature/SKILL.md
@@ -10,6 +10,13 @@ the design diagrams and the session journals. You will: (1) declare the feature,
its design, (3) build it in slices — teaching and journaling one or two real decisions at
each slice boundary. Never gate; never lecture. Keep the developer the author.
+## Bundled CLI (Codex)
+
+Before invoking a deterministic command, set `FLUENCYLOOP_SKILL_DIR` to the absolute path of
+this loaded `skills/fluencyloop-feature` directory. The bundled command is then
+`"$FLUENCYLOOP_SKILL_DIR/../../fluencyloop"`. Every `fluencyloop …` command below means that
+bundled command; do not require a globally installed CLI.
+
## Question delivery — preserve the pause
When this workflow needs a real answer, choice, confirmation, or knowledge probe, use
@@ -20,8 +27,9 @@ permission to bury a real question in prose.
## 0. Preconditions
-Confirm `.fluencyloop/` exists (`fluencyloop check` reports it). If it does not, tell
-the user to run `fluencyloop init` first, and stop.
+Confirm `.fluencyloop/` exists (`fluencyloop check --json` reports it). If it is absent, run
+`fluencyloop init` yourself, say that the repository is now initialised, and continue. Only stop
+if `init` itself fails (for example, because the directory is not a Git repository).
**Read the loop state.** If `.fluencyloop/state.json` exists, read it *first* — it is the loop's
single source of truth for the active feature (`feature` slug, `branch`, `stage`, `last_session`,
diff --git a/skills/fluencyloop-plan/SKILL.md b/plugins/fluencyloop/skills/fluencyloop-plan/SKILL.md
similarity index 94%
rename from skills/fluencyloop-plan/SKILL.md
rename to plugins/fluencyloop/skills/fluencyloop-plan/SKILL.md
index 8d8c589..60104eb 100644
--- a/skills/fluencyloop-plan/SKILL.md
+++ b/plugins/fluencyloop/skills/fluencyloop-plan/SKILL.md
@@ -12,6 +12,13 @@ with a critical path, (5) offer to open GitHub tickets under a milestone, (6) ha
off to `fluencyloop-feature`. The plan is a **map you build against, not a spec to ratify** —
do not over-invest. Keep the developer the architect.
+## Bundled CLI (Codex)
+
+Before invoking a deterministic command, set `FLUENCYLOOP_SKILL_DIR` to the absolute path of
+this loaded `skills/fluencyloop-plan` directory. The bundled command is then
+`"$FLUENCYLOOP_SKILL_DIR/../../fluencyloop"`. Every `fluencyloop …` command below means that
+bundled command; do not require a globally installed CLI.
+
## Question delivery — preserve the pause
For a real answer, choice, or confirmation, use **`AskUserQuestion` in Claude Code**. Codex has
@@ -20,8 +27,9 @@ create issues, write a settled choice, or advance the workflow until the develop
## 0. Preconditions
-Confirm `.fluencyloop/` exists (`fluencyloop check` reports it). If not, tell the user
-to run `fluencyloop init` first, and stop.
+Confirm `.fluencyloop/` exists (`fluencyloop check --json` reports it). If it is absent, run
+`fluencyloop init` yourself, say that the repository is now initialised, and continue. Only stop
+if `init` itself fails (for example, because the directory is not a Git repository).
**Read the constitution up front** — `docs/fluencyloop/constitution.md`, and **if it's a pointer**
(a `Source of truth:` line naming another file, e.g. `.specify/memory/constitution.md`), read
diff --git a/skills/fluencyloop-review/SKILL.md b/plugins/fluencyloop/skills/fluencyloop-review/SKILL.md
similarity index 93%
rename from skills/fluencyloop-review/SKILL.md
rename to plugins/fluencyloop/skills/fluencyloop-review/SKILL.md
index a35afa0..4e631c5 100644
--- a/skills/fluencyloop-review/SKILL.md
+++ b/plugins/fluencyloop/skills/fluencyloop-review/SKILL.md
@@ -8,6 +8,13 @@ description: 'FluencyLoop Stage 4. Assemble the reviewer-facing PR view from a f
A **feature is a branch**, so the review view assembles itself: no manual linking. You turn
the feature's sessions into a summary a reviewer can read to get fluent fast.
+## Bundled CLI (Codex)
+
+Before invoking a deterministic command, set `FLUENCYLOOP_SKILL_DIR` to the absolute path of
+this loaded `skills/fluencyloop-review` directory. The bundled command is then
+`"$FLUENCYLOOP_SKILL_DIR/../../fluencyloop"`. Every `fluencyloop …` command below means that
+bundled command; do not require a globally installed CLI.
+
## Question delivery — preserve the pause
For a real answer, choice, or confirmation, use **`AskUserQuestion` in Claude Code**. Codex has
diff --git a/skills/fluencyloop/SKILL.md b/plugins/fluencyloop/skills/fluencyloop/SKILL.md
similarity index 91%
rename from skills/fluencyloop/SKILL.md
rename to plugins/fluencyloop/skills/fluencyloop/SKILL.md
index 9a44d53..092bd6f 100644
--- a/skills/fluencyloop/SKILL.md
+++ b/plugins/fluencyloop/skills/fluencyloop/SKILL.md
@@ -7,6 +7,13 @@ description: 'FluencyLoop — stay fluent in code as AI writes it. Router/overvi
*The code and your fluency in it are produced together, or not at all.*
+## Bundled CLI (Codex)
+
+Before invoking a deterministic command, set `FLUENCYLOOP_SKILL_DIR` to the absolute path of
+this loaded `skills/fluencyloop` directory. The bundled command is then
+`"$FLUENCYLOOP_SKILL_DIR/../../fluencyloop"`. Every `fluencyloop …` command in this skill means
+that bundled command; it is **not** a command the developer must install on their PATH.
+
## Question delivery
FluencyLoop's stage skills use **`AskUserQuestion` in Claude Code** for genuine prompts. Codex
@@ -43,7 +50,7 @@ repeatable stances from real decisions. Same law as the journal and the calibrat
| Document work that shipped without the loop | **fluencyloop-backfill** |
If the user just says "fluency" or "set up FluencyLoop" and `.fluencyloop/` does not exist yet,
-run `fluencyloop init` (it scaffolds the state + an **empty** constitution stub), then go to
+run the bundled `fluencyloop init` (it scaffolds the state + an **empty** constitution stub), then go to
**fluencyloop-feature** (or **fluencyloop-plan** for a big chunk) — the constitution fills itself
in from there.
diff --git a/templates/constitution.md b/plugins/fluencyloop/templates/constitution.md
similarity index 100%
rename from templates/constitution.md
rename to plugins/fluencyloop/templates/constitution.md
diff --git a/templates/design.md b/plugins/fluencyloop/templates/design.md
similarity index 100%
rename from templates/design.md
rename to plugins/fluencyloop/templates/design.md
diff --git a/templates/plan.md b/plugins/fluencyloop/templates/plan.md
similarity index 100%
rename from templates/plan.md
rename to plugins/fluencyloop/templates/plan.md
diff --git a/templates/session.md b/plugins/fluencyloop/templates/session.md
similarity index 100%
rename from templates/session.md
rename to plugins/fluencyloop/templates/session.md
diff --git a/tests/cli.bats b/tests/cli.bats
index 7b31e4b..a7c2b0b 100644
--- a/tests/cli.bats
+++ b/tests/cli.bats
@@ -1,5 +1,5 @@
#!/usr/bin/env bats
-# fluencyloop — the CLI dispatcher: version, help, and safe handling of bad input.
+# fluencyloop — the plugin-bundled CLI dispatcher: version, help, and safe handling of bad input.
load test_helper
@@ -14,7 +14,7 @@ load test_helper
[ "$status" -eq 0 ]
[[ "$output" == *"feature"* ]]
[[ "$output" == *"check"* ]]
- [[ "$output" == *"self upgrade"* ]]
+ [[ "$output" != *"self upgrade"* ]]
}
@test "an unknown command exits non-zero and prints usage" {
@@ -23,11 +23,6 @@ load test_helper
[[ "$output" == *"Unknown command"* ]]
}
-@test "self with a bad subcommand exits non-zero" {
- run bash "$DIST/fluencyloop" self bogus
- [ "$status" -ne 0 ]
-}
-
@test "check --json is wired through the dispatcher inside a repo" {
setup_initialized_repo
run bash "$DIST/fluencyloop" check --json
diff --git a/tests/plugin.bats b/tests/plugin.bats
index 85e77df..50e2228 100644
--- a/tests/plugin.bats
+++ b/tests/plugin.bats
@@ -1,29 +1,38 @@
#!/usr/bin/env bats
-# Claude Code plugin package: metadata stays aligned with the CLI release and the bundled
-# launcher can find the distribution scripts after marketplace installation.
+# Agent plugin packages: metadata stays aligned with the bundled runtime, and neither agent
+# relies on the retired machine-wide installer.
load test_helper
-@test "Claude plugin manifest and marketplace describe the distributable package" {
- run python3 - "$DIST" <<'PY'
+@test "Claude Code and Codex marketplace packages describe the same runtime" {
+ run python3 - "$REPO_ROOT" "$DIST" <<'PY'
import json
import pathlib
import sys
root = pathlib.Path(sys.argv[1])
-version = (root / "VERSION").read_text().strip()
-plugin = json.loads((root / ".claude-plugin" / "plugin.json").read_text())
-marketplace = json.loads((root / ".claude-plugin" / "marketplace.json").read_text())
-
-assert plugin["name"] == "fluencyloop"
-assert plugin["version"] == version
-assert plugin["license"] == "Apache-2.0"
-assert marketplace["name"] == "fluencyloop"
-assert len(marketplace["plugins"]) == 1
-entry = marketplace["plugins"][0]
-assert entry["name"] == "fluencyloop"
-assert entry["source"] == "."
-assert entry["version"] == version
+dist = pathlib.Path(sys.argv[2])
+version = (dist / "VERSION").read_text().strip()
+claude_plugin = json.loads((root / ".claude-plugin" / "plugin.json").read_text())
+claude_marketplace = json.loads((root / ".claude-plugin" / "marketplace.json").read_text())
+codex_plugin = json.loads((dist / ".codex-plugin" / "plugin.json").read_text())
+codex_marketplace = json.loads((root / ".agents" / "plugins" / "marketplace.json").read_text())
+
+assert claude_plugin["name"] == codex_plugin["name"] == "fluencyloop"
+assert claude_plugin["version"] == codex_plugin["version"] == version
+assert claude_plugin["license"] == codex_plugin["license"] == "Apache-2.0"
+assert claude_marketplace["name"] == codex_marketplace["name"] == "fluencyloop"
+
+claude_entry, = claude_marketplace["plugins"]
+assert claude_entry["source"] == "."
+assert claude_entry["version"] == version
+codex_entry, = codex_marketplace["plugins"]
+assert codex_entry["name"] == "fluencyloop"
+assert codex_entry["source"] == {"source": "local", "path": "./plugins/fluencyloop"}
+assert codex_entry["policy"] == {"installation": "AVAILABLE", "authentication": "ON_INSTALL"}
+assert codex_entry["category"] == "Productivity"
+assert codex_plugin["skills"] == "./skills/"
+
for alias, source in {
"plan": "fluencyloop-plan",
"feature": "fluencyloop-feature",
@@ -31,20 +40,31 @@ for alias, source in {
"backfill": "fluencyloop-backfill",
}.items():
alias_text = (root / "claude-skills" / alias / "SKILL.md").read_text()
- source_text = (root / "skills" / source / "SKILL.md").read_text()
- assert alias_text.replace(f"name: {alias}", f"name: {source}", 1) == source_text, alias
-assert marketplace["plugins"][0]["skills"] == [
+ source_text = (dist / "skills" / source / "SKILL.md").read_text()
+ assert f"name: {alias}" in alias_text
+ assert f"name: {source}" in source_text
+ assert "## Bundled CLI (Codex)" in source_text
+assert claude_entry["skills"] == [
"./claude-skills/plan",
"./claude-skills/feature",
"./claude-skills/review",
"./claude-skills/backfill",
]
+assert not (root / "install.sh").exists()
+assert not (root / "install.ps1").exists()
+assert not (root / "skills").exists()
PY
[ "$status" -eq 0 ]
}
@test "Claude plugin launcher runs the bundled CLI" {
- run bash "$DIST/bin/fluencyloop" version
+ run bash "$REPO_ROOT/bin/fluencyloop" version
+ [ "$status" -eq 0 ]
+ [ "$output" = "$(cat "$DIST/VERSION")" ]
+}
+
+@test "Codex plugin bundles the CLI beside its skills" {
+ run bash "$DIST/fluencyloop" version
[ "$status" -eq 0 ]
[ "$output" = "$(cat "$DIST/VERSION")" ]
}
diff --git a/tests/powershell/_helper.ps1 b/tests/powershell/_helper.ps1
index 752b5a4..958eac4 100644
--- a/tests/powershell/_helper.ps1
+++ b/tests/powershell/_helper.ps1
@@ -5,7 +5,8 @@
# Scripts are invoked as `pwsh -File` subprocesses (like bats runs bash): they write to OS stdout
# via [Console]::Out.Write, which the call operator would not otherwise capture.
-$script:Bin = (Resolve-Path "$PSScriptRoot/../../scripts/powershell").Path
+$script:Dist = (Resolve-Path "$PSScriptRoot/../../plugins/fluencyloop").Path
+$script:Bin = (Resolve-Path "$script:Dist/scripts/powershell").Path
$script:PwshExe = (Get-Process -Id $PID).Path
if (-not $script:PwshExe) { $script:PwshExe = 'pwsh' }
diff --git a/tests/powershell/cli.Tests.ps1 b/tests/powershell/cli.Tests.ps1
index efa13b4..1a37118 100644
--- a/tests/powershell/cli.Tests.ps1
+++ b/tests/powershell/cli.Tests.ps1
@@ -3,8 +3,8 @@
Describe 'fluencyloop.ps1 (dispatcher)' {
BeforeAll {
. "$PSScriptRoot/_helper.ps1"
- $script:Cli = (Resolve-Path "$PSScriptRoot/../../fluencyloop.ps1").Path
- $script:Version = (Get-Content -Raw (Resolve-Path "$PSScriptRoot/../../VERSION").Path).Trim()
+ $script:Cli = (Resolve-Path "$PSScriptRoot/../../plugins/fluencyloop/fluencyloop.ps1").Path
+ $script:Version = (Get-Content -Raw (Resolve-Path "$PSScriptRoot/../../plugins/fluencyloop/VERSION").Path).Trim()
}
AfterEach {
Set-Location -LiteralPath $PSScriptRoot
@@ -16,11 +16,11 @@ Describe 'fluencyloop.ps1 (dispatcher)' {
$out.Trim() | Should -Be $script:Version
}
- It 'help lists the core commands' {
+ It 'help lists core commands without legacy self upgrade' {
$out = (& $script:PwshExe -NoProfile -File $script:Cli 'help' 2>&1 | ForEach-Object { $_.ToString() }) -join "`n"
$out | Should -Match 'feature'
$out | Should -Match 'check'
- $out | Should -Match 'self upgrade'
+ $out | Should -Not -Match 'self upgrade'
}
It 'an unknown command exits non-zero and prints usage' {
@@ -30,11 +30,6 @@ Describe 'fluencyloop.ps1 (dispatcher)' {
$out | Should -Match 'Unknown command'
}
- It 'self with a bad subcommand exits non-zero' {
- & $script:PwshExe -NoProfile -File $script:Cli 'self' 'bogus' *> $null
- $LASTEXITCODE | Should -Not -Be 0
- }
-
It 'check --json is wired through the dispatcher inside a repo' {
$script:repo = Initialize-TestRepo
$j = (& $script:PwshExe -NoProfile -File $script:Cli 'check' '--json') | ConvertFrom-Json
diff --git a/tests/test_helper.bash b/tests/test_helper.bash
index 5c29172..458bc89 100644
--- a/tests/test_helper.bash
+++ b/tests/test_helper.bash
@@ -2,8 +2,9 @@
# Each test runs in a throwaway git repo with an isolated FLUENCYLOOP_HOME, so nothing touches
# the developer's real repo, branches, or calibration profile.
-# The distribution under test = the repo this tests/ dir lives in.
-DIST="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
+# The distribution under test = the Codex plugin package in this repo.
+REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
+DIST="$REPO_ROOT/plugins/fluencyloop"
BIN="$DIST/scripts/bash"
# Make a fresh git repo on `main` with one commit. Does NOT run init (some tests want a