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.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/bin/faberun.mjs +25 -0
- package/integrations/claude-code/statusline-bench.sh +42 -0
- package/integrations/claude-code/statusline.sh +80 -0
- package/package.json +33 -0
- package/skills/faberun/SKILL.md +24 -0
- package/skills/faberun/references/contract.md +380 -0
- package/skills/faberun/references/engineering.md +29 -0
- package/skills/faberun/references/handoffs.md +26 -0
- package/skills/faberun/references/operations.md +184 -0
- package/skills/faberun/references/rules.md +35 -0
- package/skills/faberun/references/workflow.md +23 -0
- package/skills/init-agentkit/SKILL.md +108 -0
- package/skills/init-agentkit/scripts/install-agentkit.sh +127 -0
- package/skills/init-agentkit/templates/.claude/commands/create-adr.md +44 -0
- package/skills/init-agentkit/templates/.github/workflows/quality.yml +43 -0
- package/skills/init-agentkit/templates/.sentrux/baseline.json +9 -0
- package/skills/init-agentkit/templates/.sentrux/rules.toml +21 -0
- package/skills/init-agentkit/templates/AGENTS.md +110 -0
- package/skills/init-agentkit/templates/docs/ABSTRACTIONS.md +30 -0
- package/skills/init-agentkit/templates/docs/ARCHITECTURE.md +31 -0
- package/skills/init-agentkit/templates/docs/GETTING-STARTED.md +44 -0
- package/skills/init-agentkit/templates/docs/VISION.md +33 -0
- package/skills/init-agentkit/templates/docs/adr/0001-record-architecture-decisions.md +36 -0
- package/skills/init-agentkit/templates/docs/adr/0002-root-managed-ai-guidance.md +37 -0
- package/skills/init-agentkit/templates/docs/adr/0003-sentrux-structural-quality-gates.md +49 -0
- package/skills/init-agentkit/templates/docs/adr/README.md +52 -0
- package/skills/init-agentkit/templates/docs/sentrux.md +66 -0
- package/skills/init-agentkit/templates/githooks/commit-msg +22 -0
- package/skills/init-agentkit/templates/githooks/pre-commit +32 -0
- package/src/campaign/brief.mjs +394 -0
- package/src/campaign/chain.mjs +555 -0
- package/src/campaign/handoff.mjs +516 -0
- package/src/campaign/index.mjs +300 -0
- package/src/campaign/journal.mjs +347 -0
- package/src/campaign/layout.mjs +51 -0
- package/src/campaign/metrics-evals.mjs +25 -0
- package/src/campaign/metrics.mjs +517 -0
- package/src/campaign/projection.mjs +250 -0
- package/src/campaign/record.mjs +102 -0
- package/src/campaign/unpark.mjs +56 -0
- package/src/cli/brand.mjs +205 -0
- package/src/cli/campaign.mjs +730 -0
- package/src/cli/contract.mjs +67 -0
- package/src/cli/init.mjs +170 -0
- package/src/cli/launch.mjs +239 -0
- package/src/cli/seat.mjs +139 -0
- package/src/cli/setup.mjs +294 -0
- package/src/cli/skills.mjs +105 -0
- package/src/cli/update.mjs +216 -0
- package/src/cli.mjs +525 -0
- package/src/contract/articles.mjs +12 -0
- package/src/contract/assert.mjs +162 -0
- package/src/contract/definition-of-done.mjs +97 -0
- package/src/contract/final-verification.mjs +96 -0
- package/src/contract/index.mjs +641 -0
- package/src/contract/judge-envelope.mjs +25 -0
- package/src/contract/review-modes.mjs +151 -0
- package/src/contract/runtime.mjs +204 -0
- package/src/contract/schema-version.mjs +25 -0
- package/src/contract/scope-findings.mjs +77 -0
- package/src/contract/snapshot.mjs +639 -0
- package/src/contract/task-packet.mjs +495 -0
- package/src/contract/untrusted.mjs +75 -0
- package/src/contract/verification.mjs +185 -0
- package/src/contract/worker-result.mjs +138 -0
- package/src/engine/assignment.mjs +63 -0
- package/src/engine/backoff.mjs +492 -0
- package/src/engine/bulk-read.mjs +361 -0
- package/src/engine/cancel.mjs +177 -0
- package/src/engine/detach.mjs +101 -0
- package/src/engine/dispatch.mjs +752 -0
- package/src/engine/failover.mjs +192 -0
- package/src/engine/gate.mjs +183 -0
- package/src/engine/judge-gate.mjs +517 -0
- package/src/engine/lifecycle.mjs +772 -0
- package/src/engine/live-preflight.mjs +299 -0
- package/src/engine/mutation.mjs +146 -0
- package/src/engine/notify-queue.mjs +327 -0
- package/src/engine/process-identity.mjs +72 -0
- package/src/engine/process.mjs +774 -0
- package/src/engine/prompts.mjs +289 -0
- package/src/engine/recover.mjs +300 -0
- package/src/engine/result-file.mjs +222 -0
- package/src/engine/resume.mjs +635 -0
- package/src/engine/retry.mjs +334 -0
- package/src/engine/review.mjs +228 -0
- package/src/engine/run-command.mjs +287 -0
- package/src/engine/run-identity.mjs +411 -0
- package/src/engine/runtime-discovery.mjs +235 -0
- package/src/engine/scheduler.mjs +526 -0
- package/src/engine/scope.mjs +378 -0
- package/src/engine/settle.mjs +207 -0
- package/src/engine/state.mjs +148 -0
- package/src/engine/supervise.mjs +713 -0
- package/src/engine/verify.mjs +167 -0
- package/src/harnesses/agy/index.mjs +62 -0
- package/src/harnesses/catalogue.mjs +509 -0
- package/src/harnesses/claude/index.mjs +90 -0
- package/src/harnesses/codex/index.mjs +87 -0
- package/src/harnesses/dsh/closed-packet.patch.yml +42 -0
- package/src/harnesses/dsh/index.mjs +210 -0
- package/src/harnesses/dsh/runner.mjs +259 -0
- package/src/harnesses/exec-jsonl/index.mjs +788 -0
- package/src/harnesses/index.mjs +508 -0
- package/src/harnesses/protocol.mjs +531 -0
- package/src/harnesses/replay/bin.mjs +386 -0
- package/src/harnesses/replay/index.mjs +238 -0
- package/src/harnesses/zcode/index.mjs +276 -0
- package/src/host/config.mjs +87 -0
- package/src/host/home.mjs +149 -0
- package/src/host/package.mjs +23 -0
- package/src/host/preflight.mjs +520 -0
- package/src/host/tool-policy-decisions.mjs +341 -0
- package/src/host/tool-policy-hook.mjs +270 -0
- package/src/notify/index.mjs +359 -0
- package/src/notify/os-macos.mjs +81 -0
- package/src/repo/declared-paths.mjs +220 -0
- package/src/repo/integrate.mjs +546 -0
- package/src/repo/scope-closure.mjs +665 -0
- package/src/repo/signal-block.mjs +16 -0
- package/src/repo/signal.mjs +222 -0
- package/src/repo/source-identity.mjs +295 -0
- package/src/repo/workspace.mjs +557 -0
- package/src/repo/worktree.mjs +352 -0
- package/src/report/final.mjs +200 -0
- package/src/report/metrics-report.mjs +99 -0
- package/src/report/next.mjs +383 -0
- package/src/report/render.mjs +716 -0
- package/src/run/disk-gc.mjs +251 -0
- package/src/run/lock.mjs +329 -0
- package/src/run/node-store.mjs +62 -0
- package/src/run/operations.mjs +286 -0
- package/src/run/store.mjs +187 -0
- package/src/run/usage.mjs +337 -0
- package/src/seat/harnesses.mjs +83 -0
- package/src/seat/index.mjs +239 -0
- package/src/seat/tmux.mjs +208 -0
- package/src/util.mjs +0 -0
- package/src/web/api.mjs +371 -0
- package/src/web/boundary.mjs +88 -0
- package/src/web/index.html +299 -0
- 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).
|
package/bin/faberun.mjs
ADDED
|
@@ -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`.
|