instar 1.3.711 → 1.3.713
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/.claude/skills/autonomous/SKILL.md +49 -0
- package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +103 -9
- package/.claude/skills/autonomous/scripts/setup-autonomous.sh +47 -0
- package/dist/commands/init.js +9 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +19 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/AutonomousRunStore.d.ts +200 -0
- package/dist/core/AutonomousRunStore.d.ts.map +1 -0
- package/dist/core/AutonomousRunStore.js +402 -0
- package/dist/core/AutonomousRunStore.js.map +1 -0
- package/dist/core/CompletionEvaluator.d.ts +24 -0
- package/dist/core/CompletionEvaluator.d.ts.map +1 -1
- package/dist/core/CompletionEvaluator.js +22 -1
- package/dist/core/CompletionEvaluator.js.map +1 -1
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +3 -2
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +56 -3
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ScopeAccretionCorroboration.d.ts +79 -0
- package/dist/core/ScopeAccretionCorroboration.d.ts.map +1 -0
- package/dist/core/ScopeAccretionCorroboration.js +159 -0
- package/dist/core/ScopeAccretionCorroboration.js.map +1 -0
- package/dist/core/ScopeAccretionRatifier.d.ts +93 -0
- package/dist/core/ScopeAccretionRatifier.d.ts.map +1 -0
- package/dist/core/ScopeAccretionRatifier.js +228 -0
- package/dist/core/ScopeAccretionRatifier.js.map +1 -0
- package/dist/core/ScopeAccretionSweep.d.ts +83 -0
- package/dist/core/ScopeAccretionSweep.d.ts.map +1 -0
- package/dist/core/ScopeAccretionSweep.js +353 -0
- package/dist/core/ScopeAccretionSweep.js.map +1 -0
- package/dist/core/WorkingSetManifest.d.ts.map +1 -1
- package/dist/core/WorkingSetManifest.js +26 -1
- package/dist/core/WorkingSetManifest.js.map +1 -1
- package/dist/core/installCodexHooks.d.ts.map +1 -1
- package/dist/core/installCodexHooks.js +9 -0
- package/dist/core/installCodexHooks.js.map +1 -1
- package/dist/core/types.d.ts +29 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +8 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +18 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +28 -0
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/scaffold/templates.js +2 -2
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +638 -1
- package/dist/server/routes.js.map +1 -1
- package/dist/server/specReviewRoutes.d.ts +6 -0
- package/dist/server/specReviewRoutes.d.ts.map +1 -1
- package/dist/server/specReviewRoutes.js +30 -0
- package/dist/server/specReviewRoutes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-scrape-fixture-realness.js +40 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +2 -2
- package/upgrades/1.3.712.md +41 -0
- package/upgrades/1.3.713.md +65 -0
- package/upgrades/side-effects/autonomous-scope-accretion-completion.md +77 -0
- package/upgrades/side-effects/tone-gate-rule-id-contract.md +78 -0
|
@@ -104,6 +104,11 @@ Write this content:
|
|
|
104
104
|
when set, the stop-hook RUNS it on a met:true verdict and only allows the exit if it ALSO
|
|
105
105
|
passes (fail/timeout → keep working). This sentinel is the PostUpdateMigrator marker that
|
|
106
106
|
re-deploys this SKILL.md to existing agents. Spec: autonomous-completion-real-checks.md -->
|
|
107
|
+
<!-- SCOPE_ACCRETION — scope-accretion completion discipline: register the run server-side
|
|
108
|
+
(POST /autonomous/register) and record the returned run_id in the frontmatter; the server
|
|
109
|
+
then holds completion while deliverables THIS run creates sit unbuilt, uncorroborated, and
|
|
110
|
+
unratified. This sentinel is the PostUpdateMigrator marker that re-deploys this SKILL.md to
|
|
111
|
+
existing agents. Spec: autonomous-scope-accretion-completion.md -->
|
|
107
112
|
|
|
108
113
|
```markdown
|
|
109
114
|
---
|
|
@@ -124,6 +129,7 @@ completion_mode: condition # "condition" (default) | "promise-fallback"
|
|
|
124
129
|
promise_fallback_reason: "" # one line, REQUIRED iff completion_mode == promise-fallback
|
|
125
130
|
completion_promise: "ALL_TASKS_COMPLETE" # retained as the fallback token
|
|
126
131
|
hard_blocker_nonce: "{a random per-run token — get via: openssl rand -hex 8}"
|
|
132
|
+
run_id: "{the runId returned by POST /autonomous/register — see step 2b-2 below}"
|
|
127
133
|
# OPTIONAL real-check (ACT-152): a command that must exit 0 before the run may stop. When set, a
|
|
128
134
|
# met:true verdict RUNS it and gates the exit on it (fail/timeout → keep working). Omit if the
|
|
129
135
|
# goal isn't checkable by a single command. work_dir is captured by setup so a relative command
|
|
@@ -171,6 +177,27 @@ echo $CLAUDE_CODE_SESSION_ID
|
|
|
171
177
|
```
|
|
172
178
|
Then include the output in the `session_id:` field. This ensures session isolation works.
|
|
173
179
|
|
|
180
|
+
**2b-2. Register the run server-side (SCOPE_ACCRETION — MANDATORY when a report topic exists).**
|
|
181
|
+
The server takes the start snapshot (config, condition, declared deliverables, git base-root
|
|
182
|
+
SHAs), mints the `runId`, and from then on the completion judge structurally HOLDS "done" while
|
|
183
|
+
deliverables this run itself creates (specs, audits, runbooks, scripts) sit unbuilt without
|
|
184
|
+
corroboration (a merged PR / a converged report) or explicit operator ratification:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
PORT=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))")
|
|
188
|
+
AUTH=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))")
|
|
189
|
+
jq -nc --arg t "TOPIC_ID" --arg c "YOUR completion_condition text" --arg w "$(pwd)" \
|
|
190
|
+
--arg s "STARTED_AT_ISO" --arg e "END_AT_ISO" --arg sid "$CLAUDE_CODE_SESSION_ID" \
|
|
191
|
+
'{topicId:$t,condition:$c,workDir:$w,startedAt:$s,endAt:$e,sessionId:$sid,declaredDeliverables:[]}' \
|
|
192
|
+
| curl -s -H "Authorization: Bearer $AUTH" -H 'Content-Type: application/json' \
|
|
193
|
+
--data-binary @- "http://localhost:${PORT}/autonomous/register"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Write the returned `runId` into the state file's `run_id:` frontmatter field. If the mission is
|
|
197
|
+
GENUINELY draft-only (the operator asked for drafts, not builds), list those exact repo-relative
|
|
198
|
+
paths in `declaredDeliverables` — that list is operator-visible at setup and immutable mid-run.
|
|
199
|
+
If the server is unreachable, leave `run_id` empty and continue — the gate degrades honestly.
|
|
200
|
+
|
|
174
201
|
**WHY NOT bash script?** Running `bash setup-autonomous.sh` creates a subprocess that does NOT inherit `CLAUDE_CODE_SESSION_ID`. The state file ends up with an empty session_id, which causes the hook to leak into all sessions. Always write the state file from within Claude Code's context.
|
|
175
202
|
|
|
176
203
|
**SESSION ISOLATION**: The stop hook checks `session_id` — it only blocks the session that activated autonomous mode. Other sessions on the same machine pass through unaffected.
|
|
@@ -207,6 +234,28 @@ The stop hook will catch every attempt to exit and feed your task list back. Eac
|
|
|
207
234
|
5. Move to next task
|
|
208
235
|
6. Send progress reports at the configured interval
|
|
209
236
|
|
|
237
|
+
### Scope Accretion — work YOU create joins YOUR completion bar (Layer A duty)
|
|
238
|
+
|
|
239
|
+
The completion bar is NOT frozen at session start. When you discover new in-scope work and
|
|
240
|
+
draft it (a spec, an audit, a runbook, a script), that artifact joins your bar: the server's
|
|
241
|
+
git-truth sweep sees it regardless of HOW it was written (Write tool, Bash heredoc, `tee`, a
|
|
242
|
+
subagent), and the judge will HOLD "done" until it is built+corroborated (merged PR / converged
|
|
243
|
+
report), was declared at setup, or the operator ratifies deferring it. "The documented stretch
|
|
244
|
+
(out of completion condition)" is the exact anti-pattern this closes — Deferral = Deletion.
|
|
245
|
+
|
|
246
|
+
**Your recording duty (Layer A):** the moment you create a deliverable, append to the state
|
|
247
|
+
file's task list:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
- [ ] ACCRETED(<ISO date>): <repo-relative path> — build/converge/deliver, or obtain operator ratification
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This is a willpower-assist for the honest case and carries zero safety weight — the server-side
|
|
254
|
+
sweep is the enforcement. If deferral is genuinely right, ASK the operator in the topic (say
|
|
255
|
+
"ratify deferral of <paths>?"); a server-authored enumeration message will list the exact set,
|
|
256
|
+
and only the verified operator's reply binds it. The operator can also ratify from the dashboard
|
|
257
|
+
(PIN-gated).
|
|
258
|
+
|
|
210
259
|
### The Defer-to-Future-Self Trap
|
|
211
260
|
|
|
212
261
|
**This is the #1 failure mode.** It looks like:
|
|
@@ -44,6 +44,13 @@ for _arg in "$@"; do [[ "$_arg" == "--codex" ]] && IS_CODEX=1; done
|
|
|
44
44
|
# verification_command on a met:true verdict and gates the exit on it; fail/timeout/breaker-open →
|
|
45
45
|
# keep working, the safe direction). This sentinel is the PostUpdateMigrator marker that re-deploys
|
|
46
46
|
# this hook to existing agents that carry COMPLETION_DISCIPLINE but not REALCHECK_VERIFY.
|
|
47
|
+
# hook-capability: SCOPE_ACCRETION — scope-accretion completion discipline (spec:
|
|
48
|
+
# autonomous-scope-accretion-completion.md). Layer B evasion-vocabulary scan (advisory
|
|
49
|
+
# scopeAccretionSuspected signal, fenced/quoted-region excluded), topicId/runId/sessionId echoed
|
|
50
|
+
# to the evaluate-completion chokepoint (the server computes the LOAD-BEARING git-truth sweep +
|
|
51
|
+
# ratification state there), and run_end_call fired on EVERY terminal exit surface (R40/R44) so
|
|
52
|
+
# no exit is silent. This sentinel is the PostUpdateMigrator marker that re-deploys this hook to
|
|
53
|
+
# existing agents that carry REALCHECK_VERIFY but not SCOPE_ACCRETION.
|
|
47
54
|
# emit — human-facing approve/status text. In codex mode the Stop hook's STDOUT must be
|
|
48
55
|
# ONLY valid decision-JSON (the `{"decision":"block",...}` case far below) or empty:
|
|
49
56
|
# codex rejects ANY other stdout as "invalid stop hook JSON output" and reports the stop
|
|
@@ -239,6 +246,11 @@ WORK_DIR=$(fm_get_raw work_dir)
|
|
|
239
246
|
# marker (mirrors the completion_promise exact-match guard). Absent on older
|
|
240
247
|
# state files → the marker branch self-disables for that run (no false exit).
|
|
241
248
|
HARD_BLOCKER_NONCE=$(fm_get hard_blocker_nonce)
|
|
249
|
+
# SCOPE_ACCRETION — the server-minted run id written by setup at registration
|
|
250
|
+
# (POST /autonomous/register). Echoed on evaluate-completion + run-end calls so
|
|
251
|
+
# the server can verify the (topicId, runId) pair against its OWN registration
|
|
252
|
+
# record. Absent on older/unregistered runs → the server degrades honestly.
|
|
253
|
+
RUN_ID=$(fm_get run_id)
|
|
242
254
|
|
|
243
255
|
# ── COMPLETION_DISCIPLINE — off-switch + judge curl budget (read at the chokepoint) ──
|
|
244
256
|
# Autonomous Completion Discipline (spec: AUTONOMOUS-COMPLETION-DISCIPLINE.md).
|
|
@@ -281,7 +293,13 @@ try:
|
|
|
281
293
|
rcbt = rc.get('failBreakerThreshold', 3)
|
|
282
294
|
rcbw = rc.get('failWindowMs', 600000)
|
|
283
295
|
rcbc = rc.get('failCooldownMs', 600000)
|
|
284
|
-
|
|
296
|
+
# SCOPE_ACCRETION — Layer B advisory scan gate. NOTE: the LOAD-BEARING gate
|
|
297
|
+
# is snapshotted SERVER-SIDE at registration (this local read only gates the
|
|
298
|
+
# hook's ADVISORY scopeAccretionSuspected field, keeping the judge prompt
|
|
299
|
+
# byte-identical when the feature is off).
|
|
300
|
+
sa = a.get('scopeAccretion') or {}
|
|
301
|
+
sae = sa.get('enabled', True)
|
|
302
|
+
print('%s %d %d %d %d %d %d %s %d %d %d %d %d %d %s' % (
|
|
285
303
|
'1' if en else '0', jt,
|
|
286
304
|
int(bt) if str(bt).isdigit() else 3,
|
|
287
305
|
int(bw) if str(bw).isdigit() else 600000,
|
|
@@ -294,12 +312,14 @@ try:
|
|
|
294
312
|
int(rcbt) if str(rcbt).isdigit() else 3,
|
|
295
313
|
int(rcbw) if str(rcbw).isdigit() else 600000,
|
|
296
314
|
int(rcbc) if str(rcbc).isdigit() else 600000,
|
|
315
|
+
'1' if sae else '0',
|
|
297
316
|
))
|
|
298
317
|
except Exception:
|
|
299
|
-
print('1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000')
|
|
300
|
-
" 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000")
|
|
301
|
-
read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES RC_ENABLED RC_TIMEOUT_MS RC_MAX_CHARS RC_CAPTURE_BYTES RC_BREAKER_THRESHOLD RC_BREAKER_WINDOW_MS RC_BREAKER_COOLDOWN_MS <<< "$CD_CFG"
|
|
318
|
+
print('1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000 1')
|
|
319
|
+
" 2>/dev/null || echo "1 35000 3 600000 600000 500 1048576 1 120000 2000 65536 3 600000 600000 1")
|
|
320
|
+
read -r CD_ENABLED JUDGE_TIMEOUT_MS CD_BREAKER_THRESHOLD CD_BREAKER_WINDOW_MS CD_BREAKER_COOLDOWN_MS CD_MARKER_MAX_CHARS CD_LOG_ROTATE_BYTES RC_ENABLED RC_TIMEOUT_MS RC_MAX_CHARS RC_CAPTURE_BYTES RC_BREAKER_THRESHOLD RC_BREAKER_WINDOW_MS RC_BREAKER_COOLDOWN_MS SA_ENABLED <<< "$CD_CFG"
|
|
302
321
|
[[ "$CD_ENABLED" =~ ^[01]$ ]] || CD_ENABLED=1
|
|
322
|
+
[[ "${SA_ENABLED:-}" =~ ^[01]$ ]] || SA_ENABLED=1
|
|
303
323
|
[[ "$JUDGE_TIMEOUT_MS" =~ ^[0-9]+$ ]] || JUDGE_TIMEOUT_MS=35000
|
|
304
324
|
# curl -m takes SECONDS; convert ms→s (ceil), floor 5s.
|
|
305
325
|
JUDGE_TIMEOUT_S=$(( (JUDGE_TIMEOUT_MS + 999) / 1000 ))
|
|
@@ -343,6 +363,29 @@ notify_terminal_stop() {
|
|
|
343
363
|
printf '%s\n' "$msg" | "$script" "$REPORT_TOPIC" >/dev/null 2>&1 || true
|
|
344
364
|
}
|
|
345
365
|
|
|
366
|
+
# SCOPE_ACCRETION — run-end reporting (R44): EVERY terminal exit surface calls
|
|
367
|
+
# POST /autonomous/:topic/run-end so the server runs the non-blocking advisory
|
|
368
|
+
# sweep and, when unbuilt accreted work remains, enumerates it LOUDLY (R40 —
|
|
369
|
+
# the silent clock-out is structurally closed; the emergency-stop lever buys a
|
|
370
|
+
# faster exit, never a quieter one). Best-effort + `-m`-bounded: a delivery
|
|
371
|
+
# failure NEVER blocks or delays the exit (the R28b daily sweep is the backstop).
|
|
372
|
+
run_end_call() {
|
|
373
|
+
local reason="$1"
|
|
374
|
+
[[ -z "$REPORT_TOPIC" ]] && return 0
|
|
375
|
+
# Test seam: record the would-be call instead of hitting the server.
|
|
376
|
+
if [[ -n "${INSTAR_HOOK_RUNEND_RECORD:-}" ]]; then
|
|
377
|
+
printf '{"topic":"%s","reason":"%s","runId":"%s"}\n' "$REPORT_TOPIC" "$reason" "${RUN_ID:-}" \
|
|
378
|
+
>> "$INSTAR_HOOK_RUNEND_RECORD" 2>/dev/null || true
|
|
379
|
+
return 0
|
|
380
|
+
fi
|
|
381
|
+
local re_port re_auth
|
|
382
|
+
re_port=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))" 2>/dev/null || echo 4040)
|
|
383
|
+
re_auth=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
|
|
384
|
+
jq -nc --arg r "$reason" --arg id "${RUN_ID:-}" '{reason:$r} + (if $id != "" then {runId:$id} else {} end)' 2>/dev/null \
|
|
385
|
+
| curl -s -m 8 -H "Authorization: Bearer $re_auth" -H 'Content-Type: application/json' \
|
|
386
|
+
--data-binary @- "http://localhost:${re_port}/autonomous/${REPORT_TOPIC}/run-end" >/dev/null 2>&1 || true
|
|
387
|
+
}
|
|
388
|
+
|
|
346
389
|
# Validate recorded session_id is a real UUID. Claude sometimes writes a custom
|
|
347
390
|
# string instead of $CLAUDE_CODE_SESSION_ID; non-UUID values are treated as
|
|
348
391
|
# empty so the session-match backstop self-bootstraps from the real UUID.
|
|
@@ -441,6 +484,7 @@ if [[ "$GOAL_MODE" == "native" ]]; then
|
|
|
441
484
|
}
|
|
442
485
|
if [[ -f ".instar/autonomous-emergency-stop" ]]; then
|
|
443
486
|
notify_terminal_stop "🛑 My autonomous run on \"$(goal_snippet)\" was stopped (emergency stop)."
|
|
487
|
+
run_end_call "emergency-stop (native-goal)"
|
|
444
488
|
native_goal_clear; rm -f "$STATE_FILE"
|
|
445
489
|
echo "[autonomous] emergency stop — native /goal cleared" >&2; exit 0
|
|
446
490
|
fi
|
|
@@ -448,6 +492,7 @@ if [[ "$GOAL_MODE" == "native" ]]; then
|
|
|
448
492
|
NG_START=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$STARTED_AT" +%s 2>/dev/null || date -d "$STARTED_AT" +%s 2>/dev/null || echo 0)
|
|
449
493
|
if [[ "$NG_START" =~ ^[0-9]+$ ]] && [[ $NG_START -gt 0 ]] && [[ $(( $(date +%s) - NG_START )) -ge $DURATION_SECONDS ]]; then
|
|
450
494
|
notify_terminal_stop "⏰ My autonomous run on \"$(goal_snippet)\" hit its time limit and stopped. Ask me and I'll pick up where I left off."
|
|
495
|
+
run_end_call "duration-expiry (native-goal)"
|
|
451
496
|
native_goal_clear; rm -f "$STATE_FILE"
|
|
452
497
|
echo "[autonomous] duration expired — native /goal cleared" >&2; exit 0
|
|
453
498
|
fi
|
|
@@ -461,6 +506,7 @@ fi
|
|
|
461
506
|
# Validate iteration
|
|
462
507
|
if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then
|
|
463
508
|
echo "⚠️ Autonomous mode: State file corrupted (bad iteration)" >&2
|
|
509
|
+
run_end_call "state-corrupt (bad iteration)"
|
|
464
510
|
rm -f "$STATE_FILE"
|
|
465
511
|
exit 0
|
|
466
512
|
fi
|
|
@@ -478,6 +524,7 @@ if [[ "$DURATION_SECONDS" =~ ^[0-9]+$ ]] && [[ $DURATION_SECONDS -gt 0 ]]; then
|
|
|
478
524
|
emit "⏰ Autonomous mode: Duration expired ($ELAPSED seconds elapsed)."
|
|
479
525
|
emit " Session is free to exit."
|
|
480
526
|
notify_terminal_stop "⏰ My autonomous run on \"$(goal_snippet)\" just hit its time limit and stopped. Ask me and I'll pick up where I left off."
|
|
527
|
+
run_end_call "duration-expiry"
|
|
481
528
|
rm -f "$STATE_FILE"
|
|
482
529
|
exit 0
|
|
483
530
|
fi
|
|
@@ -492,6 +539,10 @@ fi
|
|
|
492
539
|
if [[ -f ".instar/autonomous-emergency-stop" ]]; then
|
|
493
540
|
emit "🛑 Autonomous mode: Emergency stop detected."
|
|
494
541
|
notify_terminal_stop "🛑 My autonomous run on \"$(goal_snippet)\" was stopped (emergency stop)."
|
|
542
|
+
# SCOPE_ACCRETION: the emergency-stop file is a run-end surface a session could
|
|
543
|
+
# `touch` itself — so it fires run-end IDENTICALLY (R44): the one-step bypass
|
|
544
|
+
# is thereby exactly as LOUD as the two-step (spec §6).
|
|
545
|
+
run_end_call "emergency-stop"
|
|
495
546
|
rm -f "$STATE_FILE"
|
|
496
547
|
# NOTE: the emergency flag is left in place so OTHER topics' hooks also see it
|
|
497
548
|
# and clear their own per-topic files; stop-all clears the flag when complete.
|
|
@@ -576,15 +627,38 @@ if [[ "$CD_ENABLED" == "1" ]] && [[ -n "$CD_JUDGE_TAIL" ]]; then
|
|
|
576
627
|
done
|
|
577
628
|
fi
|
|
578
629
|
|
|
630
|
+
# SCOPE_ACCRETION — Layer B evasion-vocabulary scan (advisory, spec §2.7).
|
|
631
|
+
# Scans the SAME already-extracted CD_JUDGE_TAIL window (no second transcript
|
|
632
|
+
# read) for accretion-evasion vocabulary, with a fenced/quoted-region exclusion
|
|
633
|
+
# that is NEW bash logic (the milestone/injection scans above are plain
|
|
634
|
+
# substring matches): text inside ``` fences and `>`-quoted lines is stripped
|
|
635
|
+
# BEFORE matching, so quoting this rule or a doc that names the anti-pattern
|
|
636
|
+
# can never trip the signal. Advisory by construction — false negatives are
|
|
637
|
+
# harmless (the server-side git-truth sweep is the defense, Layer 0).
|
|
638
|
+
CD_SCOPE_SUSPECTED="false"
|
|
639
|
+
if [[ "$CD_ENABLED" == "1" ]] && [[ "$SA_ENABLED" == "1" ]] && [[ -n "$CD_JUDGE_TAIL" ]]; then
|
|
640
|
+
CD_TAIL_SCOPE=$(printf '%s\n' "$CD_JUDGE_TAIL" \
|
|
641
|
+
| awk 'BEGIN{f=0} /^[[:space:]]*```/{f=1-f; next} f==0 && $0 !~ /^[[:space:]]*>/{print}' 2>/dev/null \
|
|
642
|
+
| tr '[:upper:]' '[:lower:]' || echo "")
|
|
643
|
+
for _sa in \
|
|
644
|
+
"documented stretch" "filed for a future session" "out of completion condition" \
|
|
645
|
+
"out of the completion condition" "drafts for later" "deferred as a follow-up spec"; do
|
|
646
|
+
if [[ "$CD_TAIL_SCOPE" == *"$_sa"* ]]; then CD_SCOPE_SUSPECTED="true"; break; fi
|
|
647
|
+
done
|
|
648
|
+
fi
|
|
649
|
+
|
|
579
650
|
# Assemble the signals JSON the judge payload carries (spec §2b.4 surface 1).
|
|
580
651
|
# completionConditionMet is set false here and overwritten true only inside the
|
|
581
652
|
# met-condition branch (the judge there is the completion judge, not this).
|
|
582
653
|
build_signals_json() {
|
|
583
654
|
local stop_kind="${1:-}" cond_met="${2:-false}"
|
|
584
|
-
local sk=""
|
|
655
|
+
local sk="" sa=""
|
|
585
656
|
[[ "$stop_kind" == "hard-blocker" ]] && sk=',"stopKind":"hard-blocker"'
|
|
586
|
-
|
|
587
|
-
|
|
657
|
+
# SCOPE_ACCRETION — the advisory Layer-B boolean is included ONLY when the
|
|
658
|
+
# feature is on, so a disabled agent's judge prompt stays byte-identical.
|
|
659
|
+
[[ "$SA_ENABLED" == "1" ]] && sa=",\"scopeAccretionSuspected\":${CD_SCOPE_SUSPECTED:-false}"
|
|
660
|
+
printf '{"completionConditionMet":%s,"uncheckedTaskCount":%s,"taskStructure":"%s","milestoneRationalizationDetected":%s,"injectionSuspected":%s%s%s}' \
|
|
661
|
+
"$cond_met" "$CD_UNCHECKED_COUNT" "$CD_TASK_STRUCTURE" "$CD_MILESTONE_DETECTED" "$CD_INJECTION_SUSPECTED" "$sa" "$sk"
|
|
588
662
|
}
|
|
589
663
|
|
|
590
664
|
# ── COMPLETION_DISCIPLINE — circuit-breaker + verdict cache (in the backoff sidecar) ──
|
|
@@ -1203,6 +1277,8 @@ if [[ "$CD_ENABLED" == "1" ]] && [[ -n "$HARD_BLOCKER_NONCE" ]] && [[ "$HARD_BLO
|
|
|
1203
1277
|
# (c) ONE plain-English notify-on-stop.
|
|
1204
1278
|
notify_terminal_stop "🚧 My autonomous run on \"$(goal_snippet)\" stopped on a hard blocker I can't resolve myself. What I'd need: ${HB_NEEDED}. I've queued it for your attention."
|
|
1205
1279
|
emit "🚧 Autonomous mode: hard blocker (external) — exit allowed. needed: ${HB_NEEDED}"
|
|
1280
|
+
# SCOPE_ACCRETION (R40/R44): the hard-blocker exit is a run-end surface.
|
|
1281
|
+
run_end_call "hard-blocker"
|
|
1206
1282
|
# (d) Remove state; allow exit (write once — state removal prevents re-append).
|
|
1207
1283
|
rm -f "$STATE_FILE" "$CD_BACKOFF_STATE" 2>/dev/null || true
|
|
1208
1284
|
exit 0
|
|
@@ -1236,6 +1312,13 @@ fi
|
|
|
1236
1312
|
EVAL_REASON=""
|
|
1237
1313
|
if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]] && [[ -n "$TRANSCRIPT_PATH" ]] && [[ -f "$TRANSCRIPT_PATH" ]]; then
|
|
1238
1314
|
EVAL_MET=""
|
|
1315
|
+
# Test seam (SCOPE_ACCRETION): record the signals + identity fields the judge
|
|
1316
|
+
# payload would carry, WITHOUT a network call (mirrors INSTAR_HOOK_ATTENTION_RECORD).
|
|
1317
|
+
if [[ -n "${INSTAR_HOOK_SIGNALS_RECORD:-}" ]] && [[ "$CD_ENABLED" == "1" ]]; then
|
|
1318
|
+
printf '{"signals":%s,"topicId":"%s","runId":"%s"}\n' \
|
|
1319
|
+
"$(build_signals_json "" "false")" "${REPORT_TOPIC:-}" "${RUN_ID:-}" \
|
|
1320
|
+
>> "$INSTAR_HOOK_SIGNALS_RECORD" 2>/dev/null || true
|
|
1321
|
+
fi
|
|
1239
1322
|
# Decide whether to fire the judge at all (CD cost-discipline gate).
|
|
1240
1323
|
CD_MIGHT_BE_DONE="true"
|
|
1241
1324
|
if [[ "$CD_ENABLED" == "1" ]] && [[ -z "${INSTAR_HOOK_EVAL_OVERRIDE:-}" ]]; then
|
|
@@ -1267,12 +1350,17 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]]
|
|
|
1267
1350
|
| tail -c 8000 || echo "")
|
|
1268
1351
|
EVAL_PORT=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))" 2>/dev/null || echo 4040)
|
|
1269
1352
|
EVAL_AUTH=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
|
|
1353
|
+
# SCOPE_ACCRETION — echo topicId/runId/sessionId so the server resolves the
|
|
1354
|
+
# run against its OWN registration record (R35 arming; R36 registered-
|
|
1355
|
+
# condition authority; §6 runId pair check). Empty fields are omitted.
|
|
1356
|
+
CD_IDS=$(jq -nc --arg topic "${REPORT_TOPIC:-}" --arg rid "${RUN_ID:-}" --arg sid "${HOOK_SESSION:-}" \
|
|
1357
|
+
'(if $topic != "" then {topicId:$topic} else {} end) + (if $rid != "" then {runId:$rid} else {} end) + (if $sid != "" then {sessionId:$sid} else {} end)' 2>/dev/null || echo '{}')
|
|
1270
1358
|
if [[ "$CD_ENABLED" == "1" ]]; then
|
|
1271
1359
|
# Fold the milestone/buildable-work scrutiny into the completion judge (single call).
|
|
1272
1360
|
CD_SIG=$(build_signals_json "" "false")
|
|
1273
|
-
EVAL_BODY=$(jq -nc --arg c "$COMPLETION_CONDITION" --arg t "$EVAL_TAIL" --argjson sig "$CD_SIG" '{condition:$c,transcriptTail:$t,signals:$sig}')
|
|
1361
|
+
EVAL_BODY=$(jq -nc --arg c "$COMPLETION_CONDITION" --arg t "$EVAL_TAIL" --argjson sig "$CD_SIG" --argjson ids "$CD_IDS" '{condition:$c,transcriptTail:$t,signals:$sig} + $ids')
|
|
1274
1362
|
else
|
|
1275
|
-
EVAL_BODY=$(jq -nc --arg c "$COMPLETION_CONDITION" --arg t "$EVAL_TAIL" '{condition:$c,transcriptTail:$t}')
|
|
1363
|
+
EVAL_BODY=$(jq -nc --arg c "$COMPLETION_CONDITION" --arg t "$EVAL_TAIL" --argjson ids "$CD_IDS" '{condition:$c,transcriptTail:$t} + $ids')
|
|
1276
1364
|
fi
|
|
1277
1365
|
EVAL_RESP=$(printf '%s' "$EVAL_BODY" \
|
|
1278
1366
|
| curl -s -m "$JUDGE_TIMEOUT_S" -H "Authorization: Bearer $EVAL_AUTH" -H 'Content-Type: application/json' \
|
|
@@ -1302,6 +1390,7 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]]
|
|
|
1302
1390
|
if realcheck_gate; then
|
|
1303
1391
|
emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
|
|
1304
1392
|
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
|
|
1393
|
+
run_end_call "met"
|
|
1305
1394
|
rm -f "$STATE_FILE" "$CD_BACKOFF_STATE" 2>/dev/null || true
|
|
1306
1395
|
exit 0
|
|
1307
1396
|
fi
|
|
@@ -1311,6 +1400,7 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$COMPLETION_CONDITION" ]]
|
|
|
1311
1400
|
if realcheck_gate; then
|
|
1312
1401
|
emit "✅ Autonomous mode: completion condition met (independent evaluator): ${EVAL_REASON}"
|
|
1313
1402
|
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — the goal was met."
|
|
1403
|
+
run_end_call "met"
|
|
1314
1404
|
rm -f "$STATE_FILE"
|
|
1315
1405
|
exit 0
|
|
1316
1406
|
fi
|
|
@@ -1343,6 +1433,7 @@ if [[ "${CD_BLOCK_TERMINAL:-}" != "true" ]] && [[ -n "$TRANSCRIPT_PATH" ]] && [[
|
|
|
1343
1433
|
emit "✅ Autonomous mode: Completion promise detected — <promise>$COMPLETION_PROMISE</promise>"
|
|
1344
1434
|
emit " Session is free to exit. Good work!"
|
|
1345
1435
|
notify_terminal_stop "✅ My autonomous run on \"$(goal_snippet)\" finished — all the work is done."
|
|
1436
|
+
run_end_call "met (promise)"
|
|
1346
1437
|
rm -f "$STATE_FILE"
|
|
1347
1438
|
exit 0
|
|
1348
1439
|
fi
|
|
@@ -1507,11 +1598,13 @@ except Exception:
|
|
|
1507
1598
|
if [[ ! -f "$STATE_FILE" ]]; then
|
|
1508
1599
|
rm -f "$BACKOFF_STATE" 2>/dev/null || true
|
|
1509
1600
|
echo "[autonomous] state file removed during idle backoff — allowing exit" >&2
|
|
1601
|
+
run_end_call "stopped (state file removed during backoff)"
|
|
1510
1602
|
exit 0
|
|
1511
1603
|
fi
|
|
1512
1604
|
if [[ -f ".instar/autonomous-emergency-stop" ]]; then
|
|
1513
1605
|
emit "🛑 Autonomous mode: Emergency stop detected (during idle backoff)."
|
|
1514
1606
|
notify_terminal_stop "🛑 My autonomous run on \"$(goal_snippet)\" was stopped (emergency stop)."
|
|
1607
|
+
run_end_call "emergency-stop (during backoff)"
|
|
1515
1608
|
rm -f "$STATE_FILE" "$BACKOFF_STATE" 2>/dev/null || true
|
|
1516
1609
|
exit 0
|
|
1517
1610
|
fi
|
|
@@ -1528,6 +1621,7 @@ NEXT_ITERATION=$((ITERATION + 1))
|
|
|
1528
1621
|
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE")
|
|
1529
1622
|
if [[ -z "$PROMPT_TEXT" ]]; then
|
|
1530
1623
|
echo "⚠️ Autonomous mode: State file has no task content" >&2
|
|
1624
|
+
run_end_call "state-corrupt (no task content)"
|
|
1531
1625
|
rm -f "$STATE_FILE"
|
|
1532
1626
|
exit 0
|
|
1533
1627
|
fi
|
|
@@ -18,6 +18,9 @@ COMPLETION_CONDITION="" # verifiable end-state; an independent judge decides "
|
|
|
18
18
|
REPORT_INTERVAL="30m"
|
|
19
19
|
VERIFICATION_COMMAND="" # opt-in real check (ACT-152): the stop-hook RUNS this on a met:true verdict and gates the exit on exit-0.
|
|
20
20
|
VERIFICATION_CWD="" # directory the verification command runs in (resolves verification_cwd → work_dir → agent home).
|
|
21
|
+
DECLARED_DELIVERABLES="" # SCOPE_ACCRETION: comma-separated repo-relative paths the run is EXPECTED to
|
|
22
|
+
# produce as drafts (the escape for genuinely draft-only missions — operator-
|
|
23
|
+
# confirmed at setup). Recorded server-side at registration; immutable mid-run.
|
|
21
24
|
|
|
22
25
|
while [[ $# -gt 0 ]]; do
|
|
23
26
|
case $1 in
|
|
@@ -61,6 +64,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
61
64
|
VERIFICATION_CWD="$2"
|
|
62
65
|
shift 2
|
|
63
66
|
;;
|
|
67
|
+
--declared-deliverables)
|
|
68
|
+
DECLARED_DELIVERABLES="$2"
|
|
69
|
+
shift 2
|
|
70
|
+
;;
|
|
64
71
|
--report-interval)
|
|
65
72
|
REPORT_INTERVAL="$2"
|
|
66
73
|
shift 2
|
|
@@ -186,6 +193,46 @@ if [[ -n "$VERIFICATION_CWD" ]]; then
|
|
|
186
193
|
$VERIFICATION_FIELDS"
|
|
187
194
|
fi
|
|
188
195
|
|
|
196
|
+
# ── SCOPE_ACCRETION — server-side run registration (spec: autonomous-scope-
|
|
197
|
+
# accretion-completion.md R30). The SERVER mints the runId, snapshots the
|
|
198
|
+
# scopeAccretion config + the sweep base roots with their start-SHAs, and clamps
|
|
199
|
+
# endAt — so mid-run edits to config/state-file change nothing the completion
|
|
200
|
+
# chokepoint reads. The runId is written into the frontmatter so the stop hook
|
|
201
|
+
# can echo it on every evaluate-completion / run-end call. Best-effort: an
|
|
202
|
+
# unreachable server leaves run_id empty (the gate degrades honestly server-side;
|
|
203
|
+
# the run still starts — registration is a discipline layer, not a start gate).
|
|
204
|
+
RUN_ID=""
|
|
205
|
+
if [[ -n "$REPORT_TOPIC" ]]; then
|
|
206
|
+
REG_PORT=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('port',4040))" 2>/dev/null || echo 4040)
|
|
207
|
+
REG_AUTH=$(python3 -c "import json;print(json.load(open('.instar/config.json')).get('authToken',''))" 2>/dev/null || echo "")
|
|
208
|
+
# Comma-separated → JSON array (empty string → []).
|
|
209
|
+
REG_DECLARED=$(printf '%s' "$DECLARED_DELIVERABLES" | python3 -c "import sys,json
|
|
210
|
+
print(json.dumps([p.strip() for p in sys.stdin.read().split(',') if p.strip()]))" 2>/dev/null || echo '[]')
|
|
211
|
+
REG_END_AT="$END_AT"
|
|
212
|
+
[[ "$REG_END_AT" == "unknown" || "$REG_END_AT" == "unlimited" ]] && REG_END_AT=""
|
|
213
|
+
REG_RESP=$(jq -nc \
|
|
214
|
+
--arg t "$REPORT_TOPIC" --arg c "$COMPLETION_CONDITION" --arg w "$WORK_DIR" \
|
|
215
|
+
--arg s "$STARTED_AT" --arg e "$REG_END_AT" --arg sid "${CLAUDE_CODE_SESSION_ID:-}" \
|
|
216
|
+
--argjson d "$REG_DECLARED" \
|
|
217
|
+
'{topicId:$t,condition:$c,workDir:$w,declaredDeliverables:$d,startedAt:$s}
|
|
218
|
+
+ (if $e != "" then {endAt:$e} else {} end)
|
|
219
|
+
+ (if $sid != "" then {sessionId:$sid} else {} end)' 2>/dev/null \
|
|
220
|
+
| curl -s -m 8 -H "Authorization: Bearer $REG_AUTH" -H 'Content-Type: application/json' \
|
|
221
|
+
--data-binary @- "http://localhost:${REG_PORT}/autonomous/register" 2>/dev/null || echo "")
|
|
222
|
+
RUN_ID=$(printf '%s' "$REG_RESP" | python3 -c "import sys,json
|
|
223
|
+
try: print(json.load(sys.stdin).get('runId',''))
|
|
224
|
+
except Exception: print('')" 2>/dev/null || echo "")
|
|
225
|
+
if [[ -n "$RUN_ID" ]]; then
|
|
226
|
+
echo " Scope-accretion: run registered server-side (runId $RUN_ID)"
|
|
227
|
+
else
|
|
228
|
+
echo " Scope-accretion: server registration unavailable — accretion gate degrades honestly (run still bounded by duration)"
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
if [[ -n "$RUN_ID" ]]; then
|
|
232
|
+
VERIFICATION_FIELDS="run_id: \"$RUN_ID\"
|
|
233
|
+
$VERIFICATION_FIELDS"
|
|
234
|
+
fi
|
|
235
|
+
|
|
189
236
|
cat > "$STATE_PATH" <<EOF
|
|
190
237
|
---
|
|
191
238
|
active: true
|
package/dist/commands/init.js
CHANGED
|
@@ -4170,6 +4170,15 @@ process.stdin.on('end', async () => {
|
|
|
4170
4170
|
event: input.hook_event || (input.tool_name ? 'PostToolUse' : 'Unknown'),
|
|
4171
4171
|
session_id: input.session_id || '',
|
|
4172
4172
|
tool_name: input.tool_name || '',
|
|
4173
|
+
// green-pr-automerge Layer 2: forward the session cwd so the server can
|
|
4174
|
+
// resolve the ending session's branch (without it, Layer 2 ships inert).
|
|
4175
|
+
// (Reconciled with the PostUpdateMigrator copy — keep BOTH in sync.)
|
|
4176
|
+
cwd: input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd() || '',
|
|
4177
|
+
// scope-accretion ADVISORY ledger (spec autonomous-scope-accretion-
|
|
4178
|
+
// completion.md R18): forward the Write/Edit file path for attribution
|
|
4179
|
+
// detail. Optional + designed-benign: the receiver stores extra fields
|
|
4180
|
+
// as-is and a payload without it remains valid.
|
|
4181
|
+
file_path: (input.tool_input && (input.tool_input.file_path || input.tool_input.path)) || '',
|
|
4173
4182
|
});
|
|
4174
4183
|
|
|
4175
4184
|
const url = new URL(serverUrl + '/hooks/events?instar_sid=' + instarSid);
|