loki-mode 10.9.1 → 10.10.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.
- package/.github/actions/issue-to-pr/action.yml +2 -2
- package/README.md +9 -3
- package/SKILL.md +12 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +1 -0
- package/autonomy/loki +39 -9
- package/autonomy/receipt_jwt.py +46 -1
- package/autonomy/run.sh +1 -1
- package/autonomy/telemetry.sh +1 -1
- package/autonomy/trigger-server.py +1 -0
- package/bin/loki +12 -1
- package/bin/loki-postinstall.js +12 -0
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +139 -0
- package/docs/INSTALLATION.md +1 -1
- package/docs/LOKI-10-BUILD-PROMPT.md +2 -2
- package/docs/PLANS-INDEX.md +2 -3
- package/docs/SLO.md +3 -3
- package/docs/alternative-installations.md +1 -1
- package/docs/bug-fixes/agent-04-memory-fixes.md +1 -1
- package/docs/dev/release-checklist.md +1 -1
- package/{.github/workflows/loki-issue-to-pr.yml → docs/examples/loki-issue-to-pr.yml.example} +2 -0
- package/docs/{ONE-RUN-AUDIT.md → history/one-run-audit.md} +6 -0
- package/docs/{BUG-AUDIT-v6.61.0.md → history/v6.61.0-audit.md} +6 -0
- package/docs/{V8-AGENT-SDK-PLAN.md → history/v8-agent-sdk-plan.md} +6 -0
- package/docs/{CONFIG-FILE-PLAN.md → history/v8-config-plan.md} +6 -0
- package/docs/test-scenarios/enterprise-scenarios.md +1 -1
- package/docs/v10/BOARD.md +3 -0
- package/docs/v10/CLI-MODERN.md +190 -0
- package/docs/v10/DEPS.md +148 -169
- package/docs/v10/FAILURE-CLASSES.md +23 -2
- package/docs/v10/GUARDS.md +18 -0
- package/docs/v10/LEGACY-REMOVAL.md +334 -25
- package/docs/v10/PROGRESS.md +15 -0
- package/loki-ts/dist/loki.js +1574 -1794
- package/mcp/__init__.py +1 -1
- package/package.json +3 -2
- package/packages/control-plane/dist/server.js +733 -397
- package/packages/control-plane/src/db/fixture-cleanup.ts +47 -0
- package/packages/control-plane/src/server/app.ts +2 -0
- package/packages/control-plane/src/server/integrity.ts +48 -9
- package/packages/control-plane/src/server/routes/stats.ts +16 -5
- package/packages/control-plane/src/server/runs.ts +16 -4
- package/packages/control-plane/ui/dist/assets/index-BinL6h3i.css +1 -0
- package/packages/control-plane/ui/dist/assets/index-BrA22FUs.js +152 -0
- package/packages/control-plane/ui/dist/index.html +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/schemas/status-result.schema.json +13 -22
- package/skills/release-cadence.md +5 -5
- package/web-app/dist/assets/{AdminPage-DqMnfZrH.js → AdminPage-BvbxMtZO.js} +1 -1
- package/web-app/dist/assets/{Avatar-BEof29du.js → Avatar-Dc7Scg02.js} +1 -1
- package/web-app/dist/assets/{Badge-WD53OL5m.js → Badge-BmVuOqbM.js} +1 -1
- package/web-app/dist/assets/{Button-Rzk5flhU.js → Button-DvzqhxRU.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-Bqz3PCMh.js → CockpitPage-D8AZo7Ha.js} +1 -1
- package/web-app/dist/assets/{ComparePage-Byo2NzAZ.js → ComparePage-NmqaTXDz.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BuE0kH3W.js → ErrorBoundary-PGl8Kzoq.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CKFHOPBx.js → EvidenceReceiptPanel-2NY4n9gv.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Df_kTMqI.js → GitHubIssuesPanel-DBO_LHHM.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CouvcKrZ.js → GitHubPRsPanel-K7m9uJ1N.js} +1 -1
- package/web-app/dist/assets/{HomePage-dEpLzj0x.js → HomePage-qNQzAJxM.js} +2 -2
- package/web-app/dist/assets/{LoginPage-6DVjUND5.js → LoginPage-esu3OK65.js} +1 -1
- package/web-app/dist/assets/{MagicPage-BDQ9MOfY.js → MagicPage-CCdVvXrA.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CXiJ04G_.js → MetricsPage-bEvugU2t.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BaXhZvyj.js → NotFoundPage-BlEjQ1wX.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BzE4V7Ti.js → ProjectPage-Bnug_Yzv.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-ipf6TLMX.js → ProjectsPage-M8r1vfpn.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BDxkUKN6.js → SettingsPage-NqAJpB48.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-YQXD72SM.js → ShowcasePage-8FggxL1P.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Dou7zHS0.js → SystemSettingsPage-3Sz3kFOO.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CQarfcYr.js → TeamsPage-nV-6vdWM.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CPi1R8Xj.js → TemplatesPage-Brbc_VR9.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-WiGhWdYD.js → TerminalOutput-D1yta5ac.js} +1 -1
- package/web-app/dist/assets/{activity-DOa7ejQA.js → activity-oEeKuqFe.js} +1 -1
- package/web-app/dist/assets/{bell-CX-VAlYN.js → bell-3NJtqBRR.js} +1 -1
- package/web-app/dist/assets/{bot-EDXaZmHc.js → bot-CuvJZ6x1.js} +1 -1
- package/web-app/dist/assets/{check-CnBdKk3L.js → check-n4289IZw.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BJ_ydRQW.js → chevron-left-CmQeio7h.js} +1 -1
- package/web-app/dist/assets/{circle-alert-tz8ufhc_.js → circle-alert-CmmVW-Pd.js} +1 -1
- package/web-app/dist/assets/{clock-DFBZPSsH.js → clock-QFqk7GlA.js} +1 -1
- package/web-app/dist/assets/{cloud-uy3p_5Q7.js → cloud-BNkSokmq.js} +1 -1
- package/web-app/dist/assets/{code-xml-3CbYD9YL.js → code-xml-nWeketqj.js} +1 -1
- package/web-app/dist/assets/{database-YiNBXc9c.js → database-C83MXTmJ.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-D0wBa4Cv.js → dollar-sign-Blg6TO4y.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-DDdZVZ5v.js → file-code-corner-Tk11oUzm.js} +1 -1
- package/web-app/dist/assets/{file-plus-D09LTBqt.js → file-plus-DF_eyA6I.js} +1 -1
- package/web-app/dist/assets/{globe-BirTPwff.js → globe-D5hDqqmn.js} +1 -1
- package/web-app/dist/assets/{hammer-DaE5qgnd.js → hammer-C2hm9lzl.js} +1 -1
- package/web-app/dist/assets/{index-D8t7KnQf.js → index-pMxOIwXq.js} +3 -3
- package/web-app/dist/assets/{layers-DVVrckRY.js → layers-XWQKGXg0.js} +1 -1
- package/web-app/dist/assets/{loader-circle-2-nMGmcb.js → loader-circle-BRuvorm2.js} +1 -1
- package/web-app/dist/assets/{lock-D0Xj43pC.js → lock-BSGvQf5v.js} +1 -1
- package/web-app/dist/assets/{package-BB7S3egm.js → package-3FJYUYgJ.js} +1 -1
- package/web-app/dist/assets/{plus-BLhoMrn3.js → plus-CeIx6g9P.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-DMc2uPLw.js → refresh-cw-ChutW42g.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DM3IP9x7.js → rotate-ccw-BdSjUAk-.js} +1 -1
- package/web-app/dist/assets/{scroll-text-lH2uW7rB.js → scroll-text-BjFqxpw3.js} +1 -1
- package/web-app/dist/assets/{server-BkerZcdR.js → server-C7UEKdol.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B8ohcUfu.js → shield-alert-DY8fpEs7.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-CTIxLzPD.js → thumbs-up-Cz1san1g.js} +1 -1
- package/web-app/dist/assets/{trash-2-C3zIpsH5.js → trash-2-D2MvGYeP.js} +1 -1
- package/web-app/dist/assets/{trending-down-BTT7wV4Y.js → trending-down-CxO3OYOB.js} +1 -1
- package/web-app/dist/assets/{trending-up-BzHdCDvF.js → trending-up-DCHKPJLe.js} +1 -1
- package/web-app/dist/assets/{upload-HxR34-A8.js → upload-CdPJpSHC.js} +1 -1
- package/web-app/dist/assets/{usePolling-DDj6Fupy.js → usePolling-DAH6K3ST.js} +1 -1
- package/web-app/dist/assets/{user-qH5y9yNy.js → user-Byyt01W4.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +0 -354
- package/docs/EXECUTION-COCKPIT-PLAN.md +0 -289
- package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +0 -1
- package/packages/control-plane/ui/dist/assets/index-Dbhu244h.js +0 -155
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
# BRANCH-LIFECYCLE-PLAN.md -- feature-branch-by-default lifecycle + CI/CD-aware deploy
|
|
2
|
-
|
|
3
|
-
**Features:** (A) `loki start` always works out of a feature branch off the branch it was
|
|
4
|
-
run from, commits the work at session end, and ADVISES the user to PR (does not auto-PR by
|
|
5
|
-
default). (B) `loki deploy` (see `docs/DEPLOY-PLAN.md`) becomes CI/CD-aware: when a pipeline
|
|
6
|
-
config is present, the primary advised "deploy" path is commit + push + PR, because the
|
|
7
|
-
enterprise pipeline deploys on merge/push.
|
|
8
|
-
|
|
9
|
-
**Unifying insight:** for most enterprises "git commit + PR" IS the deploy -- their existing
|
|
10
|
-
CI/CD pipeline runs on push/PR. So Loki should leave the user on a committed feature branch
|
|
11
|
-
ready to PR, and `loki deploy` should recognise pipeline projects and advise the git path.
|
|
12
|
-
|
|
13
|
-
**Status:** Architecture plan. No implementation code here. All `run.sh` line anchors below
|
|
14
|
-
were verified by reading the file at authoring time; line numbers drift, so anchors are given
|
|
15
|
-
WITH the function/marker name to re-locate.
|
|
16
|
-
|
|
17
|
-
**Hard environment fact (verified):** `autonomy/run.sh` runs under `set -uo pipefail` (line
|
|
18
|
-
172) -- there is NO `-e`. The brief's "set -euo pipefail safe" phrasing is loose; this plan is
|
|
19
|
-
written to the actual `set -uo pipefail`. Consequence: bare non-zero returns will NOT abort the
|
|
20
|
-
script, but `set -u` punishes unbound vars and `pipefail` still propagates pipe failures. All
|
|
21
|
-
new code: quote everything, `x=$((x+1))` never `((x++))`, guard every optional tool with
|
|
22
|
-
`command -v`, default every var (`${VAR:-}`).
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Product Owner scope locks (RECOMMENDED -- integrator to confirm)
|
|
27
|
-
|
|
28
|
-
These are the decisions the integrator should confirm before build. Each has a clear recommended
|
|
29
|
-
default. Enumerated: default-flip mechanism + opt-out name (A1), base-branch capture (A2), commit
|
|
30
|
-
granularity (A3) + commit-on-failure (A4), resume-reuse (A5), PR advisory-vs-auto + opt-in env name
|
|
31
|
-
(A6), LOKI_DELEGATE_BRANCH interaction (A7), parallel-mode guard (A8), CI/CD detection globs (B1),
|
|
32
|
-
shared push+PR helper (B2), deploy precedence (B3), non-execution invariant (B4).
|
|
33
|
-
|
|
34
|
-
### Change A locks
|
|
35
|
-
|
|
36
|
-
### LOCK A1 -- Default-flip mechanism + opt-out name
|
|
37
|
-
- Recommendation: **flip the default of the existing `LOKI_BRANCH_PROTECTION` to `true`** in
|
|
38
|
-
`setup_agent_branch` (currently `local branch_protection="${LOKI_BRANCH_PROTECTION:-false}"`
|
|
39
|
-
at run.sh:6236). `LOKI_BRANCH_PROTECTION=false` stays a fully working opt-out (back-compat
|
|
40
|
-
preserved for anyone who already sets it false; their behavior is unchanged).
|
|
41
|
-
- Justification: zero new surface, the var name already means exactly this, and the opt-out
|
|
42
|
-
contract is unchanged. Introducing a new name (e.g. `LOKI_FEATURE_BRANCH`) would force a
|
|
43
|
-
two-var back-compat matrix (new-true vs old-false precedence) for no behavioral gain.
|
|
44
|
-
- Also update the header doc at run.sh:159 (`# LOKI_BRANCH_PROTECTION ... (default: false)`)
|
|
45
|
-
to read `(default: true)` and the inline comment at run.sh:6235.
|
|
46
|
-
|
|
47
|
-
### LOCK A2 -- Base-branch capture: PERSIST it, fresh-run-only
|
|
48
|
-
- Capture the branch Loki was run FROM and PERSIST it to `.loki/state/base-branch.txt`
|
|
49
|
-
**before** the `git checkout -b` in `setup_agent_branch`, and **only on a fresh run**.
|
|
50
|
-
- Why persisted, not recomputed: `create_session_pr` runs at session END (run.sh:17464, far
|
|
51
|
-
from the :17298 capture), and by then HEAD is the `loki/*` branch -- recomputing
|
|
52
|
-
`git rev-parse --abbrev-ref HEAD` there would capture the loki branch, not the base. The
|
|
53
|
-
hardcoded `main` at run.sh:6291 AND the missing `--base` on `gh pr create` (run.sh:6307-6314,
|
|
54
|
-
it currently relies on the host default = main) are BOTH the same requirement violation; both
|
|
55
|
-
are fixed by reading this file.
|
|
56
|
-
- Why fresh-run-only: on resume we are already on `loki/*`; re-capturing would poison the base.
|
|
57
|
-
Mirror the proven `_start_sha_file` idiom at run.sh:14276 (`ITERATION_COUNT==0 || ! -s file`).
|
|
58
|
-
- Detached HEAD = `git rev-parse --abbrev-ref HEAD` returns the literal string `HEAD`. Detect
|
|
59
|
-
that exact value; on detached HEAD, do NOT branch and do NOT fabricate a base -- honest
|
|
60
|
-
message, proceed on current ref (LOCK A6).
|
|
61
|
-
|
|
62
|
-
### LOCK A3 -- Commit granularity: ONE squashed session-end commit
|
|
63
|
-
- Recommendation: a single session-end commit in the main flow, placed immediately BEFORE
|
|
64
|
-
`create_session_pr` at run.sh:17463 (after the loop has fully finished, in the cleanup region).
|
|
65
|
-
- Justification: simpler, `set -uo pipefail`-safe, one honest message, no per-iteration churn,
|
|
66
|
-
and it does not interleave with the live RARV loop. Per-iteration commits would multiply
|
|
67
|
-
failure surface inside the loop and fight the evidence-gate diff window
|
|
68
|
-
(`_LOKI_RUN_START_SHA`, run.sh:14279).
|
|
69
|
-
- Reuse the EXACT safe exclusions already used by the worktree path at run.sh:3401-3402:
|
|
70
|
-
`git add -A ':!.env' ':!*.key' ':!*.pem' ':!credentials*'`.
|
|
71
|
-
- Commit ONLY if something is staged (`git diff --cached --quiet` -> if non-zero, commit).
|
|
72
|
-
Nothing-to-commit is a clean no-op, never an error.
|
|
73
|
-
- Honest commit message, ABSOLUTE project rules: no emoji, no em-dash, no "Co-Authored-By:
|
|
74
|
-
Claude" / no Claude attribution. Recommended message form:
|
|
75
|
-
`Loki Mode session changes (<N> iterations, result=<code>)`.
|
|
76
|
-
|
|
77
|
-
### LOCK A4 -- Commit-on-failure: COMMIT ALWAYS (result-reflecting message)
|
|
78
|
-
- run.sh:17463 is reached for `result != 0` too. Recommendation: commit regardless of result,
|
|
79
|
-
with the result encoded in the message (LOCK A3). The whole point is to leave committed work
|
|
80
|
-
the user can inspect/PR even on a partial/failed run. Flagged as a scope lock to confirm.
|
|
81
|
-
|
|
82
|
-
### LOCK A5 -- Resume reuses the branch (signal = state file, not ITERATION_COUNT)
|
|
83
|
-
- Primary reuse gate: `.loki/state/agent-branch.txt` exists AND that branch is the current
|
|
84
|
-
branch OR is checkout-able. If so, reuse it; do NOT mint a new `loki/session-*` (the current
|
|
85
|
-
code mints one every invocation at run.sh:6249-6251, which under default-on would orphan prior
|
|
86
|
-
work on every resume).
|
|
87
|
-
- Do NOT rely solely on `ITERATION_COUNT` at the :17298 call site -- it is populated/used deep
|
|
88
|
-
in the run loop (run.sh:14263) and is NOT confirmed meaningful at session-start; the state
|
|
89
|
-
file is the order-independent, robust signal.
|
|
90
|
-
- Idempotency: if HEAD is ALREADY on any `loki/*` branch (session-* OR delegate-*, see LOCK A7),
|
|
91
|
-
treat as reuse -- do not branch off a loki branch recursively.
|
|
92
|
-
|
|
93
|
-
### LOCK A6 -- PR is ADVISORY, not auto (opt-in env = LOKI_AUTO_PR=1)
|
|
94
|
-
- Default: `create_session_pr` does NOT `git push` and does NOT `gh pr create`. Instead it
|
|
95
|
-
PRINTS (and best-effort clipboard-copies) the exact commands the user runs:
|
|
96
|
-
`git push -u origin <branch>` then
|
|
97
|
-
`gh pr create --base <base-branch> --head <branch> --title "..." --body "..."`
|
|
98
|
-
(or, if gh absent, the plain GitHub compare URL derived from origin, or a branch-name + "open
|
|
99
|
-
a PR on your host" message if the URL cannot be parsed).
|
|
100
|
-
- Opt-in: `LOKI_AUTO_PR=1` restores the current auto-push + auto-PR behavior (back-compat for
|
|
101
|
-
anyone relying on today's behavior), and that path MUST also emit `--base <base-branch>`.
|
|
102
|
-
- Justification: matches the deploy-advisory ethos (advise, never act outward), reversible,
|
|
103
|
-
outward-safe, and is literally what the founder asked ("so the user can do a PR then").
|
|
104
|
-
|
|
105
|
-
### LOCK A7 -- Interaction with LOKI_DELEGATE_BRANCH (do NOT refactor; do NOT enable both)
|
|
106
|
-
- A third branch mechanism exists: `LOKI_DELEGATE_BRANCH=1` creates `loki/delegate-<ts>` at
|
|
107
|
-
run.sh:14263 inside `run_autonomous()` (default OFF). Do NOT unify it with `setup_agent_branch`
|
|
108
|
-
-- that is scope creep on an untouched feature with high blast radius.
|
|
109
|
-
- VERIFIED call order: `setup_agent_branch` is called from `main()` at run.sh:17298, BEFORE
|
|
110
|
-
`main()` calls `run_autonomous()` (run.sh:14105, which contains the :14263 delegate code). So
|
|
111
|
-
with branch-default ON, setup would create `loki/session-*` first, then a later
|
|
112
|
-
`LOKI_DELEGATE_BRANCH=1` would nest `loki/delegate-*` on top of it -- the two do NOT compose
|
|
113
|
-
order-independently. Do NOT claim they do.
|
|
114
|
-
- Resolution: branch-default being ON makes `LOKI_DELEGATE_BRANCH` REDUNDANT (both isolate work on
|
|
115
|
-
a `loki/*` branch). Recommendation: document "do not enable both; if you want the delegate
|
|
116
|
-
naming, also set `LOKI_BRANCH_PROTECTION=false` so only one mechanism branches." The LOCK A5
|
|
117
|
-
idempotency rule (no-op if HEAD already on any `loki/*` branch) is still the safety net that
|
|
118
|
-
prevents `setup_agent_branch` from nesting when delegate happened to run first (e.g. on a
|
|
119
|
-
resume), but it is NOT a general order-independence guarantee.
|
|
120
|
-
|
|
121
|
-
### LOCK A8 -- Parallel-mode guard for the session-end commit
|
|
122
|
-
- The worktree path already commits at run.sh:3403 and merges back; the conflict-merge path
|
|
123
|
-
commits at run.sh:3646. The session-end commit at :17463 is in the SHARED main flow.
|
|
124
|
-
- Recommendation: gate the session-end commit on `[ "${PARALLEL_MODE:-false}" != "true" ]`
|
|
125
|
-
(PARALLEL_MODE is set at run.sh:778 from LOKI_PARALLEL_MODE). Belt-and-suspenders: the
|
|
126
|
-
"commit only if staged" check (LOCK A3) makes it a no-op anyway after a worktree merge, but
|
|
127
|
-
the explicit guard documents intent and avoids a redundant/confusing squash commit on top of
|
|
128
|
-
the merge commits.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
### Change B locks
|
|
133
|
-
|
|
134
|
-
### LOCK B1 -- CI/CD detection globs (exact)
|
|
135
|
-
Detect (read-only file existence, in `${TARGET_DIR:-.}`):
|
|
136
|
-
- GitHub Actions: any file matching `.github/workflows/*.yml` OR `.github/workflows/*.yaml`
|
|
137
|
-
- GitLab CI: `.gitlab-ci.yml`
|
|
138
|
-
- Jenkins: `Jenkinsfile`
|
|
139
|
-
- CircleCI: `.circleci/config.yml`
|
|
140
|
-
- Azure Pipelines: `azure-pipelines.yml`
|
|
141
|
-
- Bitbucket: `bitbucket-pipelines.yml`
|
|
142
|
-
Presence of ANY one = "pipeline project".
|
|
143
|
-
|
|
144
|
-
### LOCK B2 -- Shared push+PR helper: NEW sourced lib (literal shared function IS buildable)
|
|
145
|
-
- VERIFIED architecture fact: `autonomy/loki` does NOT source `autonomy/run.sh` (it shells out
|
|
146
|
-
to `RUN_SH` as a subprocess, run.sh:199 in loki). They are separate entry points. BUT `loki`
|
|
147
|
-
already sources sibling libs (tui.sh, crash.sh, quickstart.sh, provider-offer.sh, telemetry.sh
|
|
148
|
-
-- loki:59-252) and `run.sh` sources libs too. So a literal shared function IS buildable via a
|
|
149
|
-
NEW small lib that BOTH source.
|
|
150
|
-
- Recommendation: create `autonomy/lib/git-pr-advisory.sh` exposing pure, print-only helpers:
|
|
151
|
-
- `_git_pr_advisory_origin_url <dir>` -- echoes origin URL or empty (best-effort).
|
|
152
|
-
- `_git_pr_advisory_compare_url <origin_url> <base> <head>` -- echoes a GitHub compare URL, or
|
|
153
|
-
empty if not parseable. Reuse the ssh/https GitHub parse idiom already at run.sh:2123-2133.
|
|
154
|
-
- `print_pr_advice <base_branch> <head_branch> [<dir>]` -- prints the `git push -u origin
|
|
155
|
-
<head>` line, then the `gh pr create --base <base> --head <head> ...` line if `command -v
|
|
156
|
-
gh`, else the compare URL, else branch-name + "open a PR on your host." Best-effort
|
|
157
|
-
clipboard-copy of the push line (TTY-gated, `command -v` guarded, always `|| true`).
|
|
158
|
-
Both `run.sh` (Change A `create_session_pr`) and `loki` (`cmd_deploy`) source this lib and call
|
|
159
|
-
`print_pr_advice` so the two surfaces print BYTE-IDENTICAL, correct commands.
|
|
160
|
-
- This is the single source of truth that prevents the two surfaces from drifting.
|
|
161
|
-
|
|
162
|
-
### LOCK B3 -- Deploy precedence when a pipeline IS detected
|
|
163
|
-
- Print the git/PR path FIRST (most idiomatic for pipeline projects), via the SAME
|
|
164
|
-
`print_pr_advice` helper (commit-if-needed advice + push + PR). THEN the cloud-CLI options
|
|
165
|
-
from DEPLOY-PLAN.md as secondary.
|
|
166
|
-
- When NO pipeline config present: fall back to the cloud-CLI advisory exactly as DEPLOY-PLAN.md
|
|
167
|
-
specifies (no behavior change to that path).
|
|
168
|
-
|
|
169
|
-
### LOCK B4 -- Same hard invariant as DEPLOY-PLAN.md
|
|
170
|
-
- `loki deploy` is PRINT-ONLY. It NEVER runs `git push`, NEVER runs any cloud CLI, not even
|
|
171
|
-
`--version`. Tool detection is `command -v` ONLY. It ADVISES; the user runs the command. This
|
|
172
|
-
extends DEPLOY-PLAN LOCK 5's non-execution invariant to git push as well.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Change A: full design (with verified run.sh anchors)
|
|
177
|
-
|
|
178
|
-
### A.0 Verified current state
|
|
179
|
-
- `setup_agent_branch()` at run.sh:6232; gated `${LOKI_BRANCH_PROTECTION:-false}` at :6236;
|
|
180
|
-
early-returns when off (:6238-6241); else mints `loki/session-<ts>-$$` (:6249-6251) via
|
|
181
|
-
`git checkout -b` (:6256), writes `.loki/state/agent-branch.txt` (:6263). Called once at
|
|
182
|
-
run.sh:17298.
|
|
183
|
-
- `create_session_pr()` at run.sh:6270; reads agent-branch.txt (:6273-6285); counts commits with
|
|
184
|
-
hardcoded `main`: `git rev-list --count HEAD ^"$(git merge-base HEAD main ...)"` at :6291; if
|
|
185
|
-
>0 AUTO `git push -u origin` (:6299) and AUTO `gh pr create` with NO `--base` (:6307-6314).
|
|
186
|
-
Called at run.sh:17464.
|
|
187
|
-
- The MAIN single-stream RARV loop does NOT commit. Only the worktree path commits (:3403) and
|
|
188
|
-
the conflict-merge path (:3646). So a normal `loki start ./prd.md` leaves edits UNCOMMITTED ->
|
|
189
|
-
`create_session_pr`'s commit-count is 0 -> it skips. "Complete on a feature branch ready to
|
|
190
|
-
PR" is hollow today. THIS CHANGE ADDS THE COMMIT STEP (LOCK A3).
|
|
191
|
-
- `set -uo pipefail` at run.sh:172 (no -e). Origin-URL ssh/https parse precedent at
|
|
192
|
-
run.sh:2123-2133. PARALLEL_MODE at run.sh:778.
|
|
193
|
-
|
|
194
|
-
### A.1 `setup_agent_branch` rewrite (run.sh:6232-6268)
|
|
195
|
-
1. Flip default: `local branch_protection="${LOKI_BRANCH_PROTECTION:-true}"` (LOCK A1). Keep the
|
|
196
|
-
`!= "true"` early-return so `=false` opt-out still works.
|
|
197
|
-
2. Guard non-git: keep `git rev-parse --is-inside-work-tree` check (:6244) -> honest no-op.
|
|
198
|
-
3. Capture current ref: `cur="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"`.
|
|
199
|
-
- If `cur == "HEAD"` (detached): honest message ("detached HEAD; staying on current commit,
|
|
200
|
-
no feature branch created"), return 0, write NO base file (LOCK A2/A6).
|
|
201
|
-
- If `cur == loki/*` (session-* or delegate-*): idempotent reuse -- log "already on loki
|
|
202
|
-
branch <cur>", ensure agent-branch.txt records it, return 0 (LOCK A5/A7). Do NOT branch.
|
|
203
|
-
4. Resume reuse: if `.loki/state/agent-branch.txt` exists and names a branch that is checkout-
|
|
204
|
-
able, `git checkout <that>` and return (LOCK A5). Do NOT mint a new name.
|
|
205
|
-
5. Fresh branch: mint `loki/session-<ts>-$$` as today, BUT first persist base fresh-run-only:
|
|
206
|
-
`mkdir -p .loki/state; [ ! -s .loki/state/base-branch.txt ] && printf '%s\n' "$cur" >
|
|
207
|
-
.loki/state/base-branch.txt` (LOCK A2). Then `git checkout -b`, write agent-branch.txt.
|
|
208
|
-
6. All git calls `command -v git` guarded and `2>/dev/null`; no `((..))`; quote everything.
|
|
209
|
-
|
|
210
|
-
### A.2 Session-end commit (NEW, before run.sh:17463)
|
|
211
|
-
Insert a `commit_session_changes` call in the cleanup region immediately before
|
|
212
|
-
`create_session_pr` (:17463). Function:
|
|
213
|
-
1. `command -v git` + inside-work-tree guard; else return 0.
|
|
214
|
-
2. Guard: `[ "${PARALLEL_MODE:-false}" = "true" ]` -> return 0 (LOCK A8).
|
|
215
|
-
3. `git add -A ':!.env' ':!*.key' ':!*.pem' ':!credentials*'` (exact exclusions from :3401-3402).
|
|
216
|
-
4. `if git diff --cached --quiet; then return 0; fi` (nothing staged = clean no-op, LOCK A3).
|
|
217
|
-
5. `git commit -m "Loki Mode session changes (${ITERATION_COUNT:-0} iterations, result=${result})"`
|
|
218
|
-
(honest, no emoji/em-dash/Claude attribution; commit-always incl. failure, LOCK A3/A4).
|
|
219
|
-
6. `audit_agent_action "git_commit" ...` for the audit chain (mirrors :3647).
|
|
220
|
-
|
|
221
|
-
### A.3 `create_session_pr` rewrite (run.sh:6270-6326)
|
|
222
|
-
1. Read agent-branch.txt (keep early-returns). Read base from `.loki/state/base-branch.txt`;
|
|
223
|
-
if empty/missing, do not fabricate -- skip with honest message (detached/no-base case).
|
|
224
|
-
2. Commit count uses the captured base, NOT main:
|
|
225
|
-
`git rev-list --count HEAD ^"$(git merge-base HEAD "$base" 2>/dev/null || echo HEAD)"`.
|
|
226
|
-
Fixes the run.sh:6291 violation. If 0 commits -> honest "no commits to PR" no-op.
|
|
227
|
-
3. Default (advisory): call `print_pr_advice "$base" "$branch_name"` from the shared lib
|
|
228
|
-
(LOCK B2) -- prints `git push -u origin <branch>` + `gh pr create --base <base> --head
|
|
229
|
-
<branch> ...` (or compare URL / fallback), best-effort clipboard. Does NOT push, does NOT
|
|
230
|
-
create a PR.
|
|
231
|
-
4. `LOKI_AUTO_PR=1` -> restore current auto behavior: `git push -u origin` then `gh pr create`
|
|
232
|
-
but now WITH `--base "$base"` (LOCK A6). Keep the existing title/body block (:6308-6313).
|
|
233
|
-
5. Source the shared lib near the top of run.sh where sibling libs are sourced.
|
|
234
|
-
|
|
235
|
-
### A.4 No-op matrix (all honest, all proceed, set -uo pipefail safe)
|
|
236
|
-
| Situation | Behavior |
|
|
237
|
-
|---|---|
|
|
238
|
-
| Non-git dir | skip branch + skip commit + skip PR advice; honest log; run proceeds |
|
|
239
|
-
| Detached HEAD (`abbrev-ref`==`HEAD`) | no branch, no base file, no fabricated base; honest msg |
|
|
240
|
-
| Already on `loki/*` (session/delegate) | reuse, do not nest-branch (LOCK A5/A7) |
|
|
241
|
-
| Resume w/ existing agent-branch.txt | checkout + reuse that branch, do not mint new |
|
|
242
|
-
| CI without a remote | commit happens; push/PR advice prints (push will be user's problem); no crash |
|
|
243
|
-
| Nothing staged at session end | commit no-op (clean), advice notes "no commits to PR" |
|
|
244
|
-
| Parallel mode | session-end commit skipped (worktree already committed) |
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## Change B delta (folds into docs/DEPLOY-PLAN.md)
|
|
249
|
-
|
|
250
|
-
Add to `cmd_deploy` (DEPLOY-PLAN section 2.3), as a new FIRST step before the cloud-CLI cascade:
|
|
251
|
-
1. `_deploy_detect_cicd "$dir"` -- returns 0 if any LOCK B1 glob matches, echoes the detected
|
|
252
|
-
system name(s). Pure, read-only file existence (`compgen -G` or a `for f in .github/
|
|
253
|
-
workflows/*.yml; do [ -e "$f" ] && ...; done` guarded with nullglob-safe iteration).
|
|
254
|
-
2. If a pipeline IS detected: print a "CI/CD pipeline detected (<system>)" header, then call
|
|
255
|
-
the SHARED `print_pr_advice "$base" "$head" "$dir"` (LOCK B2) FIRST -- commit-if-needed +
|
|
256
|
-
push + PR -- because the pipeline deploys on merge/push. For `loki deploy` the base/head are
|
|
257
|
-
derived from the current repo state (`git rev-parse --abbrev-ref HEAD`); if on a `loki/*`
|
|
258
|
-
branch, base = `.loki/state/base-branch.txt` when present, else origin's default branch
|
|
259
|
-
best-effort, else honest "set your PR base manually." THEN print the cloud-CLI options from
|
|
260
|
-
DEPLOY-PLAN.md as secondary.
|
|
261
|
-
3. If NO pipeline detected: unchanged DEPLOY-PLAN.md cloud-CLI advisory.
|
|
262
|
-
4. Invariant (LOCK B4): print-only. `loki deploy` NEVER runs `git push` or any cloud CLI;
|
|
263
|
-
`command -v` only.
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## SDET test plan
|
|
268
|
-
|
|
269
|
-
### Change A: tests/test-branch-lifecycle.sh (NEW)
|
|
270
|
-
Harness mirrors tests/test-preview-public.sh: `set -uo pipefail`; SCRIPT_DIR/PROJECT_DIR;
|
|
271
|
-
PASS/FAIL/TOTAL counters with `pass`/`fail` helpers; `mktemp -d` WORKROOT; `trap cleanup EXIT
|
|
272
|
-
INT TERM`; color globals exported empty. Extract the contiguous branch block (setup_agent_branch,
|
|
273
|
-
commit_session_changes, create_session_pr) from run.sh by NAME ANCHOR (awk from
|
|
274
|
-
`setup_agent_branch() {` to the close of `create_session_pr`) into a temp lib and source THAT
|
|
275
|
-
(do not source run.sh -- it runs main). Non-vacuity gate: assert all three function defs are
|
|
276
|
-
present in the extracted lib, else fail loudly and abort. Every un-runnable case emits visible
|
|
277
|
-
FAIL/SKIP, never a silent pass.
|
|
278
|
-
|
|
279
|
-
Each test builds a throwaway git repo fixture under WORKROOT (`git init`, set user.email/name
|
|
280
|
-
to test values, an initial commit on a NAMED non-main branch e.g. `develop` to prove base != main).
|
|
281
|
-
|
|
282
|
-
1. **Branch created off CURRENT branch, not main.** Fixture HEAD on `develop`. Run
|
|
283
|
-
setup_agent_branch. Assert: HEAD now on `loki/session-*` AND `.loki/state/base-branch.txt`
|
|
284
|
-
contents == `develop` (NOT `main`). Tripwire for the run.sh:6291 / missing-`--base` violation.
|
|
285
|
-
2. **Resume reuses the branch.** Run setup_agent_branch twice (simulate resume: agent-branch.txt
|
|
286
|
-
persists). Assert exactly ONE `loki/session-*` branch exists (`git branch --list 'loki/*'` ==
|
|
287
|
-
1) and HEAD is on it -- no second branch minted.
|
|
288
|
-
3. **Commit happens / uncommitted -> committed.** Make an uncommitted file change. Run
|
|
289
|
-
commit_session_changes. Assert: `git status --porcelain` is clean AND `git log -1 --format=%s`
|
|
290
|
-
matches the honest message AND the message contains NO emoji/em-dash and NO "Claude".
|
|
291
|
-
4. **Nothing-to-commit is a clean no-op.** Clean tree. Run commit_session_changes inside a
|
|
292
|
-
`set -u -o pipefail` subshell; assert it returns 0, creates NO new commit (commit count
|
|
293
|
-
unchanged), and reaches an "ALIVE" sentinel after the call (no abort).
|
|
294
|
-
5. **Non-git dir no-op.** WORKROOT subdir that is NOT a git repo. Run all three functions; assert
|
|
295
|
-
no crash, return 0, honest message, no files created under a non-existent `.git`.
|
|
296
|
-
6. **Detached-HEAD no-op.** `git checkout <sha>` to detach. Run setup_agent_branch. Assert: no
|
|
297
|
-
`loki/*` branch created, NO base-branch.txt written, honest "detached" message, HEAD still
|
|
298
|
-
detached at that sha.
|
|
299
|
-
7. **Already-on-loki-branch no-op (idempotency).** Manually `git checkout -b loki/session-x`.
|
|
300
|
-
Run setup_agent_branch. Assert: no NEW branch, still on `loki/session-x`, not nested.
|
|
301
|
-
8. **HEADLINE -- advisory prints push+PR commands and does NOT push.** Build a bare remote
|
|
302
|
-
(`git init --bare remote.git`, `git remote add origin <bare>`) in the fixture, with at least
|
|
303
|
-
one commit on the working branch. Run create_session_pr in DEFAULT mode (no LOKI_AUTO_PR).
|
|
304
|
-
Assert BOTH halves: (a) NO push occurred -- the bare remote has ZERO refs afterward
|
|
305
|
-
(`git --git-dir=remote.git show-ref` is empty / returns non-zero); and (b) the exact strings
|
|
306
|
-
`git push -u origin loki/session-` AND `gh pr create --base develop` were PRINTED to stdout
|
|
307
|
-
(non-vacuity -- an empty-remote check alone passes falsely if nothing was printed). This is the
|
|
308
|
-
highest-stakes test: it proves the advisory advises without acting outward.
|
|
309
|
-
9. **LOKI_AUTO_PR=1 opt-in pushes (and uses --base).** With the fake-git push sentinel, run
|
|
310
|
-
create_session_pr under `LOKI_AUTO_PR=1`; assert the push sentinel IS present and the gh
|
|
311
|
-
invocation (stub gh) received `--base develop`. Proves back-compat opt-in.
|
|
312
|
-
10. **set -u / no-abort.** Run a representative setup_agent_branch + commit + advisory inside a
|
|
313
|
-
`set -u -o pipefail` subshell with minimal env; assert it reaches an "ALIVE" echo (no unbound
|
|
314
|
-
var / pipefail abort).
|
|
315
|
-
|
|
316
|
-
### Change B: extend tests/test-deploy.sh (per DEPLOY-PLAN.md section 5)
|
|
317
|
-
- **CI/CD detected -> git path printed FIRST.** Fixture with `.github/workflows/ci.yml` + a
|
|
318
|
-
Next.js package.json + all four cloud-CLI stubs on PATH. Assert the `git push`/`gh pr create`
|
|
319
|
-
advisory block is printed BEFORE the `vercel --prod` block (ordering), AND the non-execution
|
|
320
|
-
invariant still holds (no cloud-CLI sentinel, no git push sentinel).
|
|
321
|
-
- **No CI/CD -> cloud-CLI fallback unchanged.** Fixture with NO pipeline config; assert behavior
|
|
322
|
-
identical to DEPLOY-PLAN.md's existing cloud-CLI cascade (no git advisory printed).
|
|
323
|
-
- **Detection per glob.** One fixture per LOCK B1 file; assert `_deploy_detect_cicd` returns 0
|
|
324
|
-
and names the right system for each.
|
|
325
|
-
- **Shared-helper identical output.** Assert the push+PR lines printed by `loki deploy` are
|
|
326
|
-
byte-identical to those from run.sh's create_session_pr for the same base/head (proves the
|
|
327
|
-
shared lib is the single source of truth).
|
|
328
|
-
|
|
329
|
-
### Wiring into tests/run-all-tests.sh
|
|
330
|
-
Add near the preview registration (run-all-tests.sh:~204, after the FEAT-PREVIEW line):
|
|
331
|
-
```
|
|
332
|
-
run_test "Branch Lifecycle (default-on, base!=main, commit, advisory no-push)" "$SCRIPT_DIR/test-branch-lifecycle.sh"
|
|
333
|
-
```
|
|
334
|
-
(test-deploy.sh registration is already covered by DEPLOY-PLAN.md.) Both files are also covered
|
|
335
|
-
transitively by tests/run-shellcheck.sh and the mock/mutation detectors (gates #8/#9). The new
|
|
336
|
-
lib `autonomy/lib/git-pr-advisory.sh` must pass tests/run-shellcheck.sh clean.
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Docs to update on release
|
|
341
|
-
- README.md -- note `loki start` leaves work on a committed feature branch and PRINTS the PR
|
|
342
|
-
command (advisory; user runs the PR). Keep "human runs deploy" claims TRUE.
|
|
343
|
-
- CHANGELOG.md -- FEAT-BRANCH-DEFAULT (default-on feature branch + session-end commit + advisory
|
|
344
|
-
PR; `LOKI_BRANCH_PROTECTION=false` opt-out preserved; `LOKI_AUTO_PR=1` opt-in for old auto
|
|
345
|
-
behavior) and the Change B CI/CD-aware deploy delta.
|
|
346
|
-
- run.sh:159 header -- `LOKI_BRANCH_PROTECTION ... (default: true)`; document `LOKI_AUTO_PR`.
|
|
347
|
-
- wiki/CLI-Reference.md + skills/production.md -- document the branch + advisory-PR workflow.
|
|
348
|
-
|
|
349
|
-
## Critical files for implementation
|
|
350
|
-
- /Users/lokesh/git/loki-mode/autonomy/run.sh (setup_agent_branch :6232, create_session_pr :6270 incl. hardcoded main :6291 + no-base gh :6307, call sites :17298 / :17464, new session-end commit before :17463, default flip :6236, set line :172)
|
|
351
|
-
- /Users/lokesh/git/loki-mode/autonomy/lib/git-pr-advisory.sh (NEW shared print_pr_advice + origin/compare-URL helpers, sourced by BOTH run.sh and autonomy/loki)
|
|
352
|
-
- /Users/lokesh/git/loki-mode/autonomy/loki (cmd_deploy CI/CD branch + sourcing the shared lib; per docs/DEPLOY-PLAN.md)
|
|
353
|
-
- /Users/lokesh/git/loki-mode/tests/test-branch-lifecycle.sh (NEW -- mirror tests/test-preview-public.sh harness)
|
|
354
|
-
- /Users/lokesh/git/loki-mode/tests/run-all-tests.sh (register the new test near :204)
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
# Execution Cockpit - build plan
|
|
2
|
-
|
|
3
|
-
Lane: LOKI-EXECUTION-COCKPIT-70. Scope: `web-app/` only, plus the narrowest
|
|
4
|
-
`web-app/server.py` read adapter if strictly necessary (see "Backend changes").
|
|
5
|
-
|
|
6
|
-
## 0. What was read, and one correction
|
|
7
|
-
|
|
8
|
-
The directive says "read AGENTS.md fully". **There is no `AGENTS.md` at the repo
|
|
9
|
-
root** (`find . -maxdepth 3 -name AGENTS.md` returns nothing). Read instead:
|
|
10
|
-
|
|
11
|
-
- `CLAUDE.md` (root, project instructions) - style rules, release workflow.
|
|
12
|
-
- `BRANDING.md` - Loki (agent/OSS) vs Autonomi (commercial). Local web app is
|
|
13
|
-
"Loki Mode dashboard", so cockpit copy uses Loki, never Autonomi.
|
|
14
|
-
- `COMPONENTS.md` - component map. **Stale claim corrected**: it labels
|
|
15
|
-
`web-app/` "deprecated v7.44.0", but web-app is live and shipped -
|
|
16
|
-
`package.json:99-109` publishes `web-app/dist/` + `server.py`,
|
|
17
|
-
`prepublishOnly` builds it, `autonomy/loki:7496` serves it via `loki web`, and
|
|
18
|
-
three feature commits landed in `web-app/src` after v7.44.0 (`9e0cfdb0`,
|
|
19
|
-
`4d8299c9`, `4831933b`). Building here is correct.
|
|
20
|
-
- `docs/COCKPIT-SPEC.md`, `docs/COCKPIT-UX-MANDATE.md` - prior cockpit work.
|
|
21
|
-
Both target the **terminal** surface (`loki cockpit`, in-terminal image
|
|
22
|
-
render). This plan is the **web** cockpit; it inherits their principles
|
|
23
|
-
(honest always, never fake progress, alive-not-static, identity-first) and
|
|
24
|
-
does not touch the terminal path.
|
|
25
|
-
- `web-app/server.py` (8782 lines) - the API the web app actually calls.
|
|
26
|
-
**Not** `dashboard/server.py`, which is a different surface on a different port.
|
|
27
|
-
- `web-app/src/**` - App.tsx routing, `api/client.ts`, `types/api.ts`,
|
|
28
|
-
`components/ui/*`, `tailwind.config.js`, `ProjectWorkspace.tsx`,
|
|
29
|
-
`EvidenceReceiptPanel.tsx`.
|
|
30
|
-
|
|
31
|
-
## 1. The product
|
|
32
|
-
|
|
33
|
-
Not chat. An **issue-to-PR execution cockpit**: one screen that answers, in
|
|
34
|
-
order, the four questions a reviewer actually has.
|
|
35
|
-
|
|
36
|
-
1. **What was it asked to do, and how will we know it worked?** - task header
|
|
37
|
-
with the goal and acceptance criteria.
|
|
38
|
-
2. **Where is it now?** - calm phase timeline plus elapsed / time-to-first-signal.
|
|
39
|
-
3. **What did it change, and what proves it?** - changed-file review surface and
|
|
40
|
-
evidence attached to the outcome.
|
|
41
|
-
4. **What do I do about it?** - final actions, each either real or honestly
|
|
42
|
-
disabled, plus risk and rollback.
|
|
43
|
-
|
|
44
|
-
Agent chatter (logs, per-agent traces) is **secondary and collapsed by default**.
|
|
45
|
-
|
|
46
|
-
## 2. User journey
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
Projects list -> /project/:sessionId/cockpit
|
|
50
|
-
|
|
|
51
|
-
+-------------------+-------------------+
|
|
52
|
-
| |
|
|
53
|
-
LIVE (this session is the running one) HISTORICAL (any other session)
|
|
54
|
-
| |
|
|
55
|
-
header: goal + acceptance header: goal + acceptance
|
|
56
|
-
timeline: current phase pulses timeline: rendered from final status,
|
|
57
|
-
metrics: elapsed ticking labelled "historical", no ticking clock
|
|
58
|
-
evidence: gates as they land evidence: gates as recorded
|
|
59
|
-
actions: pause / stop (real) actions: open PR / rollback (real)
|
|
60
|
-
chatter: live log stream chatter: stored session log
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Entry: a "Cockpit" affordance on the existing project route. Exit: back to the
|
|
64
|
-
workspace or to the projects list. Deep-linkable; survives reload.
|
|
65
|
-
|
|
66
|
-
## 3. State model - the load-bearing decision
|
|
67
|
-
|
|
68
|
-
Live run state on this backend is **global**, not per-session:
|
|
69
|
-
`/api/session/status`, `/api/session/checklist`, `/api/session/logs`,
|
|
70
|
-
`/api/session/agents` and `/ws` all read one module-level `session` singleton
|
|
71
|
-
(`web-app/server.py:140`). There is exactly one running build per server.
|
|
72
|
-
|
|
73
|
-
Review state is **per-session**: `/api/sessions/{id}/git/status`, `/git/log`,
|
|
74
|
-
`/git/pr`, `/checkpoints`, `/github/prs/*`.
|
|
75
|
-
|
|
76
|
-
The cockpit spans both, so it must decide which mode it is in. The predicate:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
// LiveBinding: the global run telemetry belongs to THIS session only when the
|
|
80
|
-
// running process is working in this session's directory.
|
|
81
|
-
isLive = status.running && normalize(status.projectDir) === normalize(session.path)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Both sides are absolute paths: `SessionDetail.path` is `str(target)`
|
|
85
|
-
(`server.py:3831`) and `StatusResponse.projectDir` is `session.project_dir`,
|
|
86
|
-
assigned from the resolved project dir at `server.py:2704`. `normalize` strips a
|
|
87
|
-
trailing slash only; no other coercion, because a coincidental match would be
|
|
88
|
-
worse than a miss.
|
|
89
|
-
|
|
90
|
-
**When `isLive` is false the cockpit renders zero live facts** - no phase pulse,
|
|
91
|
-
no ticking elapsed, no "running" badge. It shows the historical view sourced from
|
|
92
|
-
`SessionDetail.status` and the per-session endpoints. This is the single rule
|
|
93
|
-
that keeps the screen from inventing a run.
|
|
94
|
-
|
|
95
|
-
### View states
|
|
96
|
-
|
|
97
|
-
| State | Predicate | Renders |
|
|
98
|
-
|---|---|---|
|
|
99
|
-
| `loading` | detail fetch in flight | skeletons in the real layout shape |
|
|
100
|
-
| `empty` | detail loaded, no PRD and no changed files | "nothing recorded for this session yet" + what would populate it |
|
|
101
|
-
| `running` | `isLive && !paused` | live timeline, ticking elapsed, live gates |
|
|
102
|
-
| `paused` | `isLive && status.paused` | timeline frozen with an explicit paused marker, Resume enabled |
|
|
103
|
-
| `recovering` | `isLive && status.exit_code == null && phase == 'starting'` | "starting up" - the backend's own synthetic phase, labelled as such, never as progress |
|
|
104
|
-
| `failed` | `!isLive && detail.status == 'failed'`, or `isLive && exit_code not in (null, 0)` | failure header, exit code, last output, rollback surfaced first |
|
|
105
|
-
| `completed` | `!isLive && detail.status == 'completed'` | historical timeline, evidence, final actions |
|
|
106
|
-
| `disconnected` | WS closed while `isLive` | last-known state, dimmed, with a "reconnecting" note and the age of the data |
|
|
107
|
-
|
|
108
|
-
`disconnected` is not decoration: the WS reconnects on a 3s timer
|
|
109
|
-
(`client.ts:786`) and a stale screen that looks live is an invented fact.
|
|
110
|
-
|
|
111
|
-
## 4. Phase map - the highest-risk table
|
|
112
|
-
|
|
113
|
-
The backend's phase vocabulary is **not** the directive's six display phases.
|
|
114
|
-
Real values, traced to source:
|
|
115
|
-
|
|
116
|
-
| Backend value | Written by | Display phase |
|
|
117
|
-
|---|---|---|
|
|
118
|
-
| `idle` | `server.py:2924` default | Not started |
|
|
119
|
-
| `starting` | `server.py:2996` synthetic (<15s, nothing on disk yet) | Understanding |
|
|
120
|
-
| `BOOTSTRAP` | `run.sh:6270` seed record | Understanding |
|
|
121
|
-
| `REASON` | `run.sh:3485` `get_rarv_phase_name` | Planning |
|
|
122
|
-
| `ACT` | `run.sh:3486` | Editing |
|
|
123
|
-
| `REFLECT` | `run.sh:3487` | Review |
|
|
124
|
-
| `VERIFY` | `run.sh:3488` | Testing |
|
|
125
|
-
| `BUILDING` | `run.sh:26167` `_advance_current_phase` | Editing |
|
|
126
|
-
| `COMPLETED` | `run.sh:26286` | PR-ready |
|
|
127
|
-
| `FAILED` | `run.sh:26289`, `:26294` | Failed |
|
|
128
|
-
| `UNKNOWN` | `run.sh:3489` | Unknown |
|
|
129
|
-
| anything else | - | **renders the raw string verbatim** |
|
|
130
|
-
|
|
131
|
-
Both routes persist the RARV name: bash at `run.sh:22295`, Bun via
|
|
132
|
-
`updateCurrentPhase` (`loki-ts/src/runner/autonomous.ts:670` ->
|
|
133
|
-
`state.ts:632`). The map lives in one exported constant so it is greppable and
|
|
134
|
-
testable. An unmapped phase is shown as itself - never silently bucketed.
|
|
135
|
-
|
|
136
|
-
Two honesty notes the UI must carry:
|
|
137
|
-
|
|
138
|
-
- RARV phases cycle (`iteration % 4`), so the timeline is **not** a monotonic
|
|
139
|
-
progress bar. It renders as a repeating cycle with the current step marked,
|
|
140
|
-
and iteration N of max shown alongside. Presenting REASON->VERIFY as a
|
|
141
|
-
one-way march would misrepresent the engine.
|
|
142
|
-
- `starting` is the server's own placeholder for "process alive, nothing written
|
|
143
|
-
yet". Labelled "starting up", not counted as work done.
|
|
144
|
-
|
|
145
|
-
## 5. Metrics
|
|
146
|
-
|
|
147
|
-
| Metric | Source | Honesty rule |
|
|
148
|
-
|---|---|---|
|
|
149
|
-
| Elapsed | `status.uptime` (seconds, server-computed) | Ticks locally between 2s pushes; resets to server truth on each push. Only when `isLive`. |
|
|
150
|
-
| Iteration | `status.iteration` / `status.max_iterations` | `max_iterations` falls back to env default 10 (`server.py:2977`); shown as "of ~10" when it came from the fallback is over-claiming, so we show the bare number and the max without an "estimated" claim only when a real value was read. If `max_iterations` came from the env fallback we render iteration alone. |
|
|
151
|
-
| Cost | `status.cost` | Zero renders as "not recorded", never "$0.00 spent". |
|
|
152
|
-
| Time to first result | **client-observed only** | The first transition out of `idle`/`starting` seen *by this mount*. `.loki/app-runner/first-preview.json` exists in the engine but **no web-app endpoint serves it** (`grep -n first-preview web-app/server.py` -> nothing). So TTFR is labelled "first signal seen this session" and is **hidden entirely on a reload that missed the transition**, rather than shown as an authoritative number. |
|
|
153
|
-
|
|
154
|
-
## 6. Evidence
|
|
155
|
-
|
|
156
|
-
| Tile | Source | Empty behaviour |
|
|
157
|
-
|---|---|---|
|
|
158
|
-
| Quality gates | `GET /api/session/checklist` -> `.loki/state/checklist.json` | The endpoint returns all-zeros with `items: []` when the file is absent (`server.py:3160`). **Zeros are not "all passing"** - renders "no gate results recorded". |
|
|
159
|
-
| Evidence receipt | `GET /api/proofs`, `/proofs/summary`, `/proofs/{run_id}` | Reuses the existing `EvidenceReceiptPanel`; no second receipt view. `unknown` stays its own bucket. |
|
|
160
|
-
| Changed files | `GET /api/sessions/{id}/git/status` | **Dedupe by path.** The endpoint emits two entries for one path when it is both staged and unstaged-modified (`server.py:5405-5411`), so a naive count is wrong. Staged/unstaged is folded into one row per path. |
|
|
161
|
-
| Tests / build / lint | `POST /sessions/{id}/test`, `/review` | These **run** things and return `{output, returncode}` - they are actions, not passive evidence. The tile shows "not run" until the user triggers it, and never implies a result that was not fetched. |
|
|
162
|
-
|
|
163
|
-
## 7. Actions - capability table
|
|
164
|
-
|
|
165
|
-
Every affordance is checked against a real endpoint. Anything without one
|
|
166
|
-
renders disabled **with a visible reason**, never a dead grey button.
|
|
167
|
-
|
|
168
|
-
| Action | Endpoint | State | Guard |
|
|
169
|
-
|---|---|---|---|
|
|
170
|
-
| Pause | `POST /api/session/pause` | Real | Enabled only when `isLive && !paused` |
|
|
171
|
-
| Resume | `POST /api/session/resume` | Real | Enabled only when `isLive && paused` |
|
|
172
|
-
| Stop | `POST /api/session/stop` | Real | Enabled only when `isLive`; confirm step |
|
|
173
|
-
| Open PR | `POST /api/sessions/{id}/git/pr` (title, body) | Real | Needs title; disabled when no changed files, reason shown |
|
|
174
|
-
| Commit | `POST /api/sessions/{id}/git/commit` | Real | Disabled when working tree clean |
|
|
175
|
-
| Push | `POST /api/sessions/{id}/git/push` | Real | Disabled when nothing ahead |
|
|
176
|
-
| Rollback | `POST /api/sessions/{id}/checkpoints/{cp_id}/restore` | Real, **destructive** | Two-step confirm naming the checkpoint. Disabled with "no checkpoints recorded" when the list is empty. |
|
|
177
|
-
| Request change | `POST /api/sessions/{id}/chat/preview` (dry-run diff) | Real, read-only | Shows what *would* change. Deliberately **not** wired to `chatStart` - that is the chat surface this product is not. |
|
|
178
|
-
| Approve | none | **Disabled / planned** | No endpoint approves a local run. `reviewGitHubPR` approves an *existing GitHub PR*; offered only when a PR number is known, otherwise the button reads "requires an open PR". |
|
|
179
|
-
|
|
180
|
-
## 8. Responsive behaviour
|
|
181
|
-
|
|
182
|
-
- **Mobile (<768px) - monitoring.** Single column, sticky task header, timeline
|
|
183
|
-
as a vertical stepper, metrics as a two-up row, evidence and diff collapsed
|
|
184
|
-
into disclosures. Actions in a bottom bar (the repo already has
|
|
185
|
-
`MobileBottomNav.tsx` as precedent). Diff bodies are horizontally scrollable,
|
|
186
|
-
never squeezed.
|
|
187
|
-
- **Tablet (768-1279px).** Two columns: timeline + metrics left, evidence and
|
|
188
|
-
diff right.
|
|
189
|
-
- **Desktop (>=1280px) - review.** Three regions: task header full-width;
|
|
190
|
-
timeline + metrics + risk in a left rail; diff review as the main surface;
|
|
191
|
-
evidence docked right. Chatter is a collapsed drawer at the bottom.
|
|
192
|
-
|
|
193
|
-
Existing `react-resizable-panels` is available, but the cockpit uses plain CSS
|
|
194
|
-
grid - fixed regions, no resize handles to fiddle with. (Noted because
|
|
195
|
-
`resizable-panels` v4 reads a bare numeric `size` as pixels, a known trap here.)
|
|
196
|
-
|
|
197
|
-
## 9. Accessibility
|
|
198
|
-
|
|
199
|
-
- **Landmarks**: `header` / `nav` / `main` / `aside` / `footer`, each labelled.
|
|
200
|
-
- **Keyboard**: every action reachable by Tab in visual order; the diff file list
|
|
201
|
-
is a roving-tabindex listbox (Up/Down to move, Enter to open); `g` then a digit
|
|
202
|
-
jumps to a region; `?` opens a shortcut sheet; Esc closes any disclosure. No
|
|
203
|
-
keyboard trap - the chatter drawer uses the existing `useFocusTrap` only while
|
|
204
|
-
it is modal on mobile.
|
|
205
|
-
- **Live regions**: phase changes and gate results announce via a polite
|
|
206
|
-
`aria-live` region, throttled so a 2s push does not spam a screen reader.
|
|
207
|
-
Elapsed does **not** announce.
|
|
208
|
-
- **Reduced motion**: follow the existing `motion-reduce:animate-none` idiom
|
|
209
|
-
(`ui/Badge.tsx:32`). The phase pulse, progress transitions, and any
|
|
210
|
-
celebration become static under `prefers-reduced-motion`.
|
|
211
|
-
- **Contrast**: body text on `#FFFEFB` uses `muted-accessible` (`#6B6960`), not
|
|
212
|
-
`muted` (`#939084`), which fails AA at small sizes.
|
|
213
|
-
- **Status is never colour-only**: every state carries an icon and a text label.
|
|
214
|
-
- **Dark mode**: `darkMode: 'class'` with a full `dark-*` token set - every
|
|
215
|
-
surface carries a dark variant or half the app looks broken.
|
|
216
|
-
|
|
217
|
-
## 10. Files
|
|
218
|
-
|
|
219
|
-
New, all under `web-app/src`:
|
|
220
|
-
|
|
221
|
-
| File | Role |
|
|
222
|
-
|---|---|
|
|
223
|
-
| `cockpit/phases.ts` | The phase map (section 4), display metadata, `mapPhase()` with verbatim fallback |
|
|
224
|
-
| `cockpit/useCockpitState.ts` | Fetches session detail + status + checklist + git status + checkpoints; owns `isLive`, view state, WS subscription, TTFR observation |
|
|
225
|
-
| `cockpit/TaskHeader.tsx` | Goal + acceptance criteria + provider/branch identity |
|
|
226
|
-
| `cockpit/PhaseTimeline.tsx` | The calm timeline; cycle-aware, reduced-motion aware |
|
|
227
|
-
| `cockpit/RunMetrics.tsx` | Elapsed, iteration, cost, TTFR - each with its empty rule |
|
|
228
|
-
| `cockpit/ChangeReview.tsx` | Deduped changed-file list + diff pane |
|
|
229
|
-
| `cockpit/EvidencePanel.tsx` | Gates, receipt link, test/build/lint "not run" states |
|
|
230
|
-
| `cockpit/RiskPanel.tsx` | Uncertainty + rollback affordance |
|
|
231
|
-
| `cockpit/FinalActions.tsx` | Section 7 table, rendered |
|
|
232
|
-
| `cockpit/AgentChatter.tsx` | Collapsed-by-default log drawer |
|
|
233
|
-
| `cockpit/ExecutionCockpit.tsx` | Composition + layout + keyboard map |
|
|
234
|
-
| `pages/CockpitPage.tsx` | Route shell, error boundary |
|
|
235
|
-
|
|
236
|
-
Modified:
|
|
237
|
-
|
|
238
|
-
| File | Change |
|
|
239
|
-
|---|---|
|
|
240
|
-
| `src/App.tsx` | One route: `/project/:sessionId/cockpit` |
|
|
241
|
-
| `src/api/client.ts` | Only if a needed call is missing a wrapper (additive) |
|
|
242
|
-
| `src/types/api.ts` | Add `exit_code` / `last_output` to `StatusResponse` - the server already returns them (`server.py:3025-3026`); the type just never declared them |
|
|
243
|
-
|
|
244
|
-
### Backend changes
|
|
245
|
-
|
|
246
|
-
**None planned.** Everything the cockpit needs is already on `web-app/server.py`.
|
|
247
|
-
If a genuine gap appears, the adapter goes in `web-app/server.py` (the server the
|
|
248
|
-
web app talks to), not `dashboard/server.py`. Any such addition is read-only and
|
|
249
|
-
called out in the final report.
|
|
250
|
-
|
|
251
|
-
## 11. Verification
|
|
252
|
-
|
|
253
|
-
`web-app` has **no unit-test harness** - only Playwright e2e (`tests/e2e/`,
|
|
254
|
-
baseURL `127.0.0.1:57375`) and pytest for the server. No new dependencies. So:
|
|
255
|
-
|
|
256
|
-
1. `npm run build` (`tsc -b && vite build`) - typecheck + build.
|
|
257
|
-
2. `npm run lint` - eslint.
|
|
258
|
-
3. A deterministic Playwright spec, `tests/e2e/cockpit.spec.ts`, driving every
|
|
259
|
-
view state via `page.route()` interception of the API - no live build needed.
|
|
260
|
-
Fixtures cover: loading, empty, running, paused, recovering, failed,
|
|
261
|
-
completed, disconnected, and the **non-live** case (a session that is not the
|
|
262
|
-
running one must show zero live facts).
|
|
263
|
-
4. A pure-function check on `mapPhase()` over every row of the section-4 table
|
|
264
|
-
plus an unmapped value - the one place a silent bug would be invisible.
|
|
265
|
-
|
|
266
|
-
Screenshots: captured with the installed Playwright only if a browser binary is
|
|
267
|
-
already present. If `npx playwright install` would be required, screenshots are
|
|
268
|
-
skipped and that is reported, not silently omitted.
|
|
269
|
-
|
|
270
|
-
## 12. Acceptance criteria
|
|
271
|
-
|
|
272
|
-
- [ ] No live phase, elapsed, or running badge renders when `isLive` is false.
|
|
273
|
-
- [ ] Every phase in the section-4 table maps to its stated display phase; an
|
|
274
|
-
unmapped phase renders verbatim.
|
|
275
|
-
- [ ] An absent checklist renders "no gate results recorded", never a green 0/0.
|
|
276
|
-
- [ ] Changed-file count matches distinct paths when a file is both staged and
|
|
277
|
-
unstaged-modified.
|
|
278
|
-
- [ ] Every disabled action states its reason in visible text.
|
|
279
|
-
- [ ] Rollback requires a confirm naming the checkpoint.
|
|
280
|
-
- [ ] Full keyboard traversal of header, timeline, diff list, and actions.
|
|
281
|
-
- [ ] Every animation is inert under `prefers-reduced-motion`.
|
|
282
|
-
- [ ] Mobile layout is single-column with no horizontal page scroll.
|
|
283
|
-
- [ ] Dark mode renders every cockpit surface.
|
|
284
|
-
- [ ] `npm run build` and `npm run lint` pass; the Playwright cockpit spec passes.
|
|
285
|
-
|
|
286
|
-
## 13. Out of scope
|
|
287
|
-
|
|
288
|
-
Version bump, release, commit, push. Any external or public mutation. Changes to
|
|
289
|
-
`dashboard/`, the terminal cockpit, or the chat surface.
|