faberun 0.3.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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/bin/faberun.mjs +25 -0
  4. package/integrations/claude-code/statusline-bench.sh +42 -0
  5. package/integrations/claude-code/statusline.sh +80 -0
  6. package/package.json +33 -0
  7. package/skills/faberun/SKILL.md +24 -0
  8. package/skills/faberun/references/contract.md +380 -0
  9. package/skills/faberun/references/engineering.md +29 -0
  10. package/skills/faberun/references/handoffs.md +26 -0
  11. package/skills/faberun/references/operations.md +184 -0
  12. package/skills/faberun/references/rules.md +35 -0
  13. package/skills/faberun/references/workflow.md +23 -0
  14. package/skills/init-agentkit/SKILL.md +108 -0
  15. package/skills/init-agentkit/scripts/install-agentkit.sh +127 -0
  16. package/skills/init-agentkit/templates/.claude/commands/create-adr.md +44 -0
  17. package/skills/init-agentkit/templates/.github/workflows/quality.yml +43 -0
  18. package/skills/init-agentkit/templates/.sentrux/baseline.json +9 -0
  19. package/skills/init-agentkit/templates/.sentrux/rules.toml +21 -0
  20. package/skills/init-agentkit/templates/AGENTS.md +110 -0
  21. package/skills/init-agentkit/templates/docs/ABSTRACTIONS.md +30 -0
  22. package/skills/init-agentkit/templates/docs/ARCHITECTURE.md +31 -0
  23. package/skills/init-agentkit/templates/docs/GETTING-STARTED.md +44 -0
  24. package/skills/init-agentkit/templates/docs/VISION.md +33 -0
  25. package/skills/init-agentkit/templates/docs/adr/0001-record-architecture-decisions.md +36 -0
  26. package/skills/init-agentkit/templates/docs/adr/0002-root-managed-ai-guidance.md +37 -0
  27. package/skills/init-agentkit/templates/docs/adr/0003-sentrux-structural-quality-gates.md +49 -0
  28. package/skills/init-agentkit/templates/docs/adr/README.md +52 -0
  29. package/skills/init-agentkit/templates/docs/sentrux.md +66 -0
  30. package/skills/init-agentkit/templates/githooks/commit-msg +22 -0
  31. package/skills/init-agentkit/templates/githooks/pre-commit +32 -0
  32. package/src/campaign/brief.mjs +394 -0
  33. package/src/campaign/chain.mjs +555 -0
  34. package/src/campaign/handoff.mjs +516 -0
  35. package/src/campaign/index.mjs +300 -0
  36. package/src/campaign/journal.mjs +347 -0
  37. package/src/campaign/layout.mjs +51 -0
  38. package/src/campaign/metrics-evals.mjs +25 -0
  39. package/src/campaign/metrics.mjs +517 -0
  40. package/src/campaign/projection.mjs +250 -0
  41. package/src/campaign/record.mjs +102 -0
  42. package/src/campaign/unpark.mjs +56 -0
  43. package/src/cli/brand.mjs +205 -0
  44. package/src/cli/campaign.mjs +730 -0
  45. package/src/cli/contract.mjs +67 -0
  46. package/src/cli/init.mjs +170 -0
  47. package/src/cli/launch.mjs +239 -0
  48. package/src/cli/seat.mjs +139 -0
  49. package/src/cli/setup.mjs +294 -0
  50. package/src/cli/skills.mjs +105 -0
  51. package/src/cli/update.mjs +216 -0
  52. package/src/cli.mjs +525 -0
  53. package/src/contract/articles.mjs +12 -0
  54. package/src/contract/assert.mjs +162 -0
  55. package/src/contract/definition-of-done.mjs +97 -0
  56. package/src/contract/final-verification.mjs +96 -0
  57. package/src/contract/index.mjs +641 -0
  58. package/src/contract/judge-envelope.mjs +25 -0
  59. package/src/contract/review-modes.mjs +151 -0
  60. package/src/contract/runtime.mjs +204 -0
  61. package/src/contract/schema-version.mjs +25 -0
  62. package/src/contract/scope-findings.mjs +77 -0
  63. package/src/contract/snapshot.mjs +639 -0
  64. package/src/contract/task-packet.mjs +495 -0
  65. package/src/contract/untrusted.mjs +75 -0
  66. package/src/contract/verification.mjs +185 -0
  67. package/src/contract/worker-result.mjs +138 -0
  68. package/src/engine/assignment.mjs +63 -0
  69. package/src/engine/backoff.mjs +492 -0
  70. package/src/engine/bulk-read.mjs +361 -0
  71. package/src/engine/cancel.mjs +177 -0
  72. package/src/engine/detach.mjs +101 -0
  73. package/src/engine/dispatch.mjs +752 -0
  74. package/src/engine/failover.mjs +192 -0
  75. package/src/engine/gate.mjs +183 -0
  76. package/src/engine/judge-gate.mjs +517 -0
  77. package/src/engine/lifecycle.mjs +772 -0
  78. package/src/engine/live-preflight.mjs +299 -0
  79. package/src/engine/mutation.mjs +146 -0
  80. package/src/engine/notify-queue.mjs +327 -0
  81. package/src/engine/process-identity.mjs +72 -0
  82. package/src/engine/process.mjs +774 -0
  83. package/src/engine/prompts.mjs +289 -0
  84. package/src/engine/recover.mjs +300 -0
  85. package/src/engine/result-file.mjs +222 -0
  86. package/src/engine/resume.mjs +635 -0
  87. package/src/engine/retry.mjs +334 -0
  88. package/src/engine/review.mjs +228 -0
  89. package/src/engine/run-command.mjs +287 -0
  90. package/src/engine/run-identity.mjs +411 -0
  91. package/src/engine/runtime-discovery.mjs +235 -0
  92. package/src/engine/scheduler.mjs +526 -0
  93. package/src/engine/scope.mjs +378 -0
  94. package/src/engine/settle.mjs +207 -0
  95. package/src/engine/state.mjs +148 -0
  96. package/src/engine/supervise.mjs +713 -0
  97. package/src/engine/verify.mjs +167 -0
  98. package/src/harnesses/agy/index.mjs +62 -0
  99. package/src/harnesses/catalogue.mjs +509 -0
  100. package/src/harnesses/claude/index.mjs +90 -0
  101. package/src/harnesses/codex/index.mjs +87 -0
  102. package/src/harnesses/dsh/closed-packet.patch.yml +42 -0
  103. package/src/harnesses/dsh/index.mjs +210 -0
  104. package/src/harnesses/dsh/runner.mjs +259 -0
  105. package/src/harnesses/exec-jsonl/index.mjs +788 -0
  106. package/src/harnesses/index.mjs +508 -0
  107. package/src/harnesses/protocol.mjs +531 -0
  108. package/src/harnesses/replay/bin.mjs +386 -0
  109. package/src/harnesses/replay/index.mjs +238 -0
  110. package/src/harnesses/zcode/index.mjs +276 -0
  111. package/src/host/config.mjs +87 -0
  112. package/src/host/home.mjs +149 -0
  113. package/src/host/package.mjs +23 -0
  114. package/src/host/preflight.mjs +520 -0
  115. package/src/host/tool-policy-decisions.mjs +341 -0
  116. package/src/host/tool-policy-hook.mjs +270 -0
  117. package/src/notify/index.mjs +359 -0
  118. package/src/notify/os-macos.mjs +81 -0
  119. package/src/repo/declared-paths.mjs +220 -0
  120. package/src/repo/integrate.mjs +546 -0
  121. package/src/repo/scope-closure.mjs +665 -0
  122. package/src/repo/signal-block.mjs +16 -0
  123. package/src/repo/signal.mjs +222 -0
  124. package/src/repo/source-identity.mjs +295 -0
  125. package/src/repo/workspace.mjs +557 -0
  126. package/src/repo/worktree.mjs +352 -0
  127. package/src/report/final.mjs +200 -0
  128. package/src/report/metrics-report.mjs +99 -0
  129. package/src/report/next.mjs +383 -0
  130. package/src/report/render.mjs +716 -0
  131. package/src/run/disk-gc.mjs +251 -0
  132. package/src/run/lock.mjs +329 -0
  133. package/src/run/node-store.mjs +62 -0
  134. package/src/run/operations.mjs +286 -0
  135. package/src/run/store.mjs +187 -0
  136. package/src/run/usage.mjs +337 -0
  137. package/src/seat/harnesses.mjs +83 -0
  138. package/src/seat/index.mjs +239 -0
  139. package/src/seat/tmux.mjs +208 -0
  140. package/src/util.mjs +0 -0
  141. package/src/web/api.mjs +371 -0
  142. package/src/web/boundary.mjs +88 -0
  143. package/src/web/index.html +299 -0
  144. package/src/web/server.mjs +552 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Felipe Broering
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/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # skills
2
+
3
+ Personal catalog of reusable agent skills, installable into any repository
4
+ with a single command.
5
+
6
+ All skills live under [`skills/`](skills/). Each skill is one folder
7
+ with a `SKILL.md`; scripts, references, and templates live inside the skill so
8
+ it stays a single copyable unit.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npx github:feliperun/faberun # every `mine` skill → .claude/skills/ of the current repo
14
+ npx github:feliperun/faberun faberun # one named skill
15
+ npx github:feliperun/faberun list # show the catalog
16
+ npx github:feliperun/faberun --global # install into ~/.claude/skills/ instead
17
+ npx github:feliperun/faberun --force # replace skills that already exist
18
+ ```
19
+
20
+ `npx` needs the repository to be reachable (public, or private with git
21
+ credentials). Without npx, copy or symlink a skill folder into
22
+ `~/.claude/skills/` or `.claude/skills/`:
23
+
24
+ ```bash
25
+ cp -r skills/faberun ~/.claude/skills/faberun
26
+ ln -s "$(pwd)/skills/init-agentkit" ~/.claude/skills/init-agentkit
27
+ ```
28
+
29
+ ## Skills
30
+
31
+ ### faberun
32
+
33
+ Executes large implementation plans as observable multi-model DAGs outside the
34
+ orchestrator's context: declarative routing (Claude or Codex workers, including
35
+ DeepSeek through Codex custom providers), closed task packets, structured
36
+ cross-model quality gates, bounded revisions, campaign journaling with
37
+ `HANDOFF.md`, and a built-in `supervise` watchdog that keeps resuming a dead
38
+ controller until the run is terminal — from any host scheduler (launchd, cron,
39
+ CI, or another agent), with no dependency on the orchestrator's runtime.
40
+
41
+ Release 1 makes a long campaign cheap to watch and cheap to finish. Liveness is
42
+ rendered ambiently from a bounded heartbeat at zero token cost, and the control
43
+ session pulls campaign events on its own cursor instead of being woken by
44
+ progress. Every Definition of Done item now declares how it is proven, so a
45
+ mechanically provable node spends no judge at all, while contract-level
46
+ `finalVerification` keeps a phase from closing on partial proof. An interrupted
47
+ run is continued in place by `resume`, never re-authored, and the factory rides
48
+ out provider exhaustion, quota resets, dead leases, and flaky networks
49
+ deterministically. `metrics` reports effectiveness and efficiency of a
50
+ campaign together, from what the runs recorded. Detail:
51
+ [references/release-1.md](skills/faberun/references/release-1.md).
52
+
53
+ Quickstart, in the repository that will receive the implementation:
54
+
55
+ ```bash
56
+ FABERUN=/path/to/faberun/src/cli.mjs
57
+ TARGET=/path/to/target-repository
58
+
59
+ rg -qxF '.runs/' "$TARGET/.gitignore" || printf '\n.runs/\n' >> "$TARGET/.gitignore"
60
+ node "$FABERUN" campaign init feature-42 --cwd "$TARGET" --goal "Deliver feature 42"
61
+ node "$FABERUN" campaign attach feature-42 --cwd "$TARGET" --tool codex --session-id <session-id> --no-transcript
62
+ ```
63
+
64
+ Inspect the target once, write the contract and its task packets, then:
65
+
66
+ ```bash
67
+ node "$FABERUN" validate contract.json
68
+ node "$FABERUN" preflight contract.json
69
+ node "$FABERUN" run --detach contract.json
70
+ node "$FABERUN" supervise --detach "$TARGET/.runs/<run-id>" # unattended resume
71
+ ```
72
+
73
+ | Goal | Command |
74
+ | --- | --- |
75
+ | Find the active campaign | `campaign list --cwd <repo>` |
76
+ | Pull unseen campaign events | `campaign sync <id> --cwd <repo> --session-id <s>` |
77
+ | Advance the session cursor | `campaign ack <id> --cwd <repo> --session-id <s> --event-id <e>` |
78
+ | Validate a contract | `validate <contract.json>` |
79
+ | Check credentials, models, binaries | `preflight <contract.json>` / `doctor [--cwd <dir>]` |
80
+ | Start without blocking the session | `run --detach <contract.json>` |
81
+ | Read current state | `status <run-dir>` / `status --json <run-dir>` |
82
+ | Name the next action per campaign | `next [--cwd <dir>] [--json]` |
83
+ | View attempts and tokens | `report <run-dir>` |
84
+ | Read what a stopped node needs: gate findings and blocking questions | `findings <run-dir>` |
85
+ | Read the campaign indicators | `metrics <campaign-id> --cwd <repo>` |
86
+ | Stop a run and terminate its providers | `cancel <run-dir>` |
87
+ | Resume an interrupted run | `resume --detach <run-dir>` |
88
+ | Keep finishing a run whose controller died | `supervise --detach <run-dir> [--interval 30]` |
89
+
90
+ Operational detail: [SKILL.md](skills/faberun/SKILL.md) and the
91
+ [contract reference](skills/faberun/references/contract.md).
92
+
93
+ ### init-agentkit
94
+
95
+ Bootstraps the agent kit into a repository: canonical `AGENTS.md` with
96
+ `CLAUDE.md`/`GEMINI.md`/`CURSOR.md`/`AGENT.md` symlinks, base docs (VISION,
97
+ ARCHITECTURE, ABSTRACTIONS, GETTING-STARTED), ADRs with template and index, the
98
+ Sentrux structural quality gate, a `create-adr` slash command, and githooks.
99
+ Always ask which compatibility rule applies before running it — see
100
+ [SKILL.md](skills/init-agentkit/SKILL.md).
101
+
102
+ ### Session continuity
103
+
104
+ Long sessions stay cheap across usage-limit resets: a curated handoff is saved
105
+ to `.claude/session-handoff.md` while the session is warm, and this
106
+ repository's [SessionStart hook](.claude/hooks/session-start.mjs) injects a
107
+ fresh handoff into every new session automatically. The save/resume protocol
108
+ lives inside faberun as
109
+ [references/session-memory.md](skills/faberun/references/session-memory.md).
110
+
111
+ ## Development
112
+
113
+ - Node.js 22 or newer; the runtime is plain ESM `.mjs` with no runtime
114
+ dependencies. TypeScript is a development-only check (`checkJs`/`noEmit`).
115
+ - `npm run check` — syntax; `npm run typecheck` — static types; `npm test` — the suite.
116
+ - The skills of this repository stay active inside it through symlinks in
117
+ `.claude/skills/`.
118
+ - [AGENTS.md](AGENTS.md) is the canonical guidance; the other agent files are
119
+ symlinks to it — never edit them.
120
+
121
+ ## Inspiration
122
+
123
+ The memory-layer split (session handoff / campaign handoff / standing memory)
124
+ draws on [ai-memory](https://github.com/akitaonrails/ai-memory) by Akita on
125
+ Rails. Most other patterns here — compile-not-retrieve summaries,
126
+ start-of-session handoff injection, cross-harness workstreams — converged
127
+ independently.
128
+
129
+ ## License
130
+
131
+ [MIT](LICENSE).
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The faberun executable.
4
+ *
5
+ * It calls `runCli()` and nothing else. A bin wrapper that parses, validates or
6
+ * decides is a second CLI with no tests. The call has to be explicit because
7
+ * `src/cli.mjs` guards its own dispatch on `process.argv[1]` being itself --
8
+ * which, invoked through here, it is not.
9
+ *
10
+ * Usage, from a repository with a `.runs/` directory:
11
+ * faberun preflight <contract.json>
12
+ * faberun run <contract.json> [--detach]
13
+ * faberun resume <run-dir> [--node <id>] [--detach]
14
+ * faberun status|report|findings <run-dir> [--json]
15
+ * faberun doctor [--cwd <dir>] [--discover] [--json]
16
+ * faberun models [--probe] [--json]
17
+ * faberun campaign <subcommand> ...
18
+ * faberun metrics <campaign-id> [--json]
19
+ *
20
+ * `src/web/server.mjs` is the browser surface and is launched directly, not
21
+ * through here.
22
+ */
23
+ import { runCli } from "../src/cli.mjs";
24
+
25
+ await runCli();
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # Bench the Claude Code status-line script: 20 runs over a fixture heartbeat,
3
+ # then print the median wall time in milliseconds. Exits 1 when the median is
4
+ # 50 ms or more.
5
+ set -euo pipefail
6
+
7
+ here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
8
+ script="$here/claude-code.sh"
9
+
10
+ tmp=$(mktemp -d "${TMPDIR:-/tmp}/if-statusline-bench.XXXXXX")
11
+ trap 'rm -rf "$tmp"' EXIT
12
+
13
+ mkdir -p "$tmp/.runs/campaigns/if-bench"
14
+ cat >"$tmp/.runs/campaigns/if-bench/heartbeat.json" <<'JSON'
15
+ {"activeNode":"bench-node","attention":null,"campaignId":"if-bench","checkpoints":{"done":2,"total":5},"generatedAt":1756742580,"lastProgressAt":1756742400,"phase":"P1","runtime":"codex","schemaVersion":1,"state":"running","weightedUsed":1200000,"weightedCap":6000000}
16
+ JSON
17
+
18
+ escaped=${tmp//\\/\\\\}
19
+ escaped=${escaped//\"/\\\"}
20
+ input="{\"cwd\":\"$escaped\",\"workspace\":{\"current_dir\":\"$escaped\"}}"
21
+
22
+ times=()
23
+ for ((i = 0; i < 20; i++)); do
24
+ elapsed=$( { TIMEFORMAT='%R'; time "$script" <<<"$input" >/dev/null; } 2>&1 )
25
+ ms=$(awk -v s="$elapsed" 'BEGIN { printf "%d", s * 1000 + 0.5 }')
26
+ times+=("$ms")
27
+ done
28
+
29
+ median=$(printf '%s\n' "${times[@]}" | sort -n | awk '
30
+ { samples[NR] = $1 }
31
+ END {
32
+ if (NR % 2 == 1) value = samples[(NR + 1) / 2]
33
+ else value = (samples[NR / 2] + samples[NR / 2 + 1]) / 2
34
+ printf "%d", value + 0.5
35
+ }')
36
+
37
+ printf 'median %d ms\n' "$median"
38
+ if [ "$median" -ge 50 ]; then
39
+ printf 'statusline too slow: median %d ms >= 50 ms\n' "$median" >&2
40
+ exit 1
41
+ fi
42
+ exit 0
@@ -0,0 +1,80 @@
1
+ #!/bin/sh
2
+ # Claude Code statusLine renderer for faberun ambient liveness. Reads
3
+ # the session JSON on stdin, reads that repo's .runs/status.json pointer
4
+ # (rewritten every controller tick), and prints one line:
5
+ # <run-id> · <state> · <node> <elapsed> · $<usd> · needs you: <n>
6
+ # elapsedSec, costUsd and needsYou are precomputed by the controller, so this
7
+ # never touches a clock or a node process, only formats. No pointer, an
8
+ # unreadable file, or one over the 1 KiB cap prints an empty line, exit 0.
9
+ # jq is used when present; otherwise sed/grep pull the flat top-level fields.
10
+ #
11
+ # Allowance guard. The same session JSON carries rate_limits.five_hour
12
+ # (used_percentage plus a reset instant); the script otherwise reads stdin only
13
+ # for cwd. At ALLOWANCE_WARN_PCT or above, the line appends the warning and the
14
+ # command to switch the seat, so the operator moves harness before the credit
15
+ # is gone rather than after. jq reads the nested field directly, and the
16
+ # grep/sed fallback reaches it as well by flattening the JSON to one line
17
+ # first, so the warning is not silently jq-only. Operator harnesses other than
18
+ # claude carry no ambient rate signal at all: their exhaustion arrives as an
19
+ # invocation failure the engine classifies as quota_exhausted, and this script
20
+ # adds no second classifier for them.
21
+
22
+ set -u
23
+
24
+ # Percent of the five-hour allowance at which the line warns. A named number,
25
+ # not a policy: the threshold is also printed in the line the operator reads.
26
+ ALLOWANCE_WARN_PCT=85
27
+
28
+ session=$(cat)
29
+ repo=$(printf '%s' "$session" | sed -n 's/.*"cwd":"\([^"]*\)".*/\1/p;s/.*"current_dir":"\([^"]*\)".*/\1/p' | head -n 1)
30
+ pointer="$repo/.runs/status.json"
31
+
32
+ # The nested five_hour.used_percentage. jq when present; otherwise flatten the
33
+ # session JSON and pull the field out of the five_hour object with sed. The
34
+ # fallback matches a compact nested object, which is what the harness emits.
35
+ used_pct=
36
+ if command -v jq >/dev/null 2>&1; then
37
+ used_pct=$(printf '%s' "$session" | jq -r '.rate_limits.five_hour.used_percentage // empty' 2>/dev/null) || used_pct=
38
+ else
39
+ flat=$(printf '%s' "$session" | tr -d '\n')
40
+ used_pct=$(printf '%s' "$flat" | sed -n 's/.*"five_hour"[[:space:]]*:[[:space:]]*{[^}]*"used_percentage"[[:space:]]*:[[:space:]]*\([0-9][0-9.]*\).*/\1/p')
41
+ fi
42
+ # Integer compare: a fraction never decides crossing a whole threshold.
43
+ whole_pct=${used_pct%%.*}
44
+ warning=
45
+ if [ -n "$used_pct" ] && [ -n "$whole_pct" ] && [ "$whole_pct" -ge "$ALLOWANCE_WARN_PCT" ] 2>/dev/null; then
46
+ warning="[warn] claude 5h ${used_pct}% >=${ALLOWANCE_WARN_PCT}% · faberun seat switch --harness <id>"
47
+ fi
48
+
49
+ line=
50
+ if [ -n "$repo" ] && [ -f "$pointer" ] && [ "$(wc -c <"$pointer" | tr -d ' ')" -le 1024 ]; then
51
+ if command -v jq >/dev/null 2>&1; then
52
+ out=$(jq -r '[.runId,.state,(.activeNode//"-"),(.elapsedSec//"-"),(.costUsd//"-"),(.needsYou//0)]|@tsv' "$pointer" 2>/dev/null) || out=
53
+ set -f; IFS=' '; set -- $out; IFS=' '; set +f
54
+ runId=${1:-}; state=${2:-}; node=${3:-}; elapsedSec=${4:-}; costUsd=${5:-}; needsYou=${6:-}
55
+ else
56
+ field() { grep -o "\"$1\":\"[^\"]*\"\|\"$1\":[0-9.null-]*" "$pointer" | head -n 1 | sed "s/.*://;s/\"//g"; }
57
+ runId=$(field runId); state=$(field state); node=$(field activeNode)
58
+ elapsedSec=$(field elapsedSec); costUsd=$(field costUsd); needsYou=$(field needsYou)
59
+ fi
60
+ if [ -n "$runId" ] && [ -n "$state" ]; then
61
+ [ -n "$node" ] && [ "$node" != "null" ] || node=-
62
+ [ -n "$needsYou" ] && [ "$needsYou" != "null" ] || needsYou=0
63
+ elapsed=-
64
+ if [ -n "${elapsedSec:-}" ] && [ "$elapsedSec" != "null" ] && [ "$elapsedSec" != "-" ]; then
65
+ h=$((elapsedSec / 3600)); m=$(((elapsedSec % 3600) / 60)); s=$((elapsedSec % 60))
66
+ if [ "$h" -gt 0 ]; then elapsed="${h}h$(printf '%02d' "$m")m"
67
+ elif [ "$m" -gt 0 ]; then elapsed="${m}m$(printf '%02d' "$s")s"
68
+ else elapsed="${s}s"
69
+ fi
70
+ fi
71
+ usd=-
72
+ [ -n "${costUsd:-}" ] && [ "$costUsd" != "null" ] && [ "$costUsd" != "-" ] && usd="\$$costUsd"
73
+ line="$runId · $state · $node $elapsed · $usd · needs you: $needsYou"
74
+ fi
75
+ fi
76
+
77
+ if [ -n "$warning" ]; then
78
+ if [ -n "$line" ]; then line="$line · $warning"; else line="$warning"; fi
79
+ fi
80
+ printf '%s\n' "$line"
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "faberun",
3
+ "version": "0.3.0",
4
+ "description": "Faberun is a development orchestration system that turns intent into verified software: harness- and model-agnostic, it keeps the intent, coordinates the work, verifies the result and decides what happens next.",
5
+ "type": "module",
6
+ "bin": {
7
+ "faberun": "bin/faberun.mjs"
8
+ },
9
+ "repository": "github:feliperun/faberun",
10
+ "homepage": "https://github.com/feliperun/faberun",
11
+ "files": [
12
+ "bin",
13
+ "src",
14
+ "skills",
15
+ "integrations"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "scripts": {
21
+ "check": "for f in bin/*.mjs .claude/hooks/*.mjs src/*.mjs src/*/*.mjs src/*/*/*.mjs evals/*.mjs test/*.mjs test/*/*.mjs; do node --check \"$f\" || exit 1; done",
22
+ "typecheck": "tsc",
23
+ "test": "node --test test/*.test.mjs test/*/*.test.mjs",
24
+ "prepare": "husky"
25
+ },
26
+ "devDependencies": {
27
+ "@commitlint/cli": "^21.2.2",
28
+ "@commitlint/config-conventional": "^21.2.2",
29
+ "@types/node": "^22.10.0",
30
+ "husky": "^9.1.7",
31
+ "typescript": "^5.7.0"
32
+ }
33
+ }
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: faberun
3
+ description: Faberun: a harness- and model-agnostic development orchestrator that turns plans into verified software changes.
4
+ ---
5
+
6
+ # Faberun
7
+
8
+ Run a plan outside the main context, this session as the control plane.
9
+ Read [rules](references/rules.md) first.
10
+
11
+ | Action | Read |
12
+ | --- | --- |
13
+ | Author a contract (fallback) | [contract](references/contract.md), [engineering](references/engineering.md) |
14
+ | Launch, resume, integrate | [workflow](references/workflow.md), [operations](references/operations.md) |
15
+ | Dispatch a node | [handoffs](references/handoffs.md) |
16
+ | Supervise, answer attention | [operations](references/operations.md), [handoffs](references/handoffs.md) |
17
+ | Verify, judge, settle | [engineering](references/engineering.md), [handoffs](references/handoffs.md) |
18
+
19
+ Watchdog re-invocations:
20
+
21
+ node src/cli.mjs supervise <run-dir>
22
+ node src/cli.mjs supervise campaign <id> [--allow-main]
23
+
24
+ launchd: `StartInterval 300`; `launchctl load -w ~/Library/LaunchAgents/faberun.plist`.