Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions Li+update.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,28 @@ clone mode:
`git -C {workspace_root}/{repo_dir} checkout {target_tag}`
Both are the literal to execute; add no flags to either. Proceed to step 3.
- exists -> fetch --tags, then:
a. Resolve and report both values: current checked-out tag and target tag from LI_PLUS_CHANNEL.
a. Check that the clone can fetch branches: `git -C {workspace_root}/{repo_dir} config --get-all
remote.origin.fetch` must carry at least one refspec whose source side is under `refs/heads/`.
A clone carrying none of those still resolves tags, so the `fetch --tags` just run advances tags
and leaves every branch where it was, and a later bare `git fetch origin` succeeds as a no-op.
If none is present, name the finding to the user, and name what it costs: local branches never
advance, so a worktree or a build taken from a local branch is taken from a stale tree.
Detection only. Do not add the refspec, do not re-clone, do not abort — continue to step b either
way. The repair is the user's: it writes shared local git state, which no agent takes on its own.
The same condition is surfaced every session by the on-session-start hooks, which is where a clone
that stays in this state keeps being reported; the destination, and why it is not
`LI_PLUS_UPDATE_STATUS`, are `rules/evolution/cold-start-synthesis.md` Clone Branch Fetch Surface.
b. Resolve and report both values: current checked-out tag and target tag from LI_PLUS_CHANNEL.
Name which of the two is newer: the target is not necessarily the newer one, since a channel
can resolve to a tag behind the current one.
b. If same -> continue.
c. If different -> ask the user how to proceed before continuing to Phase 4.
c. If same -> continue.
d. If different -> ask the user how to proceed before continuing to Phase 4.
Do not report bootstrap completion before this choice is resolved.
Minimum choices:
- update now to the target tag
- stay on the current tag for this session
d. Checkout the target tag only if the user agrees.
e. If the user chooses to stay, continue on the current tag only after explicitly naming both tags.
e. Checkout the target tag only if the user agrees.
f. If the user chooses to stay, continue on the current tag only after explicitly naming both tags.
3. Source files are now available at the resolved tag. Phase 4 handles reading.

## Phase 4: Host Integration
Expand Down
72 changes: 68 additions & 4 deletions adapter/claude/hooks/on-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,69 @@ if [ -n "$TALLY_BODY" ]; then
TALLY_EMITTED=1
fi

# --- clone branch fetch surface (outside the diff-only set) ---
# Implements rules/evolution/cold-start-synthesis.md "Clone Branch Fetch
# Surface". A Li+ clone whose remote.origin.fetch carries no
# refspec sourced under refs/heads/ resolves tags and moves no branch, and
# nothing raises: `fetch --tags` succeeds and a bare `git fetch origin` is a
# silent no-op, so the lag accumulates with no surface at all.
#
# Carries no section key and is not registered for diff comparison, like the two
# surfaces above, and for the same asymmetry: the trigger is not content-driven,
# so a fingerprint would surface the clone once and then suppress it for the
# whole time it stays broken. Unlike those two the trigger is state-driven, not
# date-driven, which is why nothing here reads a date and nothing has to be
# removed: the emission stops when the condition stops holding.
#
# Not placed on LI_PLUS_UPDATE_STATUS: that marker is the trigger condition for
# step 2 of the adapter startup procedure and branches on the status alone, so a
# reason stacked there would let a detection-only check start the update
# walkthrough.
#
# Gathered here rather than in the gather phase because it is not in the diff
# set, so its body is needed only at this point, and this point is past the
# non-startup exit -- the `git` call is not spent on resume / clear / compact /
# fork. Silent skip when the directory is not a clone (api mode) or `git` is
# absent: neither state is evidence about a refspec.
CLONE_REFSPEC_EMITTED=0
if [ -e "$LIPLUS_DIR/.git" ] && command -v git >/dev/null 2>&1; then
CLONE_FETCH_RAW=$(git -C "$LIPLUS_DIR" config --get-all remote.origin.fetch 2>/dev/null)
CLONE_FETCH_REFSPECS=$(printf '%s' "$CLONE_FETCH_RAW" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
# Source side only. A refspec is [+]<src>:<dst>, and the contract's predicate
# is the src half: refs/heads/ appearing as the dst
# (+refs/tags/v1:refs/heads/mirror) maps none of the remote's branches, so a
# substring test over the whole string reads such a clone as healthy.
# ^<pattern> is an exclusion and establishes no mapping either.
CLONE_BRANCH_MAPPED=0
while IFS= read -r CLONE_REFSPEC; do
[ -n "$CLONE_REFSPEC" ] || continue
case "$CLONE_REFSPEC" in
^*) continue ;;
esac
CLONE_REFSPEC_SRC="${CLONE_REFSPEC#+}"
CLONE_REFSPEC_SRC="${CLONE_REFSPEC_SRC%%:*}"
case "$CLONE_REFSPEC_SRC" in
refs/heads/*) CLONE_BRANCH_MAPPED=1 ;;
esac
done <<CLONE_REFSPEC_EOF
$CLONE_FETCH_RAW
CLONE_REFSPEC_EOF
if [ "$CLONE_BRANCH_MAPPED" -eq 0 ]; then
emit_section "Clone cannot fetch branches" "${LIPLUS_DIR} - remote.origin.fetch maps no branch.
configured: ${CLONE_FETCH_REFSPECS:-(none)}
expected: at least one refspec whose source side is under refs/heads/
Tags still resolve, so fetch --tags succeeds and a bare git fetch origin is a
silent no-op; local branches never advance, so a worktree or a build taken from
a local branch is taken from a stale tree.
Surfacing is observation, not auto-action. The repair (one git config --add
remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' line) writes shared
local git state and is taken on a human go-sign; name this to the human and stop
there. Contract = rules/evolution/cold-start-synthesis.md Clone Branch Fetch
Surface."
CLONE_REFSPEC_EMITTED=1
fi
fi

# --- diff-only logic (startup matcher only) ---
#
# Compute current fingerprint per section. Load prior fingerprint set from
Expand Down Expand Up @@ -1279,10 +1342,11 @@ done
# If no section emitted under diff-only mode, emit the no-new-material marker
# so the human can still observe that a session boundary occurred (silent
# skip is intentionally avoided — it would hide the session transition).
# The two date-driven surfaces count as material: pairing a just-emitted overdue
# entry or an expired tally cluster with "No new orientation material" would be
# self-contradictory output.
if [ "$EMITTED_ANY" -eq 0 ] && [ "$OBSERVATION_EMITTED" -eq 0 ] && [ "$TALLY_EMITTED" -eq 0 ] && [ "$FAIL_SAFE_FULL_EMIT" -eq 0 ]; then
# The three surfaces outside the diff-only set count as material: pairing a
# just-emitted overdue entry, an expired tally cluster or a clone that cannot
# fetch branches with "No new orientation material" would be self-contradictory
# output.
if [ "$EMITTED_ANY" -eq 0 ] && [ "$OBSERVATION_EMITTED" -eq 0 ] && [ "$TALLY_EMITTED" -eq 0 ] && [ "$CLONE_REFSPEC_EMITTED" -eq 0 ] && [ "$FAIL_SAFE_FULL_EMIT" -eq 0 ]; then
emit_section "Orientation diff" "No new orientation material since last session. Prior in-context state remains authoritative."
MARKER_EMITTED=1
fi
Expand Down
53 changes: 49 additions & 4 deletions adapter/codex/hooks/on-session-start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,50 @@ if ($tallyBody) {
$tallyEmitted = $true
}

# --- clone branch fetch surface (outside the diff-only set) ---
# Implements rules/evolution/cold-start-synthesis.md "Clone Branch Fetch
# Surface". Port of the same block in
# adapter/claude/hooks/on-session-start.sh; the rationale for the placement, for
# staying out of the diff set, and for staying off LI_PLUS_UPDATE_STATUS is
# there. State-driven trigger, so nothing here reads a date and nothing has to
# be removed: the emission stops when the condition stops holding.
# -cmatch, not -match: git ref names are case-sensitive and the two bash ports
# compare case-sensitively.
$cloneRefspecEmitted = $false
if ((Test-Path -LiteralPath (Join-Path $liplusDir '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) {
$cloneFetchRefspecs = @(git -C $liplusDir config --get-all remote.origin.fetch 2>$null)
# Source side only, as in the two bash ports: the src half of [+]<src>:<dst>,
# skipping ^<pattern> exclusions. Ordinal comparison, not StartsWith's
# culture-sensitive default -- git ref names are case-sensitive.
$cloneBranchMapped = $false
foreach ($cloneRefspec in $cloneFetchRefspecs) {
if (-not $cloneRefspec) { continue }
$spec = $cloneRefspec.Trim()
if (-not $spec -or $spec.StartsWith('^', [System.StringComparison]::Ordinal)) { continue }
$src = $spec.TrimStart('+')
$colon = $src.IndexOf(':')
if ($colon -ge 0) { $src = $src.Substring(0, $colon) }
if ($src.StartsWith('refs/heads/', [System.StringComparison]::Ordinal)) { $cloneBranchMapped = $true }
}
if (-not $cloneBranchMapped) {
$configured = if ($cloneFetchRefspecs -and ($cloneFetchRefspecs -join ' ').Trim()) { ($cloneFetchRefspecs -join ' ').Trim() } else { '(none)' }
Emit-Section 'Clone cannot fetch branches' @"
$liplusDir - remote.origin.fetch maps no branch.
configured: $configured
expected: at least one refspec whose source side is under refs/heads/
Tags still resolve, so fetch --tags succeeds and a bare git fetch origin is a
silent no-op; local branches never advance, so a worktree or a build taken from
a local branch is taken from a stale tree.
Surfacing is observation, not auto-action. The repair (one git config --add
remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' line) writes shared
local git state and is taken on a human go-sign; name this to the human and stop
there. Contract = rules/evolution/cold-start-synthesis.md Clone Branch Fetch
Surface.
"@
$cloneRefspecEmitted = $true
}
}

# ===================================================================
# Diff-only emission (startup matcher)
# ===================================================================
Expand Down Expand Up @@ -1040,11 +1084,12 @@ for ($i = 0; $i -lt $sectionKeys.Count; $i++) {
}
}

# The two date-driven surfaces count as material: pairing a just-emitted overdue
# entry or an expired tally cluster with "No new orientation material" would be
# self-contradictory output.
# The three surfaces outside the diff-only set count as material: pairing a
# just-emitted overdue entry, an expired tally cluster or a clone that cannot
# fetch branches with "No new orientation material" would be self-contradictory
# output.
$markerEmitted = $false
if (-not $emittedAny -and -not $observationEmitted -and -not $tallyEmitted -and -not $failSafeFull) {
if (-not $emittedAny -and -not $observationEmitted -and -not $tallyEmitted -and -not $cloneRefspecEmitted -and -not $failSafeFull) {
Emit-Section 'Orientation diff' 'No new orientation material since last session. Prior in-context state remains authoritative.'
$markerEmitted = $true
}
Expand Down
72 changes: 68 additions & 4 deletions adapter/codex/hooks/on-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,69 @@ if [ -n "$TALLY_BODY" ]; then
TALLY_EMITTED=1
fi

# --- clone branch fetch surface (outside the diff-only set) ---
# Implements rules/evolution/cold-start-synthesis.md "Clone Branch Fetch
# Surface". A Li+ clone whose remote.origin.fetch carries no
# refspec sourced under refs/heads/ resolves tags and moves no branch, and
# nothing raises: `fetch --tags` succeeds and a bare `git fetch origin` is a
# silent no-op, so the lag accumulates with no surface at all.
#
# Carries no section key and is not registered for diff comparison, like the two
# surfaces above, and for the same asymmetry: the trigger is not content-driven,
# so a fingerprint would surface the clone once and then suppress it for the
# whole time it stays broken. Unlike those two the trigger is state-driven, not
# date-driven, which is why nothing here reads a date and nothing has to be
# removed: the emission stops when the condition stops holding.
#
# Not placed on LI_PLUS_UPDATE_STATUS: that marker is the trigger condition for
# step 2 of the adapter startup procedure and branches on the status alone, so a
# reason stacked there would let a detection-only check start the update
# walkthrough.
#
# Gathered here rather than in the gather phase because it is not in the diff
# set, so its body is needed only at this point, and this point is past the
# non-startup exit -- the `git` call is not spent on resume / clear / compact /
# fork. Silent skip when the directory is not a clone (api mode) or `git` is
# absent: neither state is evidence about a refspec.
CLONE_REFSPEC_EMITTED=0
if [ -e "$LIPLUS_DIR/.git" ] && command -v git >/dev/null 2>&1; then
CLONE_FETCH_RAW=$(git -C "$LIPLUS_DIR" config --get-all remote.origin.fetch 2>/dev/null)
CLONE_FETCH_REFSPECS=$(printf '%s' "$CLONE_FETCH_RAW" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
# Source side only. A refspec is [+]<src>:<dst>, and the contract's predicate
# is the src half: refs/heads/ appearing as the dst
# (+refs/tags/v1:refs/heads/mirror) maps none of the remote's branches, so a
# substring test over the whole string reads such a clone as healthy.
# ^<pattern> is an exclusion and establishes no mapping either.
CLONE_BRANCH_MAPPED=0
while IFS= read -r CLONE_REFSPEC; do
[ -n "$CLONE_REFSPEC" ] || continue
case "$CLONE_REFSPEC" in
^*) continue ;;
esac
CLONE_REFSPEC_SRC="${CLONE_REFSPEC#+}"
CLONE_REFSPEC_SRC="${CLONE_REFSPEC_SRC%%:*}"
case "$CLONE_REFSPEC_SRC" in
refs/heads/*) CLONE_BRANCH_MAPPED=1 ;;
esac
done <<CLONE_REFSPEC_EOF
$CLONE_FETCH_RAW
CLONE_REFSPEC_EOF
if [ "$CLONE_BRANCH_MAPPED" -eq 0 ]; then
emit_section "Clone cannot fetch branches" "${LIPLUS_DIR} - remote.origin.fetch maps no branch.
configured: ${CLONE_FETCH_REFSPECS:-(none)}
expected: at least one refspec whose source side is under refs/heads/
Tags still resolve, so fetch --tags succeeds and a bare git fetch origin is a
silent no-op; local branches never advance, so a worktree or a build taken from
a local branch is taken from a stale tree.
Surfacing is observation, not auto-action. The repair (one git config --add
remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' line) writes shared
local git state and is taken on a human go-sign; name this to the human and stop
there. Contract = rules/evolution/cold-start-synthesis.md Clone Branch Fetch
Surface."
CLONE_REFSPEC_EMITTED=1
fi
fi

# ===================================================================
# Diff-only emission (startup)
# ===================================================================
Expand Down Expand Up @@ -989,10 +1052,11 @@ while [ "$i" -lt "${#SECTION_KEYS[@]}" ]; do
fi
done

# The two date-driven surfaces count as material: pairing a just-emitted overdue
# entry or an expired tally cluster with "No new orientation material" would be
# self-contradictory output.
if [ "$EMITTED_ANY" -eq 0 ] && [ "$OBSERVATION_EMITTED" -eq 0 ] && [ "$TALLY_EMITTED" -eq 0 ] && [ "$FAIL_SAFE_FULL_EMIT" -eq 0 ]; then
# The three surfaces outside the diff-only set count as material: pairing a
# just-emitted overdue entry, an expired tally cluster or a clone that cannot
# fetch branches with "No new orientation material" would be self-contradictory
# output.
if [ "$EMITTED_ANY" -eq 0 ] && [ "$OBSERVATION_EMITTED" -eq 0 ] && [ "$TALLY_EMITTED" -eq 0 ] && [ "$CLONE_REFSPEC_EMITTED" -eq 0 ] && [ "$FAIL_SAFE_FULL_EMIT" -eq 0 ]; then
emit_section "Orientation diff" "No new orientation material since last session. Prior in-context state remains authoritative."
MARKER_EMITTED=1
fi
Expand Down
Loading
Loading