loki-mode 8.19.3 → 8.19.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 +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +10 -0
- package/dashboard/__init__.py +1 -1
- package/docs/FOUNDER-DECISIONS-TRUST-SEMANTICS-2026-08-01.md +145 -0
- 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
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v8.19.
|
|
6
|
+
# Loki Mode v8.19.5
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
469
469
|
|
|
470
470
|
---
|
|
471
471
|
|
|
472
|
-
**v8.19.
|
|
472
|
+
**v8.19.5 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.19.
|
|
1
|
+
8.19.5
|
|
@@ -653,6 +653,16 @@ def _collect_security(loki_dir):
|
|
|
653
653
|
{ran, total, active, waived, high_active, status, findings:[{rule,severity}]}.
|
|
654
654
|
status: not_run (no scan) | clean (ran, no active findings) | findings
|
|
655
655
|
(ran, active findings present).
|
|
656
|
+
|
|
657
|
+
PARTIAL record-half. An active HIGH finding IS read by _compute_degraded and
|
|
658
|
+
becomes a gap. An UNRUN scan is not: absence of a scan is deliberately not a
|
|
659
|
+
security gap (tests/test_proof_generator.py::test_no_security_file_is_not_a_gap),
|
|
660
|
+
so a receipt can read VERIFIED with an empty gap list while no scan ever ran.
|
|
661
|
+
That matches how `functional` and `healthcheck` behave, and like them,
|
|
662
|
+
changing it is the founder-gated trust decision rather than an inference to
|
|
663
|
+
make here. Stated explicitly because those two say so in their own
|
|
664
|
+
docstrings and this one did not, leaving the behaviour to be inferred from
|
|
665
|
+
silence -- which is the exact failure the honesty ledger exists to prevent.
|
|
656
666
|
"""
|
|
657
667
|
out = {
|
|
658
668
|
"ran": False, "total": 0, "active": 0, "waived": 0,
|
package/dashboard/__init__.py
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Founder Decisions: trust semantics (2026-08-01)
|
|
2
|
+
|
|
3
|
+
Two questions this session surfaced that I deliberately did not answer, because
|
|
4
|
+
answering them changes what the word "Verified" means on a receipt. Both are
|
|
5
|
+
recorded with the measurement that raised them, so the decision can be made on
|
|
6
|
+
evidence rather than on my summary of it.
|
|
7
|
+
|
|
8
|
+
Everything else from this session shipped. These are the only open items.
|
|
9
|
+
|
|
10
|
+
## Why these are yours and not mine
|
|
11
|
+
|
|
12
|
+
`autonomy/lib/proof-generator.py` already records the rule, in the note on the
|
|
13
|
+
functional-verification fact:
|
|
14
|
+
|
|
15
|
+
> Making functional-satisfaction gate the green headline is a trust-semantics
|
|
16
|
+
> product decision (council + founder), the second half of FV-2. Recording it
|
|
17
|
+
> first lets the signal be seen and validated safely.
|
|
18
|
+
|
|
19
|
+
Record first, gate later. Both items below follow that pattern: the fact is now
|
|
20
|
+
recorded and visible on every surface, and the verdict is unchanged pending your
|
|
21
|
+
call.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Decision 1: should a disabled trust gate demote "Verified"?
|
|
26
|
+
|
|
27
|
+
**Status today:** the gate is recorded and shown everywhere. The headline is
|
|
28
|
+
unchanged.
|
|
29
|
+
|
|
30
|
+
A run can disable any phase by environment variable. When code review or
|
|
31
|
+
security is switched off, the proof now records it (`quality_gates.disabled_phases`),
|
|
32
|
+
the honesty ledger carries a per-gate entry, both receipts name it, and the
|
|
33
|
+
plain-English owner page refuses to show a green "ready" badge.
|
|
34
|
+
|
|
35
|
+
What it does **not** do is change `honesty.headline`. A receipt with code review
|
|
36
|
+
disabled can still read `VERIFIED`.
|
|
37
|
+
|
|
38
|
+
**The case for demoting it:** "Verified" on a receipt with two correctness gates
|
|
39
|
+
switched off is defensible only if it means "verified as far as it was checked".
|
|
40
|
+
A reader who has not read the gaps list will not make that distinction.
|
|
41
|
+
|
|
42
|
+
**The case against:** the headline is re-derived by `proof-verify` to detect
|
|
43
|
+
tampering. Changing what feeds it changes what an honest proof looks like, and
|
|
44
|
+
older signed receipts would re-derive differently. There is also a real risk of
|
|
45
|
+
teaching users to ignore the headline if it moves for reasons they consider
|
|
46
|
+
routine.
|
|
47
|
+
|
|
48
|
+
**If you decide to demote:** the change is in `_compute_headline`, and the
|
|
49
|
+
disabled-gate entries must then be included in the verifier's re-derivation
|
|
50
|
+
rather than filtered out (`_recorded_degraded_raw`). Both halves must move
|
|
51
|
+
together or every honest receipt is reported as forged, which is exactly the bug
|
|
52
|
+
v8.19.2 fixed.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Decision 2: is an unrun security scan a gap?
|
|
57
|
+
|
|
58
|
+
**Status today:** deliberately not a gap. There is a test asserting it.
|
|
59
|
+
|
|
60
|
+
Measured on a real generated proof, with a clean build and passing tests:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
headline : VERIFIED
|
|
64
|
+
gaps listed : []
|
|
65
|
+
security fact : {'ran': False, 'status': 'not_run', 'high_active': 0}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
So a receipt reads `VERIFIED` with an empty gaps list while the security scan
|
|
69
|
+
never ran at all.
|
|
70
|
+
|
|
71
|
+
Tests and build are both flagged as gaps when they do not run. Security is
|
|
72
|
+
flagged only when it **ran and found** an active HIGH finding. The asymmetry is
|
|
73
|
+
intentional: `tests/test_proof_generator.py::test_no_security_file_is_not_a_gap`
|
|
74
|
+
states it outright, "Absence of a scan is not a security gap (the gate did not
|
|
75
|
+
run)".
|
|
76
|
+
|
|
77
|
+
I prototyped changing it and reverted, because overriding a documented decision
|
|
78
|
+
with a test guarding it is not a call I should make silently.
|
|
79
|
+
|
|
80
|
+
**The case for treating it as a gap:** the honesty ledger exists so "a reader
|
|
81
|
+
sees exactly what was NOT verified rather than inferring it from silence"
|
|
82
|
+
(`_compute_degraded`'s own docstring). An unrun security scan is precisely that,
|
|
83
|
+
and it is the one omission a reader is least likely to notice on their own.
|
|
84
|
+
|
|
85
|
+
**The case against:** most projects have no security scanner configured, so
|
|
86
|
+
every receipt would carry a security gap that means nothing about that
|
|
87
|
+
project's quality. A gap list that is never empty is a gap list nobody reads,
|
|
88
|
+
and that costs more than the omission.
|
|
89
|
+
|
|
90
|
+
**A middle option:** treat it as a gap only when the project has a scanner
|
|
91
|
+
configured and it did not run. That distinguishes "not applicable" from
|
|
92
|
+
"skipped", which is the distinction the current binary answer cannot express.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Context found after writing the above: it is three tiers, not two
|
|
97
|
+
|
|
98
|
+
Sweeping every fact the proof records showed the exclusion is a deliberate,
|
|
99
|
+
consistent pattern rather than an oversight in one place. Three facts are
|
|
100
|
+
recorded and never enter the honesty ledger on a clean run:
|
|
101
|
+
|
|
102
|
+
| Fact | In the ledger? | Documented as record-half? |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `functional` | never | yes, explicitly (FV-2) |
|
|
105
|
+
| `healthcheck` | never | yes, explicitly |
|
|
106
|
+
| `security` | only on an active HIGH finding | no note, but behaves the same when unrun |
|
|
107
|
+
|
|
108
|
+
`functional` and `healthcheck` both carry a note in their collector saying they
|
|
109
|
+
are "NOT read by `_compute_headline` / `_compute_degraded`" and that gating on
|
|
110
|
+
them is founder-gated. `security` carries no such note, yet an unrun scan is
|
|
111
|
+
excluded exactly as they are.
|
|
112
|
+
|
|
113
|
+
That changes decision 2 from "is this an oversight?" to "should the third tier
|
|
114
|
+
join the first two, or move the other way?". It also means answering it in
|
|
115
|
+
isolation would leave the receipt inconsistent: if an unrun security scan
|
|
116
|
+
becomes a gap, a reader will reasonably ask why an unrun healthcheck is not.
|
|
117
|
+
|
|
118
|
+
The cheapest correct step, if you want one, is to give `security` the same
|
|
119
|
+
explicit record-half note the other two have, so the current behaviour is
|
|
120
|
+
documented where a reader will find it rather than inferred from its absence.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## What I would recommend, briefly
|
|
125
|
+
|
|
126
|
+
Revised after the three-tier finding above, which I had not made when I first
|
|
127
|
+
wrote this section.
|
|
128
|
+
|
|
129
|
+
Decision 2 should not be taken in isolation. Answering it for security alone
|
|
130
|
+
makes the receipt internally inconsistent, because a reader who sees an unrun
|
|
131
|
+
security scan flagged will reasonably ask why an unrun healthcheck is not. The
|
|
132
|
+
real question is what the whole record-half tier should do, and that is one
|
|
133
|
+
decision, not three.
|
|
134
|
+
|
|
135
|
+
Decision 1 is the larger change and depends on how you want "Verified" to read
|
|
136
|
+
to someone who did not run the build. It is worth deciding deliberately rather
|
|
137
|
+
than inheriting, and it is independent of decision 2.
|
|
138
|
+
|
|
139
|
+
Neither is blocking. Both facts are already visible to anyone who reads the
|
|
140
|
+
receipt.
|
|
141
|
+
|
|
142
|
+
The one change I made without asking is documentation only: `security` now
|
|
143
|
+
carries the same explicit record-half note that `functional` and `healthcheck`
|
|
144
|
+
have. That records the existing behaviour where a reader will find it. It
|
|
145
|
+
changes no verdict and decides nothing.
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.19.
|
|
2
|
+
var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.19.5";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=t_(e_(import.meta.url)),Q=eK(X);h5=o_(r_(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var HQ=p(()=>{G8()});var bO={};l0(bO,{runOrThrow:()=>Of,run:()=>E0,readStreamCapped:()=>UQ,commandVersion:()=>Af,commandExists:()=>X9,ShellError:()=>Z$,MAX_STDOUT_BYTES:()=>yO});async function UQ(Z,X=yO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([UQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function Of(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Z$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Lf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Lf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Af(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var yO=16777216,Z$;var x9=p(()=>{Z$=class Z$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return jf?"":Z}var jf,L0,k8,p0,ZV0,i0,H8,Q9,v;var S6=p(()=>{jf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),k8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),ZV0=o7("\x1B[0;34m"),i0=o7("\x1B[0;36m"),H8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as xf}from"fs";async function E7(){if(k4!==void 0)return k4;let Z="/opt/homebrew/bin/python3.12";if(xf(Z))return k4=Z,Z;let X=await X9("python3.12");if(X)return k4=X,X;let Q=await X9("python3");return k4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var k4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Yh});import{existsSync as Y9,readFileSync as v3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as sf}from"path";import{homedir as nf}from"os";function sO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function nO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*NQ/X);if(J>NQ)J=NQ;let z=NQ-J,K=k8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=sO(Z),V=sO(X);return` ${H8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function rf(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
|
|
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)
|
|
@@ -1222,4 +1222,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1222
1222
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (b_(),y_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1223
1223
|
`),process.stderr.write(__),2}}cO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var fW0=await _W0(Bun.argv.slice(2));process.exit(fW0);
|
|
1224
1224
|
|
|
1225
|
-
//# debugId=
|
|
1225
|
+
//# debugId=358DEA81D936CB2E64756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "8.19.
|
|
4
|
+
"version": "8.19.5",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "8.19.
|
|
5
|
+
"version": "8.19.5",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|