loki-mode 7.72.0 → 7.73.0
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/README.md +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/git-pr-advisory.sh +112 -0
- package/autonomy/loki +404 -1
- package/autonomy/run.sh +416 -28
- package/autonomy/verify.sh +7 -1
- package/dashboard/__init__.py +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +354 -0
- package/docs/DEPLOY-PLAN.md +302 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,354 @@
|
|
|
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)
|