loki-mode 7.7.3 → 7.7.5

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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.7.3
6
+ # Loki Mode v7.7.5
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
381
381
 
382
382
  ---
383
383
 
384
- **v7.7.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.7.5 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.7.3
1
+ 7.7.5
package/autonomy/run.sh CHANGED
@@ -3575,10 +3575,17 @@ except: print('{\"total\":0,\"unacknowledged\":0}')
3575
3575
  " 2>/dev/null || echo '{"total":0,"unacknowledged":0}')
3576
3576
  fi
3577
3577
 
3578
- # Write comprehensive JSON state (atomic via temp file + mv)
3578
+ # Write comprehensive JSON state (atomic via temp file + mv).
3579
+ # v7.7.5 fix: previously used `${output_file}.tmp` (no PID suffix). When two
3580
+ # background processes both called write_dashboard_state concurrently, they
3581
+ # raced on the same .tmp filename -- one would clobber the other's content,
3582
+ # the loser's `mv` would fail with "No such file or directory" because the
3583
+ # winner already moved the shared .tmp away. This flooded the agent output
3584
+ # with `mv: rename .loki/dashboard-state.json.tmp ...` errors and made
3585
+ # Loki sessions appear broken. Now each process gets a unique tmp suffix.
3579
3586
  local project_name=$(basename "$(pwd)")
3580
3587
  local project_path=$(pwd)
3581
- local _tmp_state="${output_file}.tmp"
3588
+ local _tmp_state="${output_file}.tmp.$$.$RANDOM"
3582
3589
 
3583
3590
  # BUG #49 fix: Escape project path/name for JSON to handle special chars
3584
3591
  # (spaces, quotes, backslashes in directory names)
@@ -3643,7 +3650,12 @@ except: print('null')
3643
3650
  "notifications": $notification_summary
3644
3651
  }
3645
3652
  EOF
3646
- mv "$_tmp_state" "$output_file"
3653
+ # v7.7.5 fix: silence mv stderr so any residual race (different process
3654
+ # already swapped a newer file in) doesn't flood the agent output. The
3655
+ # PID-suffixed tmp eliminates the race; the 2>/dev/null is belt-and-
3656
+ # suspenders. `|| rm -f "$_tmp_state" 2>/dev/null` cleans up the tmp
3657
+ # on the rare failure rather than leaking it.
3658
+ mv "$_tmp_state" "$output_file" 2>/dev/null || rm -f "$_tmp_state" 2>/dev/null
3647
3659
  }
3648
3660
 
3649
3661
  #===============================================================================
@@ -11727,12 +11739,16 @@ LOKI_PROVIDER_ACTIVE=0
11727
11739
  kill_provider_child() {
11728
11740
  local killed=0
11729
11741
  local protected_pids=""
11730
- # Build list of PIDs that MUST survive a provider kill (dashboard, app-runner,
11731
- # status monitor, resource monitor). These are recorded as PID files when
11732
- # cmd_dashboard_start / cmd_api_start spawn them.
11742
+ # v7.7.5 follow-up: previously this only read `*.pid` files, but the
11743
+ # canonical registry (`register_pid` in run.sh:873) writes `*.json` files
11744
+ # named `<PID>.json`. The dashboard PID was registered as JSON and thus
11745
+ # not protected; provider kill cascade caught it. Now reads BOTH:
11746
+ # *.pid files (legacy + .loki/dashboard/dashboard.pid) AND *.json files
11747
+ # (the canonical pid registry, where the JSON filename IS the PID).
11733
11748
  local pid_root="${TARGET_DIR:-.}/.loki/pids"
11734
11749
  if [ -d "$pid_root" ]; then
11735
11750
  local pid_file pid
11751
+ # Legacy / external `.pid` files: content is the PID
11736
11752
  for pid_file in "$pid_root"/*.pid; do
11737
11753
  [ -f "$pid_file" ] || continue
11738
11754
  pid=$(cat "$pid_file" 2>/dev/null | head -1 | tr -d '[:space:]')
@@ -11740,6 +11756,19 @@ kill_provider_child() {
11740
11756
  protected_pids="${protected_pids} ${pid}"
11741
11757
  fi
11742
11758
  done
11759
+ # Canonical `register_pid` registry: filename `<PID>.json` IS the PID.
11760
+ for pid_file in "$pid_root"/*.json; do
11761
+ [ -f "$pid_file" ] || continue
11762
+ pid=$(basename "$pid_file" .json)
11763
+ # Verify numeric + alive before adding (basename may be non-numeric
11764
+ # if some other consumer wrote a non-PID JSON file here).
11765
+ case "$pid" in
11766
+ ''|*[!0-9]*) continue ;;
11767
+ esac
11768
+ if kill -0 "$pid" 2>/dev/null; then
11769
+ protected_pids="${protected_pids} ${pid}"
11770
+ fi
11771
+ done
11743
11772
  fi
11744
11773
  # Also protect the dashboard PID file at .loki/dashboard/dashboard.pid (older path).
11745
11774
  local dash_pid_file="${TARGET_DIR:-.}/.loki/dashboard/dashboard.pid"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.7.3"
10
+ __version__ = "7.7.5"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.7.3
5
+ **Version:** v7.7.5
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return u(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var y=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(n!==null)return n;let K="7.7.3";if(typeof K==="string"&&K.length>0)return n=K,n;try{let $=w7(S7(import.meta.url)),z=N1($);n=x7(F7(z,"VERSION"),"utf-8").trim()}catch{n="unknown"}return n}var n=null;var D1=R(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>S,commandVersion:()=>D7,commandExists:()=>D,ShellError:()=>C1});async function S(K,$={}){let z=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),Q,X;if($.timeoutMs&&$.timeoutMs>0)Q=setTimeout(()=>{try{z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{z.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[H,Z,q]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:q}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function N7(K,$={}){let z=await S(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function D(K){let $=k7(K),z=await S(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await D(K))return null;let Q=await S([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var c=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,z,Q){super(K);this.message=K;this.exitCode=$;this.stdout=z;this.stderr=Q;this.name="ShellError"}}});function l(K){return C7?"":K}var C7,E,C,x,O6,O,k,F,W;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=l("\x1B[0;31m"),C=l("\x1B[0;32m"),x=l("\x1B[1;33m"),O6=l("\x1B[0;34m"),O=l("\x1B[0;36m"),k=l("\x1B[1m"),F=l("\x1B[2m"),W=l("\x1B[0m")});import{existsSync as c7}from"fs";async function t(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await D("python3.12");if($)return z1=$,$;let z=await D("python3");return z1=z,z}async function s(K,$={}){let z=await t();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return S([z,"-c",K],$)}var z1;var Q1=R(()=>{c()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as N,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as w,basename as a7}from"path";async function r7(){if(await D("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${W}
2
+ var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var b=(K,$)=>{for(var z in $)_7(K,z,{get:$[z],enumerable:!0,configurable:!0,set:P7.bind($,z)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var V1=import.meta.require;var e1={};b(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let z=S1(K);if(z===K)break;K=z}return u(i1,"..","..","..")}function N1(K){let $=K;for(let z=0;z<6;z++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let Q=S1($);if(Q===$)break;$=Q}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var y=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as x7}from"fs";import{resolve as F7,dirname as w7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(n!==null)return n;let K="7.7.5";if(typeof K==="string"&&K.length>0)return n=K,n;try{let $=w7(S7(import.meta.url)),z=N1($);n=x7(F7(z,"VERSION"),"utf-8").trim()}catch{n="unknown"}return n}var n=null;var D1=R(()=>{y()});var $0={};b($0,{runOrThrow:()=>N7,run:()=>S,commandVersion:()=>D7,commandExists:()=>D,ShellError:()=>C1});async function S(K,$={}){let z=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),Q,X;if($.timeoutMs&&$.timeoutMs>0)Q=setTimeout(()=>{try{z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{z.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[H,Z,q]=await Promise.all([new Response(z.stdout).text(),new Response(z.stderr).text(),z.exited]);return{stdout:H,stderr:Z,exitCode:q}}finally{if(Q)clearTimeout(Q);if(X)clearTimeout(X)}}async function N7(K,$={}){let z=await S(K,$);if(z.exitCode!==0)throw new C1(`command failed (${z.exitCode}): ${K.join(" ")}`,z.exitCode,z.stdout,z.stderr);return z}async function D(K){let $=k7(K),z=await S(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(z.exitCode===0)return z.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await D(K))return null;let Q=await S([K,$],{timeoutMs:5000});if(Q.exitCode!==0)return null;return((Q.stdout||Q.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var c=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,z,Q){super(K);this.message=K;this.exitCode=$;this.stdout=z;this.stderr=Q;this.name="ShellError"}}});function l(K){return C7?"":K}var C7,E,C,x,O6,O,k,F,W;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=l("\x1B[0;31m"),C=l("\x1B[0;32m"),x=l("\x1B[1;33m"),O6=l("\x1B[0;34m"),O=l("\x1B[0;36m"),k=l("\x1B[1m"),F=l("\x1B[2m"),W=l("\x1B[0m")});import{existsSync as c7}from"fs";async function t(){if(z1!==void 0)return z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return z1=K,K;let $=await D("python3.12");if($)return z1=$,$;let z=await D("python3");return z1=z,z}async function s(K,$={}){let z=await t();if(!z)return{stdout:"",stderr:"python3 not found",exitCode:127};return S([z,"-c",K],$)}var z1;var Q1=R(()=>{c()});var G0={};b(G0,{runStatus:()=>z5});import{existsSync as N,readFileSync as Z1,readdirSync as H0,statSync as W0}from"fs";import{resolve as w,basename as a7}from"path";async function r7(){if(await D("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${W}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -550,4 +550,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
550
550
  `),2}default:return process.stderr.write(`Unknown command: ${$}
551
551
  `),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var z6=await $6(Bun.argv.slice(2));process.exit(z6);
552
552
 
553
- //# debugId=B6208839B2FD587864756E2164756E21
553
+ //# debugId=387990C60D3104AE64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.7.3'
60
+ __version__ = '7.7.5'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.7.3",
3
+ "version": "7.7.5",
4
4
  "description": "Loki Mode by Autonomi. Multi-agent autonomous SDLC framework. Spec to deployed app: PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief. 4 AI providers (Claude Code, OpenAI Codex, Cline, Aider). 11 quality gates.",
5
5
  "keywords": [
6
6
  "agent",
@@ -0,0 +1,145 @@
1
+ # SDLC Fleet Pattern (skill module)
2
+
3
+ **Established:** v7.7.4 (2026-05-24)
4
+ **Origin:** user mandate "agent teams with senior most developers, Loki architects, sdet team and council of reviewers exist and coordinate and work in parallel"
5
+ **Proven by:** v7.6.0 fleet ship (architect + 3 principal engineers + SDET + 3-reviewer council + real-user QA) demonstrated the pattern works end-to-end.
6
+
7
+ This skill describes the STANDING pattern for shipping any non-trivial change in Loki Mode. The integrator (Claude Code session) is responsible for orchestrating the roles below. Each role is a Loki Agent call.
8
+
9
+ ## When to invoke this pattern
10
+
11
+ Apply this pattern when shipping ANY of:
12
+
13
+ - A new feature touching > 3 files
14
+ - A bug fix touching agent runtime, council, memory, or auto-spawn surfaces
15
+ - A MINOR or MAJOR release
16
+ - A change with cross-route parity implications (bash + Bun + TS)
17
+ - A change the user has marked "critical" or "production-blocking"
18
+
19
+ Skip this pattern (acceptable shortcuts) when:
20
+
21
+ - Single-line typo fix in user-facing strings
22
+ - Pure docs edit (no code change)
23
+ - A revert of a previously-shipped change with no new logic
24
+ - An emergency hotfix where waiting on parallel agents would exceed the production-impact window
25
+
26
+ ## The 6 roles
27
+
28
+ ### 1. Architect (1 agent, Plan subagent_type, model: opus)
29
+
30
+ **Job:** Design the change end-to-end before any code is written. Produces a written plan saved as `docs/<TOPIC>-PLAN.md`.
31
+
32
+ **Inputs:** the original user directive verbatim + relevant source files + any prior council findings.
33
+
34
+ **Outputs:** a markdown plan with: scope, acceptance criteria, implementation steps, file-level diffs sketch, testing plan, risks + mitigations, NOT-tested honest disclosure.
35
+
36
+ **Output is BINDING** for the dev fleet -- don't change scope without re-architecting.
37
+
38
+ ### 2. Product Owner (the integrator, NOT a separate agent)
39
+
40
+ **Job:** Lock scope before implementation. Use `AskUserQuestion` to surface ambiguity to the user. NEVER guess at scope.
41
+
42
+ **Output:** explicit user answer that locks the acceptance criteria.
43
+
44
+ ### 3. Dev Fleet (3-5 principal engineers in parallel, model: opus)
45
+
46
+ **Job:** Implement independent slices of the architect's plan IN PARALLEL. Each engineer gets a single self-contained task with explicit file references and binding constraints.
47
+
48
+ **Pattern:** spawn N agents via `Agent` tool with `run_in_background: true` in ONE message (parallel). Each agent gets:
49
+
50
+ - The architect's plan section relevant to their slice
51
+ - A SINGLE deliverable (one fix, one feature, one file)
52
+ - Binding constraints (no version bumps, no commits, no emojis, no em dashes, NO destructive operations)
53
+ - Explicit "report back in N words" cap
54
+
55
+ The integrator does NOT delegate synthesis -- they integrate the parallel outputs themselves.
56
+
57
+ ### 4. SDET (1-2 agents, model: opus)
58
+
59
+ **Job:** Write tests + capture screenshots. For each shipped feature:
60
+
61
+ - One or more bash / Playwright tests asserting acceptance criteria
62
+ - For UI surfaces: screenshot capture with `chrome --headless --screenshot` or Playwright
63
+ - For backends: curl-based smoke tests
64
+
65
+ **Output:** test files + screenshot artifacts under `artifacts/<release>-screens/`.
66
+
67
+ ### 5. Council Reviewers (3 agents, parallel, model: 2 opus + 1 sonnet)
68
+
69
+ **Job:** Independent code review. Binding gate: unanimous APPROVE required to ship.
70
+
71
+ **Pattern:** spawn 3 agents in parallel via `Agent` tool. Each gets:
72
+
73
+ - The full file diff at HEAD
74
+ - Their specialized focus (e.g. "frontend correctness", "backend / integration risk", "release readiness")
75
+ - Strict output format: `VOTE: APPROVE | CONCERN | REJECT` + findings list + reasoning
76
+
77
+ On any CONCERN or REJECT:
78
+
79
+ 1. Integrator reads source + validates concern
80
+ 2. If valid: dispatch fix agent + RE-RUN entire council on post-fix state
81
+ 3. If invalid: refute with evidence + re-spawn ONLY the dissenting reviewer with the refutation
82
+ 4. Loop until 3-of-3 APPROVE
83
+ 5. "2-of-3 is good enough" is NEVER acceptable
84
+
85
+ ### 6. Real-User QA (1 agent OR the integrator directly)
86
+
87
+ **Job:** Install the just-shipped release fresh from npm/bun, run a user-realistic scenario end-to-end, capture screenshots, report any breakage.
88
+
89
+ **Pattern:** after `gh release` confirms, run:
90
+
91
+ ```bash
92
+ bun install -g loki-mode@<NEW_VERSION>
93
+ loki --version # confirm new version on PATH
94
+ # Then exercise the new feature in a user-realistic way:
95
+ loki <new-command> ... # the change we just shipped
96
+ ```
97
+
98
+ Capture:
99
+ - Exit codes
100
+ - Output structure
101
+ - Any unexpected errors / warnings
102
+ - Browser screenshots if UI surface
103
+
104
+ ## Parallel-execution rules
105
+
106
+ When spawning multiple agents in the SAME message:
107
+
108
+ - Use `Agent` tool with `run_in_background: true`
109
+ - Send all N tool calls in a SINGLE message (parallel scheduling)
110
+ - Each agent gets a SELF-CONTAINED prompt (no shared context)
111
+ - Each agent gets a SHORT response cap (under 250 words)
112
+ - The integrator INTEGRATES the parallel outputs -- never delegates synthesis
113
+
114
+ Anti-pattern: spawning agents sequentially when work is independent. Always parallel.
115
+
116
+ ## Honest acknowledgements pattern
117
+
118
+ Every CHANGELOG entry that ships work via this pattern MUST include:
119
+
120
+ ```markdown
121
+ ### NOT tested in this release
122
+
123
+ - <each test that was skipped + reason>
124
+ - <any feature shipped without end-to-end real-user repro>
125
+ - <any divergence from architect plan + workaround>
126
+ ```
127
+
128
+ Failing to disclose is fabrication. CLAUDE.md MEMORY.md "Be real, no fabrication, no lying" applies.
129
+
130
+ ## Reference run: v7.6.0 LSP integration
131
+
132
+ Demonstrated this pattern works:
133
+
134
+ 1. **Architect** (Plan agent): designed Merge-3 in `docs/MERGE3-PLAN.md`
135
+ 2. **Product Owner** (integrator + AskUserQuestion): user chose merge scope
136
+ 3. **Dev Fleet** (3 principal engineers in parallel): Engineer A fixed memory PYTHONPATH; Engineer B added USAGE.md auto-gen across bash + TS; Engineer C added Memory drill-down UI
137
+ 4. **SDET** (Explore + Playwright): captured 20+ UI screenshots, ran 24-scenario CLI permutation test
138
+ 5. **Council** (2 Opus + 1 Sonnet): 2 rounds; round 1 had 2 CONCERN-major; fixes applied; round 2 unanimous APPROVE
139
+ 6. **Real-user QA**: fresh `bun install -g loki-mode@7.7.0` + ran on a different stack (Python Flask) than dev test + verified USAGE.md auto-generated + Memory drill-down rendered in a real browser
140
+
141
+ Result: 4 releases shipped that day with all 8 GitHub workflows SUCCESS on each.
142
+
143
+ ## Per-release checklist (always)
144
+
145
+ See `artifacts/ROADMAP-v7.6.2-v7.7.0.md` "Per-release CLAUDE.md checklist" section for the binding 14-step checklist (architect plan, 14 version bumps, CHANGELOG, pre-publish validation, council, local-ci, individual staging, push, workflow validation, distribution validation, real-user smoke test, UI screenshot regression, cleanup).