forge-orkes 0.63.0 → 0.64.2

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 (25) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/hooks/forge-release-fold.sh +9 -1
  3. package/template/.claude/skills/jarvis/SKILL.md +204 -0
  4. package/template/.forge/checks/forge-board-render.sh +11 -5
  5. package/template/.forge/checks/forge-jarvis-answer.sh +85 -0
  6. package/template/.forge/checks/forge-jarvis-awareness.sh +133 -0
  7. package/template/.forge/checks/forge-jarvis-bridge-clean.sh +133 -0
  8. package/template/.forge/checks/forge-jarvis-dispatch.sh +198 -0
  9. package/template/.forge/checks/forge-jarvis-instruments.sh +149 -0
  10. package/template/.forge/checks/forge-jarvis-notify-logonly.sh +46 -0
  11. package/template/.forge/checks/forge-jarvis-promote.sh +116 -0
  12. package/template/.forge/checks/forge-jarvis-prwatch.sh +109 -0
  13. package/template/.forge/checks/forge-jarvis-relay.sh +242 -0
  14. package/template/.forge/checks/forge-jarvis.sh +161 -0
  15. package/template/.forge/checks/tests/forge-jarvis-answer.test.sh +75 -0
  16. package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +118 -0
  17. package/template/.forge/checks/tests/forge-jarvis-bridge-clean.test.sh +94 -0
  18. package/template/.forge/checks/tests/forge-jarvis-dispatch.test.sh +162 -0
  19. package/template/.forge/checks/tests/forge-jarvis-instruments.test.sh +64 -0
  20. package/template/.forge/checks/tests/forge-jarvis-notify-logonly.test.sh +63 -0
  21. package/template/.forge/checks/tests/forge-jarvis-promote.test.sh +102 -0
  22. package/template/.forge/checks/tests/forge-jarvis-prwatch.test.sh +66 -0
  23. package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +135 -0
  24. package/template/.forge/checks/tests/forge-jarvis.test.sh +122 -0
  25. package/template/.forge/templates/project.yml +11 -0
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env sh
2
+ # Fixture test suite for .forge/checks/forge-jarvis-prwatch.sh (m-37, step 4 B4(c); plan 68-02).
3
+ #
4
+ # Contract under test (handoff-step4-jarvis.md REV2 B4(c); NFR-036/Fork 3; acceptance #1/#6):
5
+ # - merged + staging-deploy success → the come-try text fires ONCE (at-most-once marker).
6
+ # - bound reached (PR stays OPEN) → clean exit, NO fire (never a standing daemon).
7
+ # - PR closed without a live deploy → self-terminate, no fire.
8
+ # - host truth only: merge + deploy come from gh (stubbed here via FORGE_JARVIS_GH).
9
+ #
10
+ # All runs use --interval 0 + a tiny --max-iterations so nothing sleeps or hangs. Each assertion
11
+ # prints WHY. RED-friendly: a missing script fails every case and exits non-zero.
12
+ set -u
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ PRWATCH="$SCRIPT_DIR/../forge-jarvis-prwatch.sh"
16
+ [ -f "$PRWATCH" ] || printf 'NOTE: %s missing — RED phase, every case should FAIL\n' "$PRWATCH" >&2
17
+
18
+ ROOT="$(mktemp -d)"
19
+ trap 'rm -rf "$ROOT"' EXIT INT TERM
20
+ PASSED=0; FAILED=0
21
+ ok() { PASSED=$((PASSED+1)); printf ' ok %s\n' "$1"; }
22
+ bad() { FAILED=$((FAILED+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; }
23
+
24
+ # stub gh: `pr view` → $STUB_STATE ; `api .../deployments` → statuses_url / state per $STUB_DEPLOY
25
+ STUB="$ROOT/gh-stub.sh"
26
+ cat > "$STUB" <<'EOF'
27
+ #!/usr/bin/env sh
28
+ case "$1" in
29
+ pr) printf '%s\n' "${STUB_STATE:-OPEN}" ;;
30
+ api) case "$2" in
31
+ *deployments\?environment*) [ "${STUB_DEPLOY:-no}" = yes ] && printf 'https://x/statuses\n' || printf '\n' ;;
32
+ *statuses*) [ "${STUB_DEPLOY:-no}" = yes ] && printf 'success\n' || printf 'pending\n' ;;
33
+ esac ;;
34
+ esac
35
+ EOF
36
+ chmod +x "$STUB"
37
+ MK="$ROOT/markers"
38
+
39
+ run() { FORGE_JARVIS_GH="$STUB" sh "$PRWATCH" --max-iterations 3 --interval 0 --marker-dir "$MK" "$@" 2>/dev/null; }
40
+
41
+ # 1. merged + deployed → come-try fires
42
+ out="$(STUB_STATE=MERGED STUB_DEPLOY=yes run --pr 42)"
43
+ case "$out" in *"come try"*"#42"*) ok "merged + staging deploy → come-try tap fires, names the PR" ;; *) bad "come-try fire" "$out" ;; esac
44
+
45
+ # 2. at-most-once: a second run does NOT fire again (marker suppresses)
46
+ out="$(STUB_STATE=MERGED STUB_DEPLOY=yes run --pr 42)"
47
+ [ -z "$out" ] && ok "come-try is at-most-once (marker suppresses the re-fire)" || bad "double come-try" "$out"
48
+
49
+ # 3. OPEN forever → bound reached, clean exit (rc 0), NO fire (not a daemon)
50
+ out="$(STUB_STATE=OPEN run --pr 99)"; rc=$?
51
+ if [ "$rc" = 0 ] && [ -z "$out" ]; then ok "OPEN → hard bound reached, clean exit, no fire (self-terminating)"; else bad "bound self-terminate" "rc=$rc out=$out"; fi
52
+
53
+ # 4. merged but NOT deployed → within the bound it does not fire (waits for the deploy, then stops)
54
+ out="$(STUB_STATE=MERGED STUB_DEPLOY=no run --pr 55)"; rc=$?
55
+ if [ "$rc" = 0 ] && [ -z "$out" ]; then ok "merged w/o deploy → no premature come-try; bound stops it cleanly"; else bad "merged-no-deploy" "rc=$rc out=$out"; fi
56
+
57
+ # 5. CLOSED → self-terminate immediately, no fire
58
+ out="$(STUB_STATE=CLOSED run --pr 77)"; rc=$?
59
+ if [ "$rc" = 0 ] && [ -z "$out" ]; then ok "closed PR → self-terminate, no fire"; else bad "closed terminate" "rc=$rc out=$out"; fi
60
+
61
+ # 6. --dry-run prints the resolved plan and never polls
62
+ out="$(sh "$PRWATCH" --pr 7 --staging-env prod --dry-run 2>/dev/null)"
63
+ case "$out" in *"pr=7"*"env=prod"*) ok "--dry-run prints the resolved plan (pr, env, bound), no polling" ;; *) bad "dry-run plan" "$out" ;; esac
64
+
65
+ printf '\n%s: %d passed, %d failed\n' "$(basename "$0")" "$PASSED" "$FAILED"
66
+ [ "$FAILED" = 0 ]
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env sh
2
+ # Fixture test suite for .forge/checks/forge-jarvis-relay.sh (m-37, step 4 B4; plan 68-01).
3
+ #
4
+ # Contract under test (handoff-step4-jarvis.md REV2 B4(a/b/e); FR-200..202; NFR-036/040;
5
+ # acceptance #2/#3/#4):
6
+ # - each §8 type + halt → correct TYPED text; ambiguity relays the question VERBATIM.
7
+ # - HOST-TRUTH recompute wins: a payload claiming a merge the fold DENIES relays a loud
8
+ # DISCREPANCY (the standing lesson) — the state claim is never sourced from the payload.
9
+ # - markers are at-most-once (a re-scan of unchanged log re-buzzes NOTHING; a new line still
10
+ # relays — no double-buzz, no silent loss), keyed by log-line offset.
11
+ # - --mode catchup surfaces pending pings as ONE "while you were away" line, then marks them.
12
+ # - --audit-sources passes (no state claim sourced from the payload).
13
+ #
14
+ # The fold is stubbed via FORGE_JARVIS_FOLD so no real repo/main state is needed. Each assertion
15
+ # prints WHY. RED-friendly: a missing/broken script fails every case and exits non-zero.
16
+ #
17
+ # Usage: ./.forge/checks/tests/forge-jarvis-relay.test.sh [--markers] (both run by default)
18
+ set -u
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
+ RELAY="$SCRIPT_DIR/../forge-jarvis-relay.sh"
22
+ [ -f "$RELAY" ] || printf 'NOTE: %s missing — RED phase, every case should FAIL\n' "$RELAY" >&2
23
+
24
+ ROOT="$(mktemp -d)"
25
+ trap 'rm -rf "$ROOT"' EXIT INT TERM
26
+
27
+ PASSED=0; FAILED=0
28
+ ok() { PASSED=$((PASSED+1)); printf ' ok %s\n' "$1"; }
29
+ bad() { FAILED=$((FAILED+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; }
30
+
31
+ # --- stub folds: echo a fixed release_state so the relay recomputes against a KNOWN host truth --
32
+ FOLD_UNMERGED="$ROOT/fold-unmerged.sh"
33
+ printf '#!/usr/bin/env sh\necho "release_state=unmerged reason=not-on-main"\n' > "$FOLD_UNMERGED"
34
+ FOLD_VALIDATED="$ROOT/fold-validated.sh"
35
+ printf '#!/usr/bin/env sh\necho "release_state=validated reason=signed-off"\n' > "$FOLD_VALIDATED"
36
+ chmod +x "$FOLD_UNMERGED" "$FOLD_VALIDATED"
37
+
38
+ # one(): run --one with a given TSV line + stub fold; echo the relay text
39
+ one() { # one <fold> <tsv-line>
40
+ FORGE_JARVIS_FOLD="$1" sh "$RELAY" --one "$2" --milestone m-37 2>/dev/null
41
+ }
42
+ TAB="$(printf '\t')"
43
+
44
+ # 1. ambiguity → the QUESTION verbatim, no host state leaked in
45
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}ambiguity${TAB}JWT or sessions?")"
46
+ case "$out" in
47
+ *"JWT or sessions?"*) case "$out" in *release_state*) bad "ambiguity verbatim" "leaked host state: $out" ;; *) ok "ambiguity relays the question verbatim (not a state claim)" ;; esac ;;
48
+ *) bad "ambiguity verbatim" "question missing: $out" ;;
49
+ esac
50
+
51
+ # 2. fork → names the sketch path
52
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}fork${TAB}/tmp/sketch-a.png")"
53
+ case "$out" in *"/tmp/sketch-a.png"*) ok "fork names the sketch path to look at" ;; *) bad "fork path" "$out" ;; esac
54
+
55
+ # 3. irreversible → verbatim, named
56
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}irreversible${TAB}force-push to main?")"
57
+ case "$out" in *"force-push to main?"*) ok "irreversible relays the action, named" ;; *) bad "irreversible" "$out" ;; esac
58
+
59
+ # 4. budget → verbatim, named (runner's own tally — no host source)
60
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}budget${TAB}cap \$25 reached")"
61
+ case "$out" in *"cap \$25 reached"*) ok "budget relays the runner's tally, named" ;; *) bad "budget" "$out" ;; esac
62
+
63
+ # 5. honest done (payload makes NO merge claim), fold=unmerged → host truth, NO discrepancy
64
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}done${TAB}Phases complete, PR opened")"
65
+ case "$out" in
66
+ *DISCREPANCY*) bad "honest done" "false discrepancy on an honest ping: $out" ;;
67
+ *"release_state=unmerged"*) ok "honest done phrased from host release_state (no false discrepancy)" ;;
68
+ *) bad "honest done" "no host state in text: $out" ;;
69
+ esac
70
+
71
+ # 6. LYING halt: payload claims merged, fold says unmerged → LOUD discrepancy, host wins
72
+ out="$(one "$FOLD_UNMERGED" "2026-07-21T10:00:00Z${TAB}halt${TAB}All done and merged to main!")"
73
+ case "$out" in
74
+ *DISCREPANCY*"release_state=unmerged"*) ok "lying halt → discrepancy; host truth (unmerged) wins over the payload's merge claim" ;;
75
+ *) bad "lying halt discrepancy" "expected DISCREPANCY + release_state=unmerged, got: $out" ;;
76
+ esac
77
+
78
+ # 7. done claims merged AND fold=validated → host CONFIRMS, no discrepancy
79
+ out="$(one "$FOLD_VALIDATED" "2026-07-21T10:00:00Z${TAB}done${TAB}merged and validated")"
80
+ case "$out" in
81
+ *DISCREPANCY*) bad "confirmed merge" "false discrepancy when host agrees: $out" ;;
82
+ *"release_state=validated"*) ok "merge claim + host agrees (validated) → confirmed, no discrepancy" ;;
83
+ *) bad "confirmed merge" "$out" ;;
84
+ esac
85
+
86
+ # 8. --audit-sources passes (no state claim sourced from the payload)
87
+ if FORGE_JARVIS_FOLD="$FOLD_UNMERGED" sh "$RELAY" --audit-sources >/dev/null 2>&1; then
88
+ ok "--audit-sources: relay speaks host truth only"
89
+ else
90
+ bad "--audit-sources" "static source audit failed"
91
+ fi
92
+
93
+ # --- markers: at-most-once, no double-buzz, no silent loss ------------------------------------
94
+ WT="$ROOT/slice-wt"; mkdir -p "$WT/.forge/runner-work"
95
+ LOG="$WT/.forge/runner-work/notify.log"; MK="$WT/.forge/runner-work/relay-markers"
96
+ printf '2026-07-21T10:00:00Z\tambiguity\tfirst?\n' >> "$LOG"
97
+ printf '2026-07-21T10:01:00Z\tdone\tPR opened\n' >> "$LOG"
98
+
99
+ scan() { FORGE_JARVIS_FOLD="$FOLD_UNMERGED" sh "$RELAY" --scan "$LOG" --marker-dir "$MK" --milestone m-37 2>/dev/null; }
100
+
101
+ # 9. first scan relays BOTH lines
102
+ out="$(scan)"; n="$(printf '%s\n' "$out" | grep -c .)"
103
+ [ "$n" -eq 2 ] && ok "first --scan relays all unmarked lines ($n)" || bad "first scan count" "expected 2 got $n: $out"
104
+
105
+ # 10. re-scan of UNCHANGED log relays NOTHING (at-most-once — no double-buzz)
106
+ out="$(scan)"; n="$(printf '%s\n' "$out" | grep -c .)"
107
+ [ "$n" -eq 0 ] && ok "re-scan of unchanged log re-buzzes nothing (at-most-once markers)" || bad "double-buzz" "expected 0 got $n: $out"
108
+
109
+ # 11. a NEW line still relays (no silent loss of a genuinely-new ping)
110
+ printf '2026-07-21T10:02:00Z\thalt\tstuck\n' >> "$LOG"
111
+ out="$(scan)"; n="$(printf '%s\n' "$out" | grep -c .)"
112
+ [ "$n" -eq 1 ] && ok "a new line still relays after prior lines were marked (no silent loss)" || bad "new-line relay" "expected 1 got $n: $out"
113
+
114
+ # 12. markers exist keyed by offset (line number)
115
+ if [ -f "$MK/1.relayed" ] && [ -f "$MK/2.relayed" ] && [ -f "$MK/3.relayed" ]; then
116
+ ok "markers written keyed by log-line offset (1,2,3)"
117
+ else
118
+ bad "offset markers" "missing marker(s): $(ls "$MK" 2>/dev/null | tr '\n' ' ')"
119
+ fi
120
+
121
+ # 13. --mode catchup surfaces pending pings as ONE while-you-were-away line
122
+ WT2="$ROOT/slice-wt2"; mkdir -p "$WT2/.forge/runner-work"
123
+ LOG2="$WT2/.forge/runner-work/notify.log"
124
+ printf '2026-07-21T09:00:00Z\tbudget\tcap hit\n' >> "$LOG2"
125
+ printf '2026-07-21T09:05:00Z\tirreversible\tdrop table?\n' >> "$LOG2"
126
+ out="$(FORGE_JARVIS_FOLD="$FOLD_UNMERGED" sh "$RELAY" --scan "$LOG2" --milestone m-37 --mode catchup 2>/dev/null)"
127
+ lines="$(printf '%s\n' "$out" | grep -c .)"
128
+ case "$out" in
129
+ *"While you were away"*) [ "$lines" -eq 1 ] && ok "catchup surfaces pending pings as ONE while-you-were-away line" || bad "catchup one-line" "got $lines lines: $out" ;;
130
+ *) bad "catchup summary" "no while-you-were-away line: $out" ;;
131
+ esac
132
+
133
+ # --- summary -----------------------------------------------------------------
134
+ printf '\n%s: %d passed, %d failed\n' "$(basename "$0")" "$PASSED" "$FAILED"
135
+ [ "$FAILED" = 0 ]
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env sh
2
+ # Fixture-repo test suite for .forge/checks/forge-jarvis.sh --dry-run (m-37, Brief-R2 step 4 B2).
3
+ #
4
+ # Contract under test (handoff-step4-jarvis.md REV2 B2 + operator ruling 6 +
5
+ # runbook-B1-live-test-recon.md "After B1" bake-ins):
6
+ # --dry-run prints the fully resolved launch command and NEVER executes:
7
+ # caffeinate -s claude remote-control --name <sticky> --spawn worktree --capacity <N>
8
+ # Name: jarvis-<repo-basename> (from the git COMMON dir — stable across worktrees),
9
+ # + optional suffix from jarvis.name_suffix, overridden per machine by
10
+ # FORGE_JARVIS_NAME_SUFFIX (probe ii: the sticky name is the only discoverability
11
+ # anchor). Capacity: --capacity flag > jarvis.capacity > 4. NO token export ever
12
+ # (probe iv — asserted here as an env-name absence in the output). Refusals:
13
+ # jarvis.enabled: false, or not a git repo → non-zero exit, NO command on stdout.
14
+ #
15
+ # Builds throwaway git repos in a mktemp dir, offline by construction; only ever
16
+ # invokes --dry-run, so `claude`/`caffeinate` need not exist (dry-run is pure
17
+ # resolution — that determinism is itself part of the contract).
18
+ #
19
+ # Usage: ./.forge/checks/tests/forge-jarvis.test.sh
20
+ # RED-phase friendly: a missing/non-executable script fails every case, exits non-zero.
21
+ set -u
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
+ JARVIS="$SCRIPT_DIR/../forge-jarvis.sh"
25
+
26
+ [ -f "$JARVIS" ] || printf 'NOTE: %s missing — RED phase, every case should FAIL\n' "$JARVIS" >&2
27
+
28
+ ROOT="$(mktemp -d)"
29
+ trap 'rm -rf "$ROOT"' EXIT INT TERM
30
+ GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
31
+ export GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM
32
+ unset FORGE_JARVIS_NAME_SUFFIX 2>/dev/null || true
33
+
34
+ PASSED=0; FAILED=0
35
+ ok() { PASSED=$((PASSED+1)); printf ' ok %s\n' "$1"; }
36
+ bad() { FAILED=$((FAILED+1)); printf ' FAIL %s\n %s\n' "$1" "$2"; }
37
+
38
+ # --- fixture repo (basename "proj" → expected sticky base jarvis-proj) -------
39
+ REPO="$ROOT/proj"
40
+ mkdir -p "$REPO/.forge"
41
+ git -C "$ROOT" init -q -b main "$REPO"
42
+
43
+ # 1. default derivation — no jarvis: block → full default command
44
+ out="$(cd "$REPO" && sh "$JARVIS" --dry-run 2>/dev/null)"; rc=$?
45
+ want="caffeinate -s claude remote-control --name jarvis-proj --spawn worktree --capacity 4"
46
+ if [ "$rc" = 0 ] && [ "$out" = "$want" ]; then
47
+ ok "default derivation: no config → jarvis-proj, capacity 4"
48
+ else
49
+ bad "default derivation" "rc=$rc out=$out"
50
+ fi
51
+
52
+ # 2. caffeinate -s present (operator ruling 6 — asserted on its own so a future
53
+ # edit dropping the wrap fails a NAMED case, not just the exact-match above)
54
+ case "$out" in
55
+ "caffeinate -s "*) ok "caffeinate -s wraps the resolved command (ruling 6 / probe iii)" ;;
56
+ *) bad "caffeinate -s wrap" "out=$out" ;;
57
+ esac
58
+
59
+ # 3. config override — jarvis: block with name_suffix + capacity
60
+ cat > "$REPO/.forge/project.yml" <<'EOF'
61
+ project:
62
+ name: "proj"
63
+ jarvis:
64
+ enabled: true
65
+ name_suffix: "alpha"
66
+ capacity: 2
67
+ EOF
68
+ out="$(cd "$REPO" && sh "$JARVIS" --dry-run 2>/dev/null)"; rc=$?
69
+ want="caffeinate -s claude remote-control --name jarvis-proj-alpha --spawn worktree --capacity 2"
70
+ if [ "$rc" = 0 ] && [ "$out" = "$want" ]; then
71
+ ok "config override: name_suffix + capacity from jarvis: block"
72
+ else
73
+ bad "config override" "rc=$rc out=$out"
74
+ fi
75
+
76
+ # 4. per-machine env seam — FORGE_JARVIS_NAME_SUFFIX beats jarvis.name_suffix
77
+ out="$(cd "$REPO" && FORGE_JARVIS_NAME_SUFFIX=work sh "$JARVIS" --dry-run 2>/dev/null)"; rc=$?
78
+ case "$out" in
79
+ *"--name jarvis-proj-work "*) ok "env FORGE_JARVIS_NAME_SUFFIX overrides config suffix (per-machine seam)" ;;
80
+ *) bad "env suffix override" "rc=$rc out=$out" ;;
81
+ esac
82
+
83
+ # 5. capacity flag override — --capacity beats jarvis.capacity
84
+ out="$(cd "$REPO" && sh "$JARVIS" --dry-run --capacity 7 2>/dev/null)"; rc=$?
85
+ case "$out" in
86
+ *"--capacity 7") ok "--capacity flag overrides config capacity" ;;
87
+ *) bad "capacity flag override" "rc=$rc out=$out" ;;
88
+ esac
89
+
90
+ # 6. no token export — the resolved command never carries an auth env assignment
91
+ # (probe iv: keychain authenticates; a token in the launch line would be the
92
+ # exact regression the B1 bake-in forbids)
93
+ case "$out" in
94
+ *CLAUDE_CODE_OAUTH_TOKEN*|*ANTHROPIC_API_KEY*) bad "no token in resolved command" "out=$out" ;;
95
+ *) ok "no token export in the resolved command (probe iv)" ;;
96
+ esac
97
+
98
+ # 7. NEG: enabled: false → clean refusal (non-zero, no command on stdout)
99
+ cat > "$REPO/.forge/project.yml" <<'EOF'
100
+ jarvis:
101
+ enabled: false
102
+ EOF
103
+ out="$(cd "$REPO" && sh "$JARVIS" --dry-run 2>/dev/null)"; rc=$?
104
+ if [ "$rc" != 0 ] && [ -z "$out" ]; then
105
+ ok "neg-control: jarvis.enabled false → refusal, no command emitted"
106
+ else
107
+ bad "neg-control enabled:false" "rc=$rc out=$out"
108
+ fi
109
+
110
+ # 8. NEG: not a git repo → clean refusal (RC --spawn worktree needs one)
111
+ NOREPO="$ROOT/norepo"
112
+ mkdir -p "$NOREPO"
113
+ out="$(cd "$NOREPO" && sh "$JARVIS" --dry-run 2>/dev/null)"; rc=$?
114
+ if [ "$rc" != 0 ] && [ -z "$out" ]; then
115
+ ok "neg-control: outside a git repo → refusal, no command emitted"
116
+ else
117
+ bad "neg-control non-repo" "rc=$rc out=$out"
118
+ fi
119
+
120
+ # --- summary -----------------------------------------------------------------
121
+ printf '\n%s: %d passed, %d failed\n' "$(basename "$0")" "$PASSED" "$FAILED"
122
+ [ "$FAILED" = 0 ]
@@ -138,6 +138,17 @@ models:
138
138
  # backlog: true # lane is PROJECT-LOCAL config composing these; the framework carries none.
139
139
  # deferred: true # status / backlog (explicit-intake, M21) / deferred view.
140
140
 
141
+ # jarvis: # OPTIONAL front door (Brief-R2 step 4) — the repo's standing presence: a
142
+ # # named `claude remote-control` server whose pre-created session is Jarvis,
143
+ # # started by the operator via .forge/checks/forge-jarvis.sh. Unlike board:,
144
+ # # absence does NOT mean inert — the launcher is operator-invoked, never
145
+ # # ambient, and works with no block (defaults below). See docs/jarvis.md.
146
+ # enabled: true # explicit false = the launcher refuses (the door is switched off)
147
+ # name_suffix: "" # appended to the sticky name jarvis-<repo-basename>; on multi-machine
148
+ # # repos each machine overrides via env FORGE_JARVIS_NAME_SUFFIX so two
149
+ # # live presences never share one name
150
+ # capacity: 4 # RC server session capacity (--capacity), default 4
151
+
141
152
  risks: # What could go wrong?
142
153
  - risk: ""
143
154
  mitigation: ""