@zachjxyz/moxie 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zach Johnson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/moxie ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # moxie — spec-driven multi-agent development with quorum convergence
3
+ #
4
+ # Pipeline: rfc → audit → fix → plan → build
5
+ # Each phase requires UNANIMOUS quorum from all agents.
6
+ #
7
+ # Usage:
8
+ # moxie init [--spec ./RFC.md]
9
+ # moxie start [--phase rfc|audit|fix|plan|build]
10
+ # moxie stop
11
+ # moxie run [--phase rfc|audit|fix|plan|build] [--dry-run]
12
+ # moxie status
13
+ # moxie cost
14
+ # moxie logs [phase]
15
+ # moxie agents
16
+
17
+ set -euo pipefail
18
+
19
+ MOXIE_VERSION="0.2.0"
20
+ MOXIE_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
21
+ MOXIE_LIB="$MOXIE_ROOT/lib"
22
+
23
+ # Source core library
24
+ source "$MOXIE_LIB/core.sh"
25
+ source "$MOXIE_LIB/agents.sh"
26
+ source "$MOXIE_LIB/phases.sh"
27
+ source "$MOXIE_LIB/tokens.sh"
28
+
29
+ # ---- Subcommand dispatch ----
30
+
31
+ COMMAND="${1:-help}"
32
+ shift || true
33
+
34
+ case "$COMMAND" in
35
+ init) cmd_init "$@" ;;
36
+ start) cmd_start "$@" ;;
37
+ stop) cmd_stop "$@" ;;
38
+ run) cmd_run "$@" ;;
39
+ status) cmd_status "$@" ;;
40
+ cost) cmd_cost "$@" ;;
41
+ logs) cmd_logs "$@" ;;
42
+ agents) cmd_agents "$@" ;;
43
+ doctor) cmd_doctor "$@" ;;
44
+ version) echo "moxie $MOXIE_VERSION" ;;
45
+ help|--help|-h)
46
+ cat <<'EOF'
47
+ moxie — spec-driven multi-agent development with quorum convergence
48
+
49
+ Commands:
50
+ init Scaffold .moxie/ for the current project
51
+ start Run pipeline in background (caffeinated, durable)
52
+ stop Stop a running pipeline
53
+ run Run pipeline in foreground (caffeinated)
54
+ status Show phase progress, quorum state, and running status
55
+ cost Token usage breakdown by phase and agent
56
+ logs Tail or view logs for a phase
57
+ agents List configured agents
58
+ doctor Check agent CLIs, auth, and project health
59
+ version Print version
60
+ help Show this help
61
+
62
+ Usage:
63
+ moxie init Initialize (RFC generated by agents)
64
+ moxie init --spec ./RFC.md Initialize with existing spec
65
+ moxie init --no-discover Skip context document discovery
66
+ moxie start Run pipeline in background
67
+ moxie start --phase build Start just the build phase
68
+ moxie stop Stop background pipeline
69
+ moxie run Run full pipeline (foreground)
70
+ moxie run --phase fix Run just the fix phase
71
+ moxie run --dry-run Trace without spawning agents
72
+ moxie status Show progress and quorum
73
+ moxie cost Show token usage summary
74
+ moxie doctor Check agents and dependencies
75
+
76
+ Pipeline: rfc → audit → fix → plan → build
77
+ Each phase uses randomized agent rotation with UNANIMOUS quorum.
78
+ The pipeline is durable — it resumes from where it left off.
79
+ EOF
80
+ ;;
81
+ *)
82
+ echo "Unknown command: $COMMAND" >&2
83
+ echo "Run 'moxie help' for usage." >&2
84
+ exit 1
85
+ ;;
86
+ esac
package/lib/agents.sh ADDED
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env bash
2
+ # moxie/lib/agents.sh — Agent dispatch and rotation
3
+ # Compatible with Bash 3.2 (macOS default) — no associative arrays.
4
+
5
+ # Global arrays populated by load_agents():
6
+ # AGENT_NAMES=("codex" "claude" "qwen")
7
+ # AGENT_CMDS_0="codex exec ..." (indexed by position in AGENT_NAMES)
8
+ # AGENT_CMDS_1="claude ..."
9
+ # AGENT_CMDS_2="qwen ..."
10
+
11
+ # ---- Load agents from config ----
12
+
13
+ load_agents() {
14
+ require_moxie_project
15
+
16
+ AGENT_NAMES=()
17
+ local agent_cmds=()
18
+ local agent_orders=()
19
+
20
+ local current_agent=""
21
+ local current_cmd=""
22
+ local current_order="99"
23
+
24
+ while IFS= read -r line; do
25
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
26
+ [[ -z "${line// /}" ]] && continue
27
+
28
+ if [[ "$line" =~ ^\[agents\.([a-zA-Z0-9_-]+)\] ]]; then
29
+ if [ -n "$current_agent" ] && [ -n "$current_cmd" ]; then
30
+ AGENT_NAMES+=("$current_agent")
31
+ agent_cmds+=("$current_cmd")
32
+ agent_orders+=("$current_order")
33
+ fi
34
+ current_agent="${BASH_REMATCH[1]}"
35
+ current_cmd=""
36
+ current_order="99"
37
+ continue
38
+ fi
39
+
40
+ # New non-agent section
41
+ if [[ "$line" =~ ^\[ ]] && ! [[ "$line" =~ ^\[agents\. ]]; then
42
+ if [ -n "$current_agent" ] && [ -n "$current_cmd" ]; then
43
+ AGENT_NAMES+=("$current_agent")
44
+ agent_cmds+=("$current_cmd")
45
+ agent_orders+=("$current_order")
46
+ fi
47
+ current_agent=""
48
+ continue
49
+ fi
50
+
51
+ if [ -n "$current_agent" ]; then
52
+ if [[ "$line" =~ ^[[:space:]]*command[[:space:]]*=[[:space:]]*(.*) ]]; then
53
+ current_cmd="${BASH_REMATCH[1]}"
54
+ current_cmd="${current_cmd#\"}"
55
+ current_cmd="${current_cmd%\"}"
56
+ current_cmd="${current_cmd#\'}"
57
+ current_cmd="${current_cmd%\'}"
58
+ fi
59
+ if [[ "$line" =~ ^[[:space:]]*order[[:space:]]*=[[:space:]]*([0-9]+) ]]; then
60
+ current_order="${BASH_REMATCH[1]}"
61
+ fi
62
+ fi
63
+ done < "$MOXIE_CONFIG"
64
+
65
+ # Save last agent
66
+ if [ -n "$current_agent" ] && [ -n "$current_cmd" ]; then
67
+ AGENT_NAMES+=("$current_agent")
68
+ agent_cmds+=("$current_cmd")
69
+ agent_orders+=("$current_order")
70
+ fi
71
+
72
+ # Sort by order using a temp file
73
+ local sorted
74
+ sorted=$(for i in "${!AGENT_NAMES[@]}"; do
75
+ echo "${agent_orders[$i]} $i ${AGENT_NAMES[$i]}"
76
+ done | sort -n)
77
+
78
+ local sorted_names=()
79
+ local sorted_cmds=()
80
+
81
+ while read -r _order idx _name; do
82
+ sorted_names+=("${AGENT_NAMES[$idx]}")
83
+ sorted_cmds+=("${agent_cmds[$idx]}")
84
+ done <<< "$sorted"
85
+
86
+ AGENT_NAMES=("${sorted_names[@]}")
87
+
88
+ # Store commands as indexed globals (Bash 3.2 compatible)
89
+ for i in "${!sorted_cmds[@]}"; do
90
+ eval "AGENT_CMD_$i=\"${sorted_cmds[$i]}\""
91
+ done
92
+ AGENT_COUNT=${#AGENT_NAMES[@]}
93
+ }
94
+
95
+ # Get command for agent by name
96
+ _agent_cmd() {
97
+ local name="$1"
98
+ for i in "${!AGENT_NAMES[@]}"; do
99
+ if [ "${AGENT_NAMES[$i]}" = "$name" ]; then
100
+ eval "echo \"\$AGENT_CMD_$i\""
101
+ return
102
+ fi
103
+ done
104
+ echo ""
105
+ }
106
+
107
+ # ---- Dispatch ----
108
+
109
+ dispatch_agent() {
110
+ local agent="$1"
111
+ local prompt_file="$2"
112
+ local timeout_secs="$3"
113
+ local cmd
114
+ cmd=$(_agent_cmd "$agent")
115
+
116
+ if [ -z "$cmd" ]; then
117
+ echo "ERROR: No command configured for agent '$agent'" >&2
118
+ return 1
119
+ fi
120
+
121
+ # Isolate: strip plugin root vars so hooks from other CLIs can't fire
122
+ env -u CLAUDE_PLUGIN_ROOT \
123
+ -u CODEX_PLUGIN_ROOT \
124
+ -u CURSOR_PLUGIN_ROOT \
125
+ timeout "$timeout_secs" $cmd "$(cat "$prompt_file")" || {
126
+ local rc=$?
127
+ if [ $rc -eq 124 ]; then
128
+ echo "[TIMEOUT] $agent exceeded ${timeout_secs}s — skipping"
129
+ fi
130
+ return $rc
131
+ }
132
+ }
133
+
134
+ # ---- Rotation ----
135
+
136
+ next_agent() {
137
+ local last="$1"
138
+ local len=${#AGENT_NAMES[@]}
139
+ for i in "${!AGENT_NAMES[@]}"; do
140
+ if [ "${AGENT_NAMES[$i]}" = "$last" ]; then
141
+ echo "${AGENT_NAMES[$(( (i + 1) % len ))]}"
142
+ return
143
+ fi
144
+ done
145
+ echo "${AGENT_NAMES[0]}"
146
+ }
147
+
148
+ # ---- Logging ----
149
+
150
+ dispatch_logged() {
151
+ local agent="$1"
152
+ local prompt_file="$2"
153
+ local timeout_secs="$3"
154
+ local log_dir="$4"
155
+ local turn="$5"
156
+ local csv_file="$6"
157
+ local phase="$7"
158
+ local ts
159
+ ts=$(date +"%m%d%y-%H%M%S")
160
+ local logfile="${log_dir}/${agent}-${ts}.log"
161
+
162
+ echo " Log: $(basename "$logfile")"
163
+
164
+ if [ "${DRY_RUN:-0}" = "1" ]; then
165
+ echo " [DRY RUN] Would run: $agent" | tee "$logfile"
166
+ echo " [DRY RUN] Prompt: $(wc -c < "$prompt_file" | tr -d ' ') bytes" | tee -a "$logfile"
167
+ record_tokens "$csv_file" "$turn" "$agent" "$ts" "0" "$phase"
168
+ return 0
169
+ fi
170
+
171
+ dispatch_agent "$agent" "$prompt_file" "$timeout_secs" 2>&1 | tee "$logfile" || true
172
+
173
+ local tokens
174
+ tokens=$(extract_tokens "$logfile" "$agent")
175
+ record_tokens "$csv_file" "$turn" "$agent" "$ts" "$tokens" "$phase"
176
+ echo " Tokens: $tokens"
177
+ }
178
+
179
+ # ---- List agents ----
180
+
181
+ cmd_agents() {
182
+ require_moxie_project
183
+ load_agents
184
+
185
+ echo "Configured agents (rotation order):"
186
+ echo ""
187
+ for i in "${!AGENT_NAMES[@]}"; do
188
+ local name="${AGENT_NAMES[$i]}"
189
+ local cmd
190
+ cmd=$(_agent_cmd "$name")
191
+ echo " $((i + 1)). $name"
192
+ echo " $cmd"
193
+ done
194
+ }
195
+
196
+ # ---- Doctor: health checks ----
197
+
198
+ cmd_doctor() {
199
+ local has_project=0
200
+ if [ -d "$MOXIE_DIR" ] && [ -f "$MOXIE_CONFIG" ]; then
201
+ has_project=1
202
+ load_agents
203
+ fi
204
+
205
+ local all_ok=1
206
+
207
+ echo "moxie doctor"
208
+ echo ""
209
+
210
+ # ---- Dependencies ----
211
+ echo "Dependencies:"
212
+ _check_dep "python3" "Required for ledger parsing and token tracking" || all_ok=0
213
+ _check_dep "caffeinate" "Keeps machine awake during runs (macOS)" || true # non-fatal
214
+ if ! command -v timeout &>/dev/null; then
215
+ if command -v gtimeout &>/dev/null; then
216
+ echo " [OK] gtimeout (aliased as timeout)"
217
+ else
218
+ echo " [!!] timeout — not found. Install coreutils: brew install coreutils"
219
+ echo " Agent turns will have no timeout protection."
220
+ fi
221
+ else
222
+ echo " [OK] timeout"
223
+ fi
224
+ echo ""
225
+
226
+ # ---- Agents ----
227
+ if [ "$has_project" = "1" ]; then
228
+ echo "Agents (from .moxie/config.toml):"
229
+ for i in "${!AGENT_NAMES[@]}"; do
230
+ local name="${AGENT_NAMES[$i]}"
231
+ local cmd
232
+ cmd=$(_agent_cmd "$name")
233
+
234
+ # Extract the base binary from the command
235
+ local binary
236
+ binary=$(echo "$cmd" | awk '{print $1}')
237
+
238
+ if ! command -v "$binary" &>/dev/null; then
239
+ echo " [!!] $name — '$binary' not found on PATH"
240
+ all_ok=0
241
+ continue
242
+ fi
243
+
244
+ # Try --version to verify it's functional
245
+ local version
246
+ version=$("$binary" --version 2>&1 | head -1)
247
+ if [ $? -eq 0 ] && [ -n "$version" ]; then
248
+ echo " [OK] $name — $version"
249
+ else
250
+ echo " [??] $name — '$binary' found but --version failed"
251
+ echo " May not be authenticated. Try running: $binary --version"
252
+ fi
253
+ done
254
+ else
255
+ echo "Agents:"
256
+ echo " (no .moxie/ project — checking common agents)"
257
+ _check_agent_binary "codex" || all_ok=0
258
+ _check_agent_binary "claude" || all_ok=0
259
+ _check_agent_binary "qwen" || all_ok=0
260
+ fi
261
+ echo ""
262
+
263
+ # ---- Project state ----
264
+ if [ "$has_project" = "1" ]; then
265
+ echo "Project:"
266
+ echo " [OK] .moxie/ directory found"
267
+
268
+ local spec="$MOXIE_DIR/spec.md"
269
+ if [ -f "$spec" ]; then
270
+ echo " [OK] spec.md present ($(wc -l < "$spec" | tr -d ' ') lines)"
271
+ else
272
+ echo " [!!] spec.md missing"
273
+ all_ok=0
274
+ fi
275
+
276
+ for phase in "${PHASES[@]}"; do
277
+ local ledger="$MOXIE_DIR/phases/$phase/ledger.json"
278
+ local prompt="$MOXIE_DIR/phases/$phase/prompt.txt"
279
+ if [ ! -f "$ledger" ]; then
280
+ echo " [!!] $phase/ledger.json missing"
281
+ all_ok=0
282
+ fi
283
+ if [ ! -f "$prompt" ]; then
284
+ echo " [!!] $phase/prompt.txt missing"
285
+ all_ok=0
286
+ fi
287
+ done
288
+ if [ "$all_ok" = "1" ]; then
289
+ echo " [OK] All phase ledgers and prompts present"
290
+ fi
291
+ else
292
+ echo "Project:"
293
+ echo " [--] No .moxie/ directory. Run 'moxie init' to set up."
294
+ fi
295
+
296
+ echo ""
297
+ if [ "$all_ok" = "1" ]; then
298
+ echo "All checks passed. Ready to run."
299
+ else
300
+ echo "Some checks failed. Fix the issues above before running."
301
+ fi
302
+ }
303
+
304
+ _check_dep() {
305
+ local binary="$1"
306
+ local desc="$2"
307
+ if command -v "$binary" &>/dev/null; then
308
+ echo " [OK] $binary"
309
+ return 0
310
+ else
311
+ echo " [!!] $binary — not found. $desc"
312
+ return 1
313
+ fi
314
+ }
315
+
316
+ _check_agent_binary() {
317
+ local binary="$1"
318
+ if ! command -v "$binary" &>/dev/null; then
319
+ echo " [!!] $binary — not found on PATH"
320
+ return 1
321
+ fi
322
+ local version
323
+ version=$("$binary" --version 2>&1 | head -1)
324
+ if [ $? -eq 0 ] && [ -n "$version" ]; then
325
+ echo " [OK] $binary — $version"
326
+ else
327
+ echo " [??] $binary — found but --version failed"
328
+ fi
329
+ return 0
330
+ }
package/lib/core.sh ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bash
2
+ # moxie/lib/core.sh — Core utilities and configuration
3
+
4
+ # ---- Constants ----
5
+ MOXIE_DIR=".moxie"
6
+ MOXIE_CONFIG="$MOXIE_DIR/config.toml"
7
+ PHASES=(rfc audit fix plan build)
8
+
9
+ # ---- macOS timeout compatibility ----
10
+ if ! command -v timeout &>/dev/null; then
11
+ if command -v gtimeout &>/dev/null; then
12
+ timeout() { gtimeout "$@"; }
13
+ else
14
+ timeout() { shift; "$@"; } # no-op fallback
15
+ fi
16
+ fi
17
+
18
+ # ---- Config parsing (TOML subset) ----
19
+
20
+ # Read a dotted key from a TOML file. Supports flat keys only.
21
+ # Usage: toml_get config.toml settings.max_rounds
22
+ toml_get() {
23
+ local file="$1" key="$2" default="${3:-}"
24
+ local section="" field=""
25
+
26
+ # Split key into section.field
27
+ if [[ "$key" == *.* ]]; then
28
+ section="${key%%.*}"
29
+ field="${key#*.}"
30
+ else
31
+ field="$key"
32
+ fi
33
+
34
+ local in_section=0
35
+ local value=""
36
+
37
+ while IFS= read -r line; do
38
+ # Skip comments and empty lines
39
+ [[ "$line" =~ ^[[:space:]]*# ]] && continue
40
+ [[ -z "${line// /}" ]] && continue
41
+
42
+ # Section header
43
+ if [[ "$line" =~ ^\[([a-zA-Z0-9_.]+)\] ]]; then
44
+ local current="${BASH_REMATCH[1]}"
45
+ if [ -n "$section" ] && [ "$current" = "$section" ]; then
46
+ in_section=1
47
+ else
48
+ in_section=0
49
+ fi
50
+ continue
51
+ fi
52
+
53
+ # Key = value (only if in the right section, or no section needed)
54
+ if { [ -z "$section" ] && [ $in_section -eq 0 ]; } || [ $in_section -eq 1 ]; then
55
+ if [[ "$line" =~ ^[[:space:]]*${field}[[:space:]]*=[[:space:]]*(.*) ]]; then
56
+ value="${BASH_REMATCH[1]}"
57
+ # Strip quotes and trailing comments
58
+ value="${value%%#*}"
59
+ value="${value## }"
60
+ value="${value%% }"
61
+ value="${value#\"}"
62
+ value="${value%\"}"
63
+ value="${value#\'}"
64
+ value="${value%\'}"
65
+ break
66
+ fi
67
+ fi
68
+ done < "$file"
69
+
70
+ echo "${value:-$default}"
71
+ }
72
+
73
+ # ---- Project detection ----
74
+
75
+ require_moxie_project() {
76
+ if [ ! -d "$MOXIE_DIR" ] || [ ! -f "$MOXIE_CONFIG" ]; then
77
+ echo "ERROR: No .moxie/ directory found." >&2
78
+ echo "Run 'moxie init --spec <file>' first." >&2
79
+ exit 1
80
+ fi
81
+ }
82
+
83
+ # ---- Display helpers ----
84
+
85
+ banner() {
86
+ local title="$1"
87
+ local max_rounds="$2"
88
+ local timeout="$3"
89
+ local dry_run="${4:-0}"
90
+ echo "╔══════════════════════════════════════════════════╗"
91
+ printf "║ %-48s║\n" "$title"
92
+ echo "║ Round-robin quorum convergence ║"
93
+ printf "║ Max rounds: %-35s║\n" "$max_rounds"
94
+ printf "║ Timeout/turn: %-33s║\n" "${timeout}s"
95
+ if [ "$dry_run" = "1" ]; then
96
+ echo "║ Mode: DRY RUN ║"
97
+ fi
98
+ echo "╚══════════════════════════════════════════════════╝"
99
+ echo ""
100
+ }
101
+
102
+ phase_label() {
103
+ case "$1" in
104
+ rfc) echo "RFC" ;;
105
+ audit) echo "Audit" ;;
106
+ fix) echo "Fix" ;;
107
+ plan) echo "Plan" ;;
108
+ build) echo "Build" ;;
109
+ *) echo "$1" ;;
110
+ esac
111
+ }