Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
6ca80f9
docs: add Slack integration setup guide and app manifest
Sep 15, 2026
6ed47ed
docs(skills): rewire the spec-reviewer prompt + analyze off the dead …
aarontrowbridge Sep 20, 2026
bead5cc
feat(packs): the pack lint + the two seeded domain packs — EE/test-&-…
aarontrowbridge Sep 20, 2026
b3d5d26
docs(skills): route amico-schema-check escalation off the dead STRATE…
aarontrowbridge Sep 24, 2026
cedaaf3
docs(paper-mode): ADR-0023 + glossary for human-in-the-loop LaTeX co-…
Sep 28, 2026
629eb3b
feat(skills): concise mode — toggleable output-shaping skill
Sep 28, 2026
5d7af5f
feat(app): concise-mode toggle in the composer bottom row
Sep 28, 2026
59cbefb
feat(app): concise mode as a composer switch, applied on send
Sep 28, 2026
a2fdaa3
fix(app): concise directive rides as invisible per-turn system, not v…
Sep 28, 2026
e4f45c7
refactor(app): drop concise-mode tooltip — label is self-explanatory
Sep 28, 2026
7208d91
fix(app): persist preview open-paths across editor reload (#1640)
Sep 28, 2026
2a6f711
fix(app): gate preview restore on store hydration (fixes only-1-of-N)
Sep 28, 2026
369fb9f
chore(app-bundle): refresh manifest for preview-persistence overlay e…
Sep 28, 2026
4ec3396
fix(app): rework preview persistence to Approach A (survives reload +…
Sep 29, 2026
d37c3d6
chore(app): strip preview-persistence debug diagnostics
Sep 29, 2026
333cebd
feat(app): responsive concise toggle — hide when the composer is narrow
Sep 30, 2026
e47a124
fix(app): concise switch survives composer remount — session-keyed st…
Sep 30, 2026
177c512
feat(amico-run): amico fleet shard-rehome — the tested re-home for me…
aarontrowbridge Oct 1, 2026
ef6adaa
feat(amico-run): corrections scan — mine session corrections into ski…
aarontrowbridge Oct 2, 2026
2cea672
fix(amico-run): corrections scan tuning — the pasqal backfill verdict…
aarontrowbridge Oct 2, 2026
2ae58dd
fix(amico-run): corrections scan — independent-review hardening (#1677)
aarontrowbridge Oct 2, 2026
915d731
feat(skills): formulation-display — canonical optimization-problem pr…
aarontrowbridge Oct 4, 2026
ec56ece
feat(skills): the component inventory — integrators, objective terms,…
aarontrowbridge Oct 4, 2026
8f92ee7
docs(skills): the papers are style exemplars, not sources of truth — …
aarontrowbridge Oct 4, 2026
4cac10b
docs(skills): formulation-display gains the record doctrine — render …
aarontrowbridge Oct 4, 2026
4b1b725
docs(skills): formulation-display adopts the research loop report nam…
aarontrowbridge Oct 5, 2026
7c8d3e7
feat(app): Cmd+F find in Preview PDFs (S1) — shared DOM engine, Custo…
Oct 5, 2026
4e6c631
docs(skills): amico-catalog — compiler-stack fields landed in the com…
aarontrowbridge Oct 7, 2026
87846f6
test(skills): admission goldens — public-only regime over the real re…
aarontrowbridge Oct 10, 2026
6c74963
test(skills): admission goldens — entitled regime + entitlement leak pin
aarontrowbridge Oct 10, 2026
b203696
test(skills): admission goldens — internal mount present/absent + per…
aarontrowbridge Oct 10, 2026
27308c0
test(skills): admission goldens — least-privilege invariant across al…
aarontrowbridge Oct 10, 2026
ed295c9
test(ops): hub staging goldens — stage-internal-skills.sh mirror acro…
aarontrowbridge Oct 10, 2026
4a028e0
test(extension): panel-registration invariants — golden-enumerated we…
aarontrowbridge Oct 10, 2026
5018865
test(extension): leak-direction pins — the panel-flag golden bites
aarontrowbridge Oct 10, 2026
9970afc
docs(skills): findings F33 + F34 — the empty cast-death class and sta…
aarontrowbridge Oct 21, 2026
b24d04c
docs(skills): develop — de-companion the implementer-card reference (…
aarontrowbridge Oct 21, 2026
7ab632e
docs(skills): loop-methodology laws from the diraq-esr-realism campaign
aarontrowbridge Oct 23, 2026
3d8af4c
fix(skills): arjev-onboarding — the real Slack scope set and the one-…
aarontrowbridge Oct 25, 2026
6b2cf7c
docs(skills): spec-design checks + loop-hygiene lessons in deliberate…
aarontrowbridge Oct 26, 2026
b7cbf8b
docs(skills): intonatissimo QP-ILC step-representation guidance; conj…
aarontrowbridge Oct 26, 2026
66cd535
docs(skills): intonatissimo — scope the step guidance to what was mea…
aarontrowbridge Oct 27, 2026
6f5fb41
docs(skills): intonatissimo — QPILCStrategy knobs (:lowrank, Analytic…
aarontrowbridge Oct 28, 2026
d04e5d0
feat(session-ui): add Cmd+L (Mod-l) go-to-line shortcut in CM6 editors
harmoniqs-amico-agent[bot] Oct 28, 2026
2aca2f0
fix(app): refocus composer and restore caret on session tab switch
harmoniqs-amico-agent[bot] Oct 28, 2026
486f9bd
test: #1790 AC1/AC2/AC3 pinning tests for the three remaining SSE str…
harmoniqs-amico-agent[bot] Oct 28, 2026
a93bd6b
perf(fleet): linear-time SSE frame splitting in the three remaining r…
harmoniqs-amico-agent[bot] Oct 28, 2026
d997537
fix(test): surface engine log on session-create timeout; 120s cold-st…
harmoniqs-amico-agent[bot] Oct 28, 2026
c9bcffe
fix(onboarding): drop profileHasIdentity() gate from shouldOnboard (#…
harmoniqs-amico-agent[bot] Oct 28, 2026
2e4a398
fix(extension): type SessionStreamFilter.tail as Buffer so subarray t…
aarontrowbridge Oct 28, 2026
9966f63
chore(app-bundle): refresh manifest for session.tsx overlay edit (#1248)
aarontrowbridge Oct 28, 2026
6c3eded
fix(app-bundle): refresh manifest hashes for editor-core overlay edit…
aarontrowbridge Oct 28, 2026
f17e56c
feat(chat): inline previews for agent-written images, with copy to th…
aarontrowbridge Oct 28, 2026
88adea0
chore(deps): bump the prod-dependencies group across 1 directory with…
dependabot[bot] Oct 28, 2026
a389bed
docs(skills): Measurement index semantics on the QICK seam — intonati…
aarontrowbridge Oct 28, 2026
966b931
feat(session-ui): syntax-aware code folding in CM6 editors (harmoniqs…
harmoniqs-amico-agent[bot] Oct 29, 2026
de6ac5a
test(notturno): RED — gates' vitest steps run under CI's env (CI=true…
aarontrowbridge Oct 29, 2026
f444f56
fix(notturno): gates run vitest suites the way CI's fast job does — e…
aarontrowbridge Oct 29, 2026
38ed48a
Merge remote-tracking branch 'origin/lane/0.4.0' into opencode/2007-s…
aarontrowbridge Oct 29, 2026
158f301
Merge branch 'opencode/1658-shard-rehome' of github.com:harmoniqs/ami…
aarontrowbridge Oct 29, 2026
432d016
Merge branch '1677-featamico-run-corrections-scan-mine-session-correc…
aarontrowbridge Oct 29, 2026
31d3a3c
Merge branch 'night/amicode-1517-2026-10-29-r2' of github.com:harmoni…
aarontrowbridge Oct 29, 2026
19da81a
Merge branch 'night/amicode-1515-2026-10-28b' of github.com:harmoniqs…
aarontrowbridge Oct 29, 2026
ce3616b
fix(amico-run): corrections — cap preview jev calls at 50, keep amico…
aarontrowbridge Oct 29, 2026
5c98f47
fix(session-ui): drop unused keymapBindings opts; test Mod-L/Mod-Alt-…
aarontrowbridge Oct 29, 2026
4fb5ecb
Merge branch 'night/amicode-1248-2026-10-28b' of github.com:harmoniqs…
aarontrowbridge Oct 29, 2026
84e2188
Merge branch 'image-preview-copy' of github.com:harmoniqs/amicode int…
aarontrowbridge Oct 29, 2026
80589a5
fix(app): guard tab-switch composer refocus like type-to-focus; use p…
aarontrowbridge Oct 29, 2026
d35db15
Merge branch 'night/amicode-1790-2026-10-28b' of github.com:harmoniqs…
aarontrowbridge Oct 29, 2026
4744b0b
fix(fleet): restore the resume filter's frame-boundary floor; move Ss…
aarontrowbridge Oct 29, 2026
1fcd8b7
Merge branch 'night/amicode-1796-2026-10-28b' of github.com:harmoniqs…
aarontrowbridge Oct 29, 2026
93b0cef
fix(chat-images): await the nonce'd clipboard-image-written reply, ne…
aarontrowbridge Oct 29, 2026
9463c91
Merge branch 'feat/pdf-find-s1' of github.com:harmoniqs/amicode into …
aarontrowbridge Oct 29, 2026
ebf8e8c
Merge branch 'preview-reload-persist' of github.com:harmoniqs/amicode…
aarontrowbridge Oct 29, 2026
0f9a004
fix(preview): persist the open set only from the visible foreground s…
aarontrowbridge Oct 29, 2026
66ef7d5
Merge branch 'concise-skill' of github.com:harmoniqs/amicode into ami…
aarontrowbridge Oct 29, 2026
874299c
fix(test): warm the instance before the timed session create in the m…
aarontrowbridge Oct 29, 2026
d914dc0
Merge branch 'night/amicode-1161-2026-10-28b' of github.com:harmoniqs…
aarontrowbridge Oct 29, 2026
d2262ba
fix(concise): pin the hidden concise directive reaching the V1 prompt…
aarontrowbridge Oct 29, 2026
c9538c3
fix(extension): delete unused profileHasIdentity and its tests; refre…
aarontrowbridge Oct 29, 2026
b0edbef
Merge branch 'opencode/issue-1747-panel-invariants' of github.com:har…
aarontrowbridge Oct 29, 2026
72d5f65
Merge branch 'dependabot/npm_and_yarn/prod-dependencies-564d45e3e8' o…
aarontrowbridge Oct 29, 2026
4d2e652
test(extension,app): RED — What's-new release selection (0.3.10→0.4.0…
aarontrowbridge Oct 29, 2026
f5ec353
feat(extension,app): What's-new landing page — docs/releases/<v>.md b…
aarontrowbridge Oct 29, 2026
a66fadd
chore(deps): lockfile-only fast-uri 3.1.9 + zod 4.6.7; bump overlay p…
aarontrowbridge Oct 29, 2026
79bf53d
Merge branch 'skill-loop-laws' of github.com:harmoniqs/amicode into a…
aarontrowbridge Oct 29, 2026
79cf79d
Merge branch 'research-loop-lessons' of github.com:harmoniqs/amicode …
aarontrowbridge Oct 29, 2026
23e2ffd
fix(skills): research — pinned-harness guidance in its own paragraph …
aarontrowbridge Oct 29, 2026
78dbad4
Merge branch 'skills-intonatissimo-qpilc' of github.com:harmoniqs/ami…
aarontrowbridge Oct 29, 2026
e148399
Merge branch 'intonatissimo-step-doctrine' of github.com:harmoniqs/am…
aarontrowbridge Oct 29, 2026
45a384e
fix(skills): intonatissimo — drop #1899's duplicate null-space-penalt…
aarontrowbridge Oct 29, 2026
e18af86
Merge branch 'skills-measurement-index' of github.com:harmoniqs/amico…
aarontrowbridge Oct 29, 2026
05ff188
Merge branch 'opencode/1726-amico-catalog-schema-v3' of github.com:ha…
aarontrowbridge Oct 29, 2026
bfa6cb1
Merge branch 'opencode/issue-1743-admission-goldens' of github.com:ha…
aarontrowbridge Oct 29, 2026
59de03c
Merge branch 'arjev-skill-scopes' of github.com:harmoniqs/amicode int…
aarontrowbridge Oct 29, 2026
9fb3483
fix(skills): arjev-onboarding — add channels:join to the Slack scopes…
aarontrowbridge Oct 29, 2026
da07bf5
Merge branch 'amico/1328-reland' of github.com:harmoniqs/amicode into…
aarontrowbridge Oct 29, 2026
a7f155f
Merge branch 'amico/1326-reland' of github.com:harmoniqs/amicode into…
aarontrowbridge Oct 29, 2026
209afa7
fix(skills): analyze — `amico-run strategy-brief` → `amico strategy-b…
aarontrowbridge Oct 29, 2026
be45245
Merge branch 'findings-f33-f34' of github.com:harmoniqs/amicode into …
aarontrowbridge Oct 29, 2026
0a92a0b
test(skills): pin develop's F33 cast-death paragraph (carried over fr…
aarontrowbridge Oct 29, 2026
c1793d2
Merge branch 'docs/slack-setup-guide' of github.com:harmoniqs/amicode…
aarontrowbridge Oct 29, 2026
03547f8
fix(skills): amico-slack SETUP — Steps 4–5 for the `amico slack login…
aarontrowbridge Oct 29, 2026
e64ed19
Merge branch 'docs/paper-mode-adr' of github.com:harmoniqs/amicode in…
aarontrowbridge Oct 29, 2026
4d1120f
fix(docs): renumber the Paper Mode ADR 0023 → 0038 (0023 is taken, 00…
aarontrowbridge Oct 29, 2026
996ede6
Merge branch 'formulation-display' of github.com:harmoniqs/amicode in…
aarontrowbridge Oct 29, 2026
d941afa
fix(skills): formulation-display — quote the YAML description (unquot…
aarontrowbridge Oct 29, 2026
d8bb78a
Merge remote-tracking branch 'origin/amico/0.4-docs' into amico/0.4-i…
aarontrowbridge Oct 29, 2026
6a9fbd6
Merge remote-tracking branch 'origin/amico/0.4-features' into amico/0…
aarontrowbridge Oct 29, 2026
72deacb
Merge remote-tracking branch 'origin/amico/0.4-night' into amico/0.4-…
aarontrowbridge Oct 29, 2026
a3e1b55
Merge remote-tracking branch 'origin/opencode/2007-sandbox-gates' int…
aarontrowbridge Oct 29, 2026
687072e
Merge remote-tracking branch 'origin/opencode/2001-whats-new' into am…
aarontrowbridge Oct 29, 2026
5c55b59
chore(app-bundle): refresh manifest after the 0.4.0 batch integration
aarontrowbridge Oct 29, 2026
a2a745a
chore: lane version 0.4.0-lane.1
aarontrowbridge Oct 29, 2026
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
139 changes: 120 additions & 19 deletions .notturno/gates
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,20 @@
# loopback suites below (fleet-dependent; they self-skip without
# an sshd and mutate ~/.ssh/authorized_keys when one exists).
# Vault-dependent tests self-skip on their own existsSync/env
# guards, exactly as in CI. A `pnpm -r run build` precedes the
# suites, as in CI (several suites read built bundles).
# guards, exactly as in CI. A `pnpm -r run build` (step `build`)
# precedes the suites, as in CI (several suites read built bundles).
# The suites run in CI's environment (#2007), not the sandbox's:
# a scrubbed env (`env -i` + PATH/TMPDIR/locale/USER) with CI=true,
# GITHUB_ACTIONS=true, RUNNER_TEMP, GITHUB_BASE_REF=<base> (a
# pull_request run), TZ=UTC, and a fresh EMPTY scratch HOME — the
# GitHub runner's HOME has no ~/.amico, no ~/.config/opencode, no
# fleet; the sandbox's HOME has the implementer's opencode config
# and the env carries AWS/GH tokens + NOTTURNO_*, which tests read.
# A red suite names its failing test files (first 5) in RESULT:
# failed: vitest:extension [test/a.test.ts, test/b.test.ts]
# Divergences the gate cannot erase are printed as `warn:` lines:
# node major != CI's 20, running as root (CI's runner is not —
# chmod-000 tests cannot fail for root).
# NOT here (need the vendored engine binary / bun materialization / Julia /
# PyPI): build-binary, engine-tests, vsix-gate, boot-smoke, schema-roundtrip.
#
Expand All @@ -30,7 +42,9 @@
# Unset/unresolvable → the nearest of origin/lane/* and
# origin/main (fewest commits from merge-base to HEAD).
# No base at all → every package's suite runs.
# NOTTURNO_GATES_STEPS comma list subset of drift,typecheck,vitest (default all)
# NOTTURNO_GATES_STEPS comma list subset of drift,typecheck,build,vitest (default all;
# build only runs when vitest has packages to test)
# NOTTURNO_GATES_HERMETIC=0 run the suites in the ambient env/HOME (debugging only)
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
Expand All @@ -45,16 +59,16 @@ PLAN=0
for a in "$@"; do
case "$a" in
--plan) PLAN=1 ;;
-h|--help) sed -n '2,36p' "$0"; exit 0 ;;
-h|--help) sed -n '2,52p' "$0"; exit 0 ;;
*) echo "[notturno-gates] RESULT: RED — unknown argument: $a"; exit 2 ;;
esac
done

STEPS="${NOTTURNO_GATES_STEPS:-drift,typecheck,vitest}"
STEPS="${NOTTURNO_GATES_STEPS:-drift,typecheck,build,vitest}"
for s in ${STEPS//,/ }; do
case "$s" in
drift|typecheck|vitest) ;;
*) echo "[notturno-gates] RESULT: RED — unknown step '$s' in NOTTURNO_GATES_STEPS (known: drift,typecheck,vitest)"; exit 2 ;;
drift|typecheck|build|vitest) ;;
*) echo "[notturno-gates] RESULT: RED — unknown step '$s' in NOTTURNO_GATES_STEPS (known: drift,typecheck,build,vitest)"; exit 2 ;;
esac
done
want() { case ",$STEPS," in *",$1,"*) return 0 ;; *) return 1 ;; esac; }
Expand Down Expand Up @@ -146,38 +160,125 @@ PASSED=""
FAILED=""
HINTS=""
T0=$SECONDS
STEP_DETAIL="" # a step may set this to a short " [..]" suffix for the RESULT line
step() { # step <name> <hint-on-failure> <cmd...>
local name="$1" hint="$2" t=$SECONDS rc
shift 2
STEP_DETAIL=""
say "── $name: $*"
"$@"
rc=$?
if [ "$rc" -eq 0 ]; then
PASSED="$PASSED $name"
say "── $name: ok ($((SECONDS - t))s)"
else
FAILED="$FAILED $name"
FAILED="$FAILED $name$STEP_DETAIL"
[ -n "$hint" ] && HINTS="$HINTS $hint;"
say "── $name: FAIL exit $rc ($((SECONDS - t))s)"
say "── $name: FAIL exit $rc ($((SECONDS - t))s)${STEP_DETAIL}"
fi
}

# ── CI's environment for the vitest suites (#2007) ───────────────────────────
SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/notturno-gates.XXXXXX")" || { echo "[notturno-gates] RESULT: RED — mktemp failed"; exit 2; }
trap 'chmod -R u+rwx "$SCRATCH" 2>/dev/null; rm -rf "$SCRATCH"' EXIT
CI_TMP="$SCRATCH/tmp"
mkdir -p "$CI_TMP" "$SCRATCH/runner-temp"

# run a command the way CI's `fast` job runs a test step
ci_env() {
if [ "${NOTTURNO_GATES_HERMETIC:-1}" = "0" ]; then "$@"; return; fi
local home
home="$(mktemp -d "$SCRATCH/home.XXXXXX")" || return 2 # fresh + empty per suite
local keep=() v
for v in PATH LANG LC_ALL USER LOGNAME SHELL; do
[ -n "${!v:-}" ] && keep+=("$v=${!v}")
done
env -i "${keep[@]}" \
HOME="$home" TMPDIR="$CI_TMP" RUNNER_TEMP="$SCRATCH/runner-temp" TZ=UTC \
CI=true GITHUB_ACTIONS=true ${BASE:+GITHUB_BASE_REF="${BASE#origin/}"} \
"$@"
}

vitest_bin() { # the package's own vitest bin (no pnpm/corepack under the scratch HOME)
if [ -x "packages/$1/node_modules/.bin/vitest" ]; then echo "$ROOT/packages/$1/node_modules/.bin/vitest"; else echo ""; fi
}

# failed_files <vitest-json> [quarantine-gate-log] → "test/a.test.ts, test/b.test.ts, +N more" (first 5)
failed_files() {
node - "$@" <<'NODE' 2>/dev/null
const fs = require("fs"), path = require("path");
const [json, gateLog] = process.argv.slice(2);
const files = [];
const add = (f) => { if (f && !files.includes(f)) files.push(f); };
if (gateLog && fs.existsSync(gateLog)) {
// quarantine-gate violations: "... FAIL <kind> <suite>::<file>::<test>: ..."
for (const m of fs.readFileSync(gateLog, "utf8").matchAll(/ FAIL [\w-]+ [^:\s]+::([^:]+)::/g)) add(m[1]);
}
if (!files.length && json && fs.existsSync(json)) {
let r; try { r = JSON.parse(fs.readFileSync(json, "utf8")); } catch { r = null; }
for (const t of (r && r.testResults) || []) {
const bad = t.status === "failed" || (t.assertionResults || []).some((a) => a.status === "failed");
if (bad) add(path.relative(process.env.PKG_DIR || process.cwd(), t.name));
}
}
if (files.length) {
const head = files.slice(0, 5);
if (files.length > 5) head.push(`+${files.length - 5} more`);
process.stdout.write(head.join(", "));
}
NODE
}

set_detail() { # set_detail <pkg> <json> [gate-log]
local f
f="$(PKG_DIR="$ROOT/packages/$1" failed_files "$2" "${3:-}")"
[ -n "$f" ] && STEP_DETAIL=" [$f]"
return 0
}

vitest_args() { # common CI flags + exclusions + a JSON report
local g
VARGS=(run --passWithNoTests --exclude '**/slow/**')
for g in $ENV_DEPENDENT_EXCLUDES; do VARGS+=(--exclude "$g"); done
VARGS+=(--reporter=default --reporter=json "--outputFile.json=$1")
}

run_vitest() { # run_vitest <pkg> <json>
local bin
bin="$(vitest_bin "$1")"
vitest_args "$2"
if [ -n "$bin" ]; then
(cd "packages/$1" && ci_env "$bin" "${VARGS[@]}")
else
(cd "packages/$1" && ci_env pnpm exec vitest "${VARGS[@]}")
fi
}

run_amico_run_suite() {
local out vexit excl=() g
out="${TMPDIR:-/tmp}/notturno-amico-run-$$.json"
for g in $ENV_DEPENDENT_EXCLUDES; do excl+=(--exclude "$g"); done
(cd packages/amico-run && pnpm exec vitest run --passWithNoTests --exclude '**/slow/**' "${excl[@]}" \
--reporter=default --reporter=json --outputFile.json="$out")
local out="$SCRATCH/vitest-amico-run.json" glog="$SCRATCH/quarantine-amico-run.log" vexit qexit
run_vitest amico-run "$out"
vexit=$?
(cd packages/amico-run && node ../../scripts/test_quarantine_gate.mjs --runner-exit "$vexit" "$out")
(cd packages/amico-run && node ../../scripts/test_quarantine_gate.mjs --runner-exit "$vexit" "$out") 2>&1 | tee "$glog"
qexit=${PIPESTATUS[0]}
[ "$qexit" -ne 0 ] && set_detail amico-run "$out" "$glog"
return "$qexit"
}

run_pkg_suite() {
local p="$1" excl=() g
for g in $ENV_DEPENDENT_EXCLUDES; do excl+=(--exclude "$g"); done
(cd "packages/$p" && pnpm exec vitest run --passWithNoTests --exclude '**/slow/**' "${excl[@]}")
local p="$1" out="$SCRATCH/vitest-$1.json" rc
run_vitest "$p" "$out"
rc=$?
[ "$rc" -ne 0 ] && set_detail "$p" "$out"
return "$rc"
}

if want vitest && [ -n "$SELECTED" ]; then
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo '?')"
say "vitest env: $([ "${NOTTURNO_GATES_HERMETIC:-1}" = "0" ] && echo 'AMBIENT (NOTTURNO_GATES_HERMETIC=0)' || echo "CI mirror — env -i, CI=true GITHUB_ACTIONS=true TZ=UTC, scratch HOME under $SCRATCH${BASE:+, GITHUB_BASE_REF=${BASE#origin/}}"); node $(node --version 2>/dev/null || echo '?')"
[ "$NODE_MAJOR" != "20" ] && say "warn: node major $NODE_MAJOR != CI's 20 (actions/setup-node node-version: 20) — a red that CI would not show may be a node-version difference"
[ "$(id -u 2>/dev/null)" = "0" ] && say "warn: running as root — CI's runner is unprivileged; permission tests (chmod 000/0500 dirs) cannot fail for root"
fi

if want drift; then
step drift "overlay edit without a manifest refresh? run 'node packages/app-bundle/scripts/refresh_manifest.mjs' then drift_gate, and commit manifest.json" \
node packages/app-bundle/scripts/drift_gate.mjs
Expand All @@ -186,7 +287,7 @@ if want typecheck; then
step typecheck "" pnpm -r run typecheck
fi
if want vitest && [ -n "$SELECTED" ]; then
step build "" pnpm -r run build
want build && step build "" pnpm -r run build
for p in $SELECTED; do
if [ "$p" = "amico-run" ]; then
step "vitest:$p" "" run_amico_run_suite
Expand Down
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ _Avoid_: Explorer (VS Code's native file explorer is separate), Panel (the in-ap
The multi-document file workspace in the side panel. Holds zero or more files as inner tabs, each rendering its content (markdown rendered with a toggle to edit; text/code files in a CodeMirror editor; images and PDFs inline). Files arrive via Sidebar single-click or a Chat file pill and accumulate as tabs — each closeable and drag-reorderable; opening an already-open file focuses its existing tab rather than duplicating it. A breadcrumb bar under each pane's tab strip shows the active file's project-relative path with interactive sibling navigation. Supports recursive split panes via edge-drop: dragging a tab toward a pane's edge divides the view, and each resulting pane keeps its own tab bar, breadcrumb, zoom, and preview/edit toggle. Empty panes auto-collapse; a minimum pane dimension is enforced so splits can't shrink below a usable size. Opens and activates automatically when the first file is selected; shows a placeholder when no file is open. For committed editing, double-click the Sidebar entry to open a native VS Code tab.
_Avoid_: Editor (Preview is a multi-document viewer, not a primary editor — committed editing belongs in a native VS Code tab), File browser (the Sidebar is still the primary project-wide file tree; the breadcrumb is a contextual sibling-navigation aid scoped to the open file, not a second tree)

**Paper Mode**:
A first-class, opt-in session surface for *committed* LaTeX co-editing: the `.tex` editor is primary, Chat docks beneath it, and the compiled PDF sits to the right. Distinct from Preview (which stays a viewer) and from the Work Column layout — Paper Mode is its own layout, not a resize of an existing one. The live editor Draft is the single source of truth; the agent reads it fresh each turn; agent edits arrive as reviewable Pending Hunks rather than direct disk writes; and compilation is manual. Does not depend on the paused wide-mode work.
_Avoid_: Write mode (superseded name), Overleaf mode, wide mode (a separate, paused feature)

**Draft** (Paper Mode):
The live in-editor content of a `.tex` file, versioned by a monotonic Content Revision. In Paper Mode it is the single source of truth: the human's typing and accepted agent edits both land here, saves flush it to disk, and the agent always reads it at its current revision. Supersedes disk and any cached copy as the authority for "current content."
_Avoid_: Buffer (implementation detail), file contents (ambiguous with disk)

**Pending Hunk**:
A proposed agent edit shown as an inline, reviewable diff decoration in the Paper Mode editor, not yet written to the Draft or disk. Accepted (`✓`) hunks apply to the Draft; rejected (`✗`) hunks vanish. A Pending Hunk that overlaps concurrent human edits becomes a Conflict Hunk requiring explicit resolution — nothing lands unchecked. Only accepted hunks emit mutation-ledger receipts and enter the compiled source.
_Avoid_: Suggestion, agent write (in Paper Mode the agent proposes, it does not write disk), pending change

### Developer tooling

**Rebuild (local)**:
Expand Down
27 changes: 27 additions & 0 deletions docs/adr/0038-paper-mode-snapshot-base-3way-merge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ADR 0038 — Paper Mode: live-draft source of truth and snapshot-base 3-way merge for AI/human co-editing

Status: proposed (2026-09-28)

Tracking: harmoniqs/amicode#1620 · Glossary: `CONTEXT.md` (Paper Mode, Draft, Pending Hunk)

Paper Mode is a new opt-in session surface for *committed* LaTeX writing (editor primary, Chat docked beneath, PDF right; distinct from Preview, which stays a viewer, and independent of the paused wide-mode work). It exists because Amicode has no safe surface for AI and human to co-edit the same `.tex`: today the agent reads a stale disk/cache snapshot (edits it "saw" go missing from its output), agent writes bypass the editor buffer (the human must reopen the file), concurrent edits clobber silently, and there is no way to review an agent change before it lands or to control when compilation runs.

The decision is fourfold, and each part is hard to reverse because tooling, UI, and the mutation boundary all bind to it:

1. **The live editor Draft is the single source of truth**, versioned by a monotonic Content Revision (extending the content-revision rule ADR-0013 already mandates). In Paper Mode the agent's Read/Edit tools read the Draft *at its current revision every turn* — never disk, never a cache. "The agent always sees my edits" becomes true by construction and checkable, because the revision it read is stamped. This supersedes the coarse revert-based fix in #1422, which only reverts a *clean* buffer and blocks while the human is typing — insufficient for real co-editing.

2. **Agent edits propose to a review layer; they do not write disk.** An agent Edit/Write in Paper Mode produces Pending Hunks — inline diff decorations in the editor — leaving the Draft and disk unchanged until the human accepts. The human's own typing writes the Draft as normal and is always authoritative. Un-accepted agent text is therefore never in the file, which is what makes "the AI never interferes," "human always wins," and "nothing compiles behind my back" all fall out of one mechanism.

3. **Concurrent edits reconcile via a snapshot-base 3-way merge.** The Content Revision the agent read at edit-start is captured as the merge base. At apply time we 3-way merge (base → human-edited Draft, base → agent hunks): non-overlapping regions land as Pending Hunks; regions both parties touched become **Conflict Hunks** the human must resolve. Conflicts are **never auto-resolved** — the human explicitly wanted to be able to check whether their text was removed. CM6's `@codemirror/merge` (already a dependency, already used in `editable-diff-view.tsx`) supplies the merge/decoration primitive.

4. **Compilation is manual, on the accepted Draft.** It fires only on an explicit Compile action and compiles the Draft as it stands — Pending (un-accepted) Hunks are excluded because they are not yet in the file. Accepted hunks emit ADR-0017 mutation-ledger receipts (appearing in Files Changed with real provenance); pending and rejected hunks emit nothing.

## Considered options

- **Agent-writes-disk with a revert-style overlay (today's #1422 model), rejected.** Reachable from current code, but the agent's text *is* in the file before the human approves, so "human always wins," "review before it lands," and "compile only on approve" all become muddy special cases rather than consequences of the model. The dirty-buffer guard also silently blocks agent edits whenever the human is typing, which is the opposite of collaboration.
- **CRDT live merge (Yjs), rejected for now.** A shared mergeable document gives Google-Docs-style seamlessness, but it is a heavy, hard-to-reverse architectural commitment, and per-hunk human review sits *on top* of it anyway. The snapshot-base 3-way merge delivers the reviewed-collaboration requirement with far less machinery; revisit only if real-time multi-human co-presence becomes a requirement.
- **Promote Preview into a primary editor, rejected.** Reverses ADR-0013 and the glossary's deliberate "Preview = viewer" boundary and tangles viewer and editor responsibilities in one component. Paper Mode is a separate bounded surface instead.

## Consequences

The agent's Edit/Write tools gain a Paper-Mode-specific behavior (propose to the review layer instead of writing disk), which must fail closed — an agent edit that cannot be expressed as a Pending Hunk is surfaced, not silently written. The Draft/revision authority and the review/merge UI are new machinery, but both build on primitives that already exist (ADR-0013 content revisions, ADR-0017 receipts, `@codemirror/merge`). Paper Mode deliberately does **not** depend on the paused wide-mode work (#1434) and is built as its own layout. LaTeX language intelligence (#1513), SyncTeX (#1432/#1514), an outline navigator, and in-editor search (#1430) are out of scope — they dock into Paper Mode later as independent slices. Revisit this decision only if Amicode adopts real-time multi-human collaboration, at which point option 2 (CRDT) is the successor.
25 changes: 25 additions & 0 deletions docs/releases/0.3.10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
version: 0.3.10
date: 2026-10-27
highlights:
- title: Sticky widgets
body: >-
Opt a widget into "sticky" and it docks as a compact strip when you
scroll it out of view, so a live readout stays in sight while the
conversation moves on.
- title: Step-limited subagents report what they finished
body: >-
A subagent that runs out of steps now hands back an EXHAUSTED receipt —
what it finished and what is left — instead of stopping silently.
- title: Specs that file their own issues
body: >-
"amico spec slices" turns a spec's plan rows into linked GitHub issues,
and "amico census" gives a quick read of open work with named gaps and a
72-hour stall tripwire.
- title: Safer hub deploys
body: >-
The hub now deploys a lane build with an atomic switch, pinned
provenance, and one-step rollback.
links:
notes: https://github.com/harmoniqs/amicode/releases/tag/v0.3.10
---
Loading
Loading