@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 +21 -0
- package/bin/moxie +86 -0
- package/lib/agents.sh +330 -0
- package/lib/core.sh +111 -0
- package/lib/phases.sh +1017 -0
- package/lib/tokens.sh +162 -0
- package/package.json +43 -0
- package/templates/audit/prompt.txt +86 -0
- package/templates/build/prompt.txt +75 -0
- package/templates/fix/prompt.txt +63 -0
- package/templates/plan/prompt.txt +67 -0
- package/templates/rfc/prompt.txt +90 -0
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
|
+
}
|