loki-mode 7.52.0 → 7.53.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +3 -3
- package/autonomy/prd-checklist.sh +18 -8
- package/autonomy/run.sh +115 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +115 -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
- package/skills/quality-gates.md +8 -5
- package/src/audit/index.js +84 -0
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 v7.
|
|
6
|
+
# Loki Mode v7.53.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -407,4 +407,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
407
407
|
|
|
408
408
|
---
|
|
409
409
|
|
|
410
|
-
**v7.
|
|
410
|
+
**v7.53.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.53.0
|
package/autonomy/loki
CHANGED
|
@@ -743,7 +743,7 @@ show_help() {
|
|
|
743
743
|
echo " --complex Force complex complexity tier (8 phases)"
|
|
744
744
|
echo " --github Enable GitHub issue import"
|
|
745
745
|
echo " --no-dashboard Disable web dashboard"
|
|
746
|
-
echo " --sandbox Run in Docker sandbox for isolation"
|
|
746
|
+
echo " --sandbox Run in Docker sandbox for isolation (default: off; requires Docker)"
|
|
747
747
|
echo " --skip-memory Skip loading memory context at startup"
|
|
748
748
|
echo " --fresh-prd Regenerate the PRD from the codebase (no-PRD runs; ignores the reusable generated PRD)"
|
|
749
749
|
echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
|
|
@@ -1063,7 +1063,7 @@ cmd_start() {
|
|
|
1063
1063
|
echo " --github Enable GitHub issue import"
|
|
1064
1064
|
echo " --no-dashboard Disable web dashboard"
|
|
1065
1065
|
echo " --api Start dashboard API server alongside the build"
|
|
1066
|
-
echo " --sandbox Run in Docker sandbox"
|
|
1066
|
+
echo " --sandbox Run in Docker sandbox (default: off; requires Docker)"
|
|
1067
1067
|
echo " --skip-memory Skip loading memory context at startup"
|
|
1068
1068
|
echo " --fresh-prd Regenerate the PRD from the codebase on a no-PRD run"
|
|
1069
1069
|
echo " (ignores the reusable generated PRD; aliases: --regen-prd,"
|
|
@@ -5680,7 +5680,7 @@ cmd_run() {
|
|
|
5680
5680
|
echo " --simple Force simple complexity tier"
|
|
5681
5681
|
echo " --complex Force complex complexity tier"
|
|
5682
5682
|
echo " --no-dashboard Disable web dashboard"
|
|
5683
|
-
echo " --sandbox Run in Docker sandbox"
|
|
5683
|
+
echo " --sandbox Run in Docker sandbox (default: off; requires Docker)"
|
|
5684
5684
|
echo " --no-plan Skip auto-shown PRD analysis at startup"
|
|
5685
5685
|
echo " --budget USD Set cost budget limit"
|
|
5686
5686
|
echo ""
|
|
@@ -422,7 +422,11 @@ checklist_should_verify() {
|
|
|
422
422
|
# non-cooperative agent with filesystem tools can read the reservation directly.
|
|
423
423
|
#
|
|
424
424
|
# Selection is idempotent and reproducible: count = clamp(round(0.25*N), 1, 5)
|
|
425
|
-
# for N>=
|
|
425
|
+
# for N>=2 items; ordering by sha256 of each item's "id" (stable, not random).
|
|
426
|
+
# Small checklists (2 <= N < 4) reserve exactly 1 held-out item via the same
|
|
427
|
+
# sha256-rank selection (the clamp floor of 1 guarantees coverage), so a small
|
|
428
|
+
# spec's checklist is never fully gameable. N<2 is a no-op: holding out the only
|
|
429
|
+
# item of a 1-item checklist would leave nothing to verify against in the loop.
|
|
426
430
|
# Written once to .loki/checklist/held-out.json; never overwritten if present.
|
|
427
431
|
checklist_select_heldout() {
|
|
428
432
|
local heldout_file="${CHECKLIST_DIR:-".loki/checklist"}/held-out.json"
|
|
@@ -442,7 +446,7 @@ checklist_select_heldout() {
|
|
|
442
446
|
# PARTIAL kept=k dropped=d - some prior ids survived; we keep only survivors
|
|
443
447
|
# DUP_SKIP - current checklist ids are not unique; the id-based
|
|
444
448
|
# mechanism is unsound, so we reserve nothing (MEDIUM-2)
|
|
445
|
-
# NOOP - n<
|
|
449
|
+
# NOOP - n<2 with no prior file, or other no-write outcome
|
|
446
450
|
# Honest caveat: re-selection or partial-survival after a regen can reserve
|
|
447
451
|
# items the build loop already saw in earlier prompts (the hidden-from-loop
|
|
448
452
|
# guarantee is best-effort once the checklist ids change mid-run).
|
|
@@ -512,7 +516,7 @@ if os.path.exists(out_path):
|
|
|
512
516
|
|
|
513
517
|
if prior is not None:
|
|
514
518
|
prior_ids = [i for i in prior.get('held_out', []) if i]
|
|
515
|
-
# A prior reservation of [] (e.g. an earlier n<
|
|
519
|
+
# A prior reservation of [] (e.g. an earlier n<2 run) is a valid no-op state;
|
|
516
520
|
# keep it idempotent rather than re-selecting now that n may have grown.
|
|
517
521
|
if not prior_ids:
|
|
518
522
|
print('IDEMPOTENT')
|
|
@@ -525,9 +529,11 @@ if prior is not None:
|
|
|
525
529
|
if not survivors:
|
|
526
530
|
# Fully stale: the checklist regenerated and orphaned the reservation.
|
|
527
531
|
# Deterministically re-select from the CURRENT checklist.
|
|
528
|
-
if n <
|
|
532
|
+
if n < 2:
|
|
533
|
+
# N<2: cannot hold out from a 1-item checklist (reserving the only
|
|
534
|
+
# item leaves nothing to verify against). No-op write of an empty set.
|
|
529
535
|
atomic_write({'held_out': [], 'total_items': n,
|
|
530
|
-
'note': 'n<
|
|
536
|
+
'note': 'n<2: no held-out reserved (re-selected after stale reservation)'})
|
|
531
537
|
print('RESELECTED 0')
|
|
532
538
|
sys.exit(0)
|
|
533
539
|
held = fresh_selection()
|
|
@@ -542,11 +548,15 @@ if prior is not None:
|
|
|
542
548
|
sys.exit(0)
|
|
543
549
|
|
|
544
550
|
# No prior reservation: first selection.
|
|
545
|
-
if n <
|
|
546
|
-
# N
|
|
547
|
-
|
|
551
|
+
if n < 2:
|
|
552
|
+
# N<2 gate: a 1-item (or empty) checklist cannot meaningfully hold out an
|
|
553
|
+
# item -- reserving the only item would leave nothing to verify against in
|
|
554
|
+
# the build loop. Write an empty set so downstream reads stay well-formed.
|
|
555
|
+
atomic_write({'held_out': [], 'total_items': n, 'note': 'n<2: no held-out reserved'})
|
|
548
556
|
print('NOOP')
|
|
549
557
|
sys.exit(0)
|
|
558
|
+
# For 2 <= N < 4, fresh_selection() reserves exactly 1 item (select_count clamps
|
|
559
|
+
# round(0.25*N) up to a floor of 1), so small specs are never fully gameable.
|
|
550
560
|
|
|
551
561
|
held = fresh_selection()
|
|
552
562
|
atomic_write({'held_out': held, 'total_items': n})
|
package/autonomy/run.sh
CHANGED
|
@@ -585,7 +585,7 @@ BACKGROUND_MODE=${LOKI_BACKGROUND:-false} # Run in background
|
|
|
585
585
|
STAGED_AUTONOMY=${LOKI_STAGED_AUTONOMY:-false} # Require plan approval
|
|
586
586
|
AUDIT_LOG_ENABLED=${LOKI_AUDIT_LOG:-true} # Enable audit logging (on by default)
|
|
587
587
|
MAX_PARALLEL_AGENTS=${LOKI_MAX_PARALLEL_AGENTS:-10} # Limit concurrent agents
|
|
588
|
-
SANDBOX_MODE=${LOKI_SANDBOX_MODE:-false} # Docker sandbox mode
|
|
588
|
+
SANDBOX_MODE=${LOKI_SANDBOX_MODE:-false} # Docker sandbox mode (informational; the real dispatch reads LOKI_SANDBOX_MODE at autonomy/loki:1965 and execs sandbox.sh -- this var is not consumed in run.sh)
|
|
589
589
|
ALLOWED_PATHS=${LOKI_ALLOWED_PATHS:-""} # Empty = all paths allowed
|
|
590
590
|
BLOCKED_COMMANDS=${LOKI_BLOCKED_COMMANDS:-"rm -rf /,dd if=,mkfs,:(){ :|:& };:"}
|
|
591
591
|
|
|
@@ -8313,6 +8313,89 @@ enforce_mutation_integrity() {
|
|
|
8313
8313
|
return 0
|
|
8314
8314
|
}
|
|
8315
8315
|
|
|
8316
|
+
# ============================================================================
|
|
8317
|
+
# Semantic Test-Authenticity Gate (P1-3): wire tests/detect-semantic-test-problems.sh
|
|
8318
|
+
# as an OPT-IN completion gate. The detector catches the harder class of fake
|
|
8319
|
+
# tests that the regex detectors (gates 5+6) miss: assertions that look real but
|
|
8320
|
+
# verify nothing because the asserted value never flows through code under test
|
|
8321
|
+
# (literal-via-variable echo HIGH, mock-return echo MED, deleted assertions MED).
|
|
8322
|
+
#
|
|
8323
|
+
# ADVISORY-FIRST POSTURE (no-deadlock contract): this helper is invoked ONLY when
|
|
8324
|
+
# LOKI_GATE_SEMANTIC_TESTS=true (the elif guard at the completion-promise arm
|
|
8325
|
+
# short-circuits when off, so there is zero runtime cost on the default path).
|
|
8326
|
+
# When on, it runs the detector with --block-high (clean exit-code contract:
|
|
8327
|
+
# rc 2 iff a CRITICAL/HIGH finding exists). We surface ALL severities to a
|
|
8328
|
+
# findings file (advisory) and return nonzero ONLY on rc 2. Every other exit --
|
|
8329
|
+
# rc 0 (clean), rc 124 (timeout), detector absent, no test files, malformed
|
|
8330
|
+
# output -- returns 0 (pass/fall-through), so the autonomous loop can NEVER
|
|
8331
|
+
# deadlock on a clean run. Mirrors enforce_mock_integrity's invocation
|
|
8332
|
+
# (cd TARGET_DIR + LOKI_SCAN_DIR=TARGET_DIR + timeout), swapping --strict for
|
|
8333
|
+
# --block-high and deciding on the rc-2 contract instead of grepping stdout.
|
|
8334
|
+
# ============================================================================
|
|
8335
|
+
enforce_semantic_integrity() {
|
|
8336
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
8337
|
+
local quality_dir="$loki_dir/quality"
|
|
8338
|
+
mkdir -p "$quality_dir"
|
|
8339
|
+
local findings_file="$quality_dir/semantic-findings.txt"
|
|
8340
|
+
local detector="$SCRIPT_DIR/../tests/detect-semantic-test-problems.sh"
|
|
8341
|
+
local gate_timeout="${LOKI_GATE_TIMEOUT:-300}"
|
|
8342
|
+
|
|
8343
|
+
if [ ! -f "$detector" ]; then
|
|
8344
|
+
log_info "Semantic test gate: detector not found, skipping (inconclusive)"
|
|
8345
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
8346
|
+
return 0
|
|
8347
|
+
fi
|
|
8348
|
+
|
|
8349
|
+
local output rc
|
|
8350
|
+
# --block-high exits 2 iff CRITICAL/HIGH present; 0 otherwise (clean wrapper).
|
|
8351
|
+
output=$(cd "${TARGET_DIR:-.}" && LOKI_SCAN_DIR="${TARGET_DIR:-.}" \
|
|
8352
|
+
timeout "$gate_timeout" bash "$detector" --block-high 2>&1)
|
|
8353
|
+
rc=$?
|
|
8354
|
+
|
|
8355
|
+
# timeout exit 124 -- inconclusive, never block on a hang (deny-filter)
|
|
8356
|
+
if [ "$rc" -eq 124 ]; then
|
|
8357
|
+
log_warn "Semantic test gate: detector timed out after ${gate_timeout}s -- inconclusive"
|
|
8358
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
8359
|
+
return 0
|
|
8360
|
+
fi
|
|
8361
|
+
|
|
8362
|
+
if [ "$rc" -eq 2 ]; then
|
|
8363
|
+
# rc 2 == one or more CRITICAL/HIGH findings. Persist per-finding text.
|
|
8364
|
+
{
|
|
8365
|
+
echo "# Semantic test-authenticity findings (CRITICAL/HIGH block this completion)"
|
|
8366
|
+
echo "$output" | grep -E '\[(CRITICAL|HIGH|MEDIUM|LOW)\]' || true
|
|
8367
|
+
} > "$findings_file"
|
|
8368
|
+
log_warn "Semantic test gate: CRITICAL/HIGH fake-test problems detected -- BLOCK"
|
|
8369
|
+
return 1
|
|
8370
|
+
fi
|
|
8371
|
+
|
|
8372
|
+
# rc 0 (and any other non-2, non-124 code, e.g. a malformed run) -> PASS.
|
|
8373
|
+
# Route any MED/LOW advisory findings to the injection file, else clear it.
|
|
8374
|
+
local med_low
|
|
8375
|
+
med_low=$(echo "$output" | grep -E '\[(MEDIUM|LOW)\]' || true)
|
|
8376
|
+
if [ -n "$med_low" ]; then
|
|
8377
|
+
{
|
|
8378
|
+
echo "# Semantic test advisory findings (MED/LOW, non-blocking)"
|
|
8379
|
+
echo "$med_low"
|
|
8380
|
+
} > "$findings_file"
|
|
8381
|
+
else
|
|
8382
|
+
rm -f "$findings_file" 2>/dev/null || true
|
|
8383
|
+
fi
|
|
8384
|
+
log_info "Semantic test gate: PASS"
|
|
8385
|
+
return 0
|
|
8386
|
+
}
|
|
8387
|
+
|
|
8388
|
+
# P1-3 wrapper that runs the semantic gate and returns its exact rc, mirroring
|
|
8389
|
+
# _evidence_gate_and_surface so the completion-promise elif arm reads cleanly
|
|
8390
|
+
# (`! _semantic_gate_and_surface`). Returns nonzero ONLY when enforce_semantic_integrity
|
|
8391
|
+
# saw an rc-2 (CRITICAL/HIGH) result; all deny-filter cases already collapse to 0
|
|
8392
|
+
# inside enforce_semantic_integrity, so this never blocks a clean run.
|
|
8393
|
+
_semantic_gate_and_surface() {
|
|
8394
|
+
local _rc=0
|
|
8395
|
+
enforce_semantic_integrity || _rc=$?
|
|
8396
|
+
return "$_rc"
|
|
8397
|
+
}
|
|
8398
|
+
|
|
8316
8399
|
# ============================================================================
|
|
8317
8400
|
# 3-Reviewer Parallel Code Review (v5.35.0)
|
|
8318
8401
|
# Specialist pool from skills/quality-gates.md with blind review
|
|
@@ -12248,6 +12331,23 @@ if d.get('blocked'):
|
|
|
12248
12331
|
gate_failure_context="${gate_failure_context}FIX THESE ISSUES BEFORE PROCEEDING WITH NEW WORK."
|
|
12249
12332
|
fi
|
|
12250
12333
|
|
|
12334
|
+
# P1-3: surface specific semantic test-authenticity findings (which fake test,
|
|
12335
|
+
# which line) when the opt-in gate (LOKI_GATE_SEMANTIC_TESTS) wrote them, so a
|
|
12336
|
+
# block converges: the agent gets the exact files/lines to fix rather than a
|
|
12337
|
+
# bare gate name. The file exists only when the gate ran AND found something
|
|
12338
|
+
# (cleared on clean), so this is zero-cost on the default path and when off.
|
|
12339
|
+
# Mirrors the static-analysis/test-results detail-surfacing above. Surfaced
|
|
12340
|
+
# whether the run blocked (CRIT/HIGH) or only advised (MED/LOW): both inform
|
|
12341
|
+
# the next iteration. Independent of gate-failures.txt presence (the
|
|
12342
|
+
# completion-promise arm does not append a gate token).
|
|
12343
|
+
if [ -f "${TARGET_DIR:-.}/.loki/quality/semantic-findings.txt" ]; then
|
|
12344
|
+
local sem_findings
|
|
12345
|
+
sem_findings=$(grep -E '\[(CRITICAL|HIGH|MEDIUM|LOW)\]' "${TARGET_DIR:-.}/.loki/quality/semantic-findings.txt" 2>/dev/null | head -20 || true)
|
|
12346
|
+
if [ -n "$sem_findings" ]; then
|
|
12347
|
+
gate_failure_context="${gate_failure_context} SEMANTIC TEST-AUTHENTICITY FINDINGS (fix the fake tests; an assertion must verify a value that flows through the code under test, not echo a literal back): ${sem_findings}"
|
|
12348
|
+
fi
|
|
12349
|
+
fi
|
|
12350
|
+
|
|
12251
12351
|
# P2-2: high-severity spec-assumption context. When DISCOVERY recorded any
|
|
12252
12352
|
# high-severity assumption (the spec was ambiguous in a high-impact place),
|
|
12253
12353
|
# surface it to the build agent so it implements with the gap in view (or
|
|
@@ -15347,6 +15447,20 @@ else:
|
|
|
15347
15447
|
log_warn "Completion claim rejected: assumption ledger gate found unresolved high-severity spec assumption(s)."
|
|
15348
15448
|
log_warn " Details under .loki/council/assumption-block.json ; opt out with LOKI_ASSUMPTION_GATE=0"
|
|
15349
15449
|
# Fall through; keep iterating until high-sev assumptions resolve.
|
|
15450
|
+
# P1-3: semantic test-authenticity gate (OPT-IN, default OFF). Catches
|
|
15451
|
+
# fake tests that look real but verify nothing (literal-via-variable
|
|
15452
|
+
# echo etc.) that the regex gates 5+6 miss. ADVISORY-FIRST: the arm is
|
|
15453
|
+
# guarded by LOKI_GATE_SEMANTIC_TESTS=true, so by default it never runs
|
|
15454
|
+
# (zero runtime cost, never blocks). When enabled it runs the detector
|
|
15455
|
+
# with --block-high and rejects the completion ONLY on a CRITICAL/HIGH
|
|
15456
|
+
# finding; clean / no-test-files / detector-absent / timeout / malformed
|
|
15457
|
+
# all collapse to a pass inside _semantic_gate_and_surface, so the
|
|
15458
|
+
# autonomous loop can never deadlock on a clean run. Mirrors the
|
|
15459
|
+
# evidence / held-out / assumption arms above.
|
|
15460
|
+
elif [ "$_completion_claimed" = 1 ] && [ "${LOKI_GATE_SEMANTIC_TESTS:-false}" = "true" ] && type _semantic_gate_and_surface &>/dev/null && ! _semantic_gate_and_surface; then
|
|
15461
|
+
log_warn "Completion claim rejected: semantic test-authenticity gate found CRITICAL/HIGH fake-test problem(s)."
|
|
15462
|
+
log_warn " Details under .loki/quality/semantic-findings.txt ; opt-in gate -- disable with LOKI_GATE_SEMANTIC_TESTS=false"
|
|
15463
|
+
# Fall through; keep iterating until the fake tests are fixed.
|
|
15350
15464
|
elif [ "$_completion_claimed" = 1 ]; then
|
|
15351
15465
|
echo ""
|
|
15352
15466
|
if [ -n "$COMPLETION_PROMISE" ]; then
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -3248,6 +3248,121 @@ async def get_audit_summary(days: int = 7):
|
|
|
3248
3248
|
return audit.get_audit_summary(days=days)
|
|
3249
3249
|
|
|
3250
3250
|
|
|
3251
|
+
# Continuous compliance surface (P3-11).
|
|
3252
|
+
#
|
|
3253
|
+
# Exposes the agent audit chain's compliance posture as an always-available
|
|
3254
|
+
# live endpoint. There is NO background scheduler in this surface (that is
|
|
3255
|
+
# infra, out of scope): the report is regenerated from the CURRENT audit
|
|
3256
|
+
# state on every request, so the endpoint is "continuous" in the sense that
|
|
3257
|
+
# it always reflects live state -- never a stale cached snapshot.
|
|
3258
|
+
#
|
|
3259
|
+
# The report is produced by the authoritative Node compliance engine
|
|
3260
|
+
# (src/audit/index.js, the single source of truth for SOC2/ISO/GDPR control
|
|
3261
|
+
# mappings) via its `report` CLI shim, so the Python surface never
|
|
3262
|
+
# reimplements (and never drifts from) the mapping logic. The chain it reads
|
|
3263
|
+
# is the JS AGENT chain at <project>/.loki/audit/audit.jsonl -- a different
|
|
3264
|
+
# chain from the Python dashboard chain that /api/enterprise/audit serves
|
|
3265
|
+
# (the two are reconciled by the cross-link verifier, not merged), so this
|
|
3266
|
+
# endpoint deliberately does NOT gate on audit.is_audit_enabled() (that flag
|
|
3267
|
+
# governs the Python chain). When the agent chain has no entries the report
|
|
3268
|
+
# is returned honestly with totalAuditEntries == 0; no fabricated pass.
|
|
3269
|
+
_COMPLIANCE_TYPES = ("soc2", "iso27001", "gdpr")
|
|
3270
|
+
|
|
3271
|
+
|
|
3272
|
+
@app.get("/api/compliance", dependencies=[Depends(auth.require_scope("audit"))])
|
|
3273
|
+
def get_compliance_status(report_type: str = Query("soc2", alias="type")):
|
|
3274
|
+
"""Live compliance status for the active project's agent audit chain.
|
|
3275
|
+
|
|
3276
|
+
Auth/tenant scoping: requires the `audit` scope (same gate as the
|
|
3277
|
+
/api/enterprise/audit family). The data is filesystem state scoped to
|
|
3278
|
+
the active project via _get_loki_dir(), exactly like the other
|
|
3279
|
+
.loki-backed read endpoints; there is no DB tenant_id on a JSONL file
|
|
3280
|
+
to enforce against.
|
|
3281
|
+
|
|
3282
|
+
Query: ?type=soc2|iso27001|gdpr (default soc2).
|
|
3283
|
+
|
|
3284
|
+
Returns the compliance report JSON regenerated from CURRENT audit
|
|
3285
|
+
state on every call. If no audit data has been recorded the report is
|
|
3286
|
+
honestly empty (totalAuditEntries == 0), not a fabricated compliant
|
|
3287
|
+
verdict. If the Node engine is unavailable, returns an honest
|
|
3288
|
+
available:false payload (HTTP 200) rather than masquerading as "no
|
|
3289
|
+
compliance".
|
|
3290
|
+
"""
|
|
3291
|
+
if not _read_limiter.check("compliance"):
|
|
3292
|
+
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
3293
|
+
if report_type not in _COMPLIANCE_TYPES:
|
|
3294
|
+
raise HTTPException(
|
|
3295
|
+
status_code=400,
|
|
3296
|
+
detail=f"Invalid type: {report_type}. Must be one of {list(_COMPLIANCE_TYPES)}",
|
|
3297
|
+
)
|
|
3298
|
+
|
|
3299
|
+
import shutil
|
|
3300
|
+
|
|
3301
|
+
# The agent audit chain lives under <project>/.loki/audit; _get_loki_dir()
|
|
3302
|
+
# returns the .loki dir, so the project root is its parent.
|
|
3303
|
+
project_dir = str(_get_loki_dir().parent.resolve())
|
|
3304
|
+
repo_root = _Path(__file__).resolve().parent.parent
|
|
3305
|
+
index_js = repo_root / "src" / "audit" / "index.js"
|
|
3306
|
+
|
|
3307
|
+
node_bin = shutil.which("node")
|
|
3308
|
+
if node_bin is None or not index_js.exists():
|
|
3309
|
+
return {
|
|
3310
|
+
"available": False,
|
|
3311
|
+
"reason": (
|
|
3312
|
+
"Node runtime not found"
|
|
3313
|
+
if node_bin is None
|
|
3314
|
+
else f"compliance engine not found at {index_js}"
|
|
3315
|
+
),
|
|
3316
|
+
"reportType": report_type,
|
|
3317
|
+
"projectDir": project_dir,
|
|
3318
|
+
"report": None,
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
try:
|
|
3322
|
+
proc = subprocess.run(
|
|
3323
|
+
[node_bin, str(index_js), "report", report_type, project_dir],
|
|
3324
|
+
capture_output=True,
|
|
3325
|
+
text=True,
|
|
3326
|
+
timeout=30,
|
|
3327
|
+
check=False,
|
|
3328
|
+
)
|
|
3329
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
3330
|
+
return {
|
|
3331
|
+
"available": False,
|
|
3332
|
+
"reason": f"compliance engine invocation failed: {exc}",
|
|
3333
|
+
"reportType": report_type,
|
|
3334
|
+
"projectDir": project_dir,
|
|
3335
|
+
"report": None,
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
if proc.returncode != 0:
|
|
3339
|
+
return {
|
|
3340
|
+
"available": False,
|
|
3341
|
+
"reason": (proc.stderr or "compliance engine returned non-zero").strip()[:500],
|
|
3342
|
+
"reportType": report_type,
|
|
3343
|
+
"projectDir": project_dir,
|
|
3344
|
+
"report": None,
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
try:
|
|
3348
|
+
report = json.loads(proc.stdout.strip())
|
|
3349
|
+
except json.JSONDecodeError:
|
|
3350
|
+
return {
|
|
3351
|
+
"available": False,
|
|
3352
|
+
"reason": "compliance engine produced non-JSON output",
|
|
3353
|
+
"reportType": report_type,
|
|
3354
|
+
"projectDir": project_dir,
|
|
3355
|
+
"report": None,
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
return {
|
|
3359
|
+
"available": True,
|
|
3360
|
+
"reportType": report_type,
|
|
3361
|
+
"projectDir": project_dir,
|
|
3362
|
+
"report": report,
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
|
|
3251
3366
|
# =============================================================================
|
|
3252
3367
|
# File-based Session Endpoints (reads from .loki/ flat files)
|
|
3253
3368
|
# =============================================================================
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.53.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -396,7 +396,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
396
396
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
397
397
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
398
398
|
-v $(pwd):/workspace -w /workspace \
|
|
399
|
-
asklokesh/loki-mode:7.
|
|
399
|
+
asklokesh/loki-mode:7.53.0 start ./my-spec.md
|
|
400
400
|
```
|
|
401
401
|
|
|
402
402
|
##### docker compose + .env (no host install)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var r6=Object.defineProperty;var t6=($)=>$;function i6($,Q){this[$]=t6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)r6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:i6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var D1={};h(D1,{lokiDir:()=>P,homeLokiDir:()=>n$,findRepoRootForVersion:()=>o$,REPO_ROOT:()=>g});import{resolve as n,dirname as d$}from"path";import{fileURLToPath as e6}from"url";import{existsSync as P$}from"fs";import{homedir as $Q}from"os";function QQ(){let $=S1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=d$($);if(Z===$)break;$=Z}return n(S1,"..","..","..")}function o$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=d$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function n$(){return n($Q(),".loki")}var S1,g;var b=L(()=>{S1=d$(e6(import.meta.url));g=QQ()});import{readFileSync as ZQ}from"fs";import{resolve as zQ,dirname as XQ}from"path";import{fileURLToPath as KQ}from"url";function j$(){if($$!==null)return $$;let $="7.
|
|
2
|
+
var r6=Object.defineProperty;var t6=($)=>$;function i6($,Q){this[$]=t6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)r6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:i6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var D1={};h(D1,{lokiDir:()=>P,homeLokiDir:()=>n$,findRepoRootForVersion:()=>o$,REPO_ROOT:()=>g});import{resolve as n,dirname as d$}from"path";import{fileURLToPath as e6}from"url";import{existsSync as P$}from"fs";import{homedir as $Q}from"os";function QQ(){let $=S1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=d$($);if(Z===$)break;$=Z}return n(S1,"..","..","..")}function o$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=d$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function n$(){return n($Q(),".loki")}var S1,g;var b=L(()=>{S1=d$(e6(import.meta.url));g=QQ()});import{readFileSync as ZQ}from"fs";import{resolve as zQ,dirname as XQ}from"path";import{fileURLToPath as KQ}from"url";function j$(){if($$!==null)return $$;let $="7.53.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=XQ(KQ(import.meta.url)),Z=o$(Q);$$=ZQ(zQ(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var a$=L(()=>{b()});var b1={};h(b1,{runOrThrow:()=>qQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>s$});async function k($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function qQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new s$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=VQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function VQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var s$;var d=L(()=>{s$=class s$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return JQ?"":$}var JQ,T,S,_,wZ,I,R,y,V;var c=L(()=>{JQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),_=a("\x1B[1;33m"),wZ=a("\x1B[0;34m"),I=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),V=a("\x1B[0m")});import{existsSync as wQ}from"fs";async function Q$(){if(G$!==void 0)return G$;let $="/opt/homebrew/bin/python3.12";if(wQ($))return G$=$,$;let Q=await f("python3.12");if(Q)return G$=Q,Q;let Z=await f("python3");return G$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var G$;var q$=L(()=>{d()});var e1={};h(e1,{runStatus:()=>uQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as C,basename as DQ}from"path";import{homedir as CQ}from"os";function n1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*k$/Q);if(X>k$)X=k$;let q=k$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${R}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function hQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}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)
|
|
@@ -790,4 +790,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
790
790
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
791
791
|
`),process.stderr.write(s6),2}}l1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var KZ=await XZ(Bun.argv.slice(2));process.exit(KZ);
|
|
792
792
|
|
|
793
|
-
//# debugId=
|
|
793
|
+
//# debugId=3BF6CF9B99A2BD7E64756E2164756E21
|
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": "7.
|
|
4
|
+
"version": "7.53.0",
|
|
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": "7.
|
|
5
|
+
"version": "7.53.0",
|
|
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",
|
package/skills/quality-gates.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
**Never ship code without passing all quality gates.**
|
|
4
4
|
|
|
5
|
-
## The 8
|
|
5
|
+
## The Quality Gates (8 default-on + 1 opt-in)
|
|
6
6
|
|
|
7
|
-
Every gate below is wired into the orchestration loop (`autonomy/run.sh`)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Every gate below is wired into the orchestration loop (`autonomy/run.sh`). The 8
|
|
8
|
+
numbered gates are default-on and block completion when they fail; the opt-in
|
|
9
|
+
gate (marked below) is default-OFF and runs only when its flag is set. The table
|
|
10
|
+
lists exactly what each gate detects, what it does NOT detect (so you never
|
|
11
|
+
over-trust a green gate), its opt-out flag, and its blocking behavior. Transcribe
|
|
12
|
+
this list verbatim; do not recompute it.
|
|
11
13
|
|
|
12
14
|
| # | Gate | Detects | Does NOT detect | Blocking | Opt-out flag |
|
|
13
15
|
|---|------|---------|-----------------|----------|--------------|
|
|
@@ -19,6 +21,7 @@ and its blocking behavior. Transcribe this list verbatim; do not recompute it.
|
|
|
19
21
|
| 6 | Test Mutation Detector | Assertion-value churn alongside implementation changes (test-fitting), low assertion density (`tests/detect-test-mutations.sh`); HIGH blocks | Logically-correct-but-weak assertions | Yes (HIGH blocks) | `LOKI_GATE_MUTATION=false` |
|
|
20
22
|
| 7 | Documentation Coverage | README presence, docs freshness within 10 commits, API docs for exported symbols in packages | Whether the docs are accurate or useful | Yes | `LOKI_GATE_DOC_COVERAGE=false` |
|
|
21
23
|
| 8 | Magic Modules Debate | Spec-vs-implementation debate findings on generated Magic Modules; BLOCK-severity findings block | Issues outside the Magic Modules debate scope | Yes (BLOCK severity) | `LOKI_GATE_MAGIC_DEBATE=false` |
|
|
24
|
+
| 9 (opt-in, default OFF) | Semantic Test-Authenticity | Fake tests that look real but verify nothing (literal-via-variable echo, mock-return echo, deleted assertions) that gates 5+6 miss (`tests/detect-semantic-test-problems.sh --block-high`); CRITICAL/HIGH block | Deep dataflow, legitimate computed-literal assertions, Python/shell tests (JS/TS only); MED/LOW are advisory | Only when enabled, and only on CRITICAL/HIGH; runs solely on a completion claim | Opt-IN: `LOKI_GATE_SEMANTIC_TESTS=true` to enable (default off = not invoked, never blocks) |
|
|
22
25
|
|
|
23
26
|
**Severity-based blocking** ties the review gates together: any Critical or High
|
|
24
27
|
finding blocks completion. Medium, Low, and cosmetic findings are advisory and
|
package/src/audit/index.js
CHANGED
|
@@ -83,6 +83,84 @@ function exportReport(type, opts) {
|
|
|
83
83
|
return compliance.exportReportJson(report);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Generate a compliance report as a plain object, with the agent-chain
|
|
88
|
+
* tamper-evidence verdict folded in.
|
|
89
|
+
*
|
|
90
|
+
* This is the object form intended for surfaces (e.g. the dashboard
|
|
91
|
+
* /api/compliance endpoint) that need the report as data rather than a
|
|
92
|
+
* pre-serialized string. It always reflects the REAL audit chain:
|
|
93
|
+
*
|
|
94
|
+
* - The report body is generated from the live audit entries
|
|
95
|
+
* (`_log.readEntries()`), never fabricated.
|
|
96
|
+
* - `chainIntegrity` is populated from `verifyChain()` so the report
|
|
97
|
+
* carries the true tamper-evidence state of the underlying chain.
|
|
98
|
+
* For the SOC2 report this fills the `chainIntegrity: null` slot the
|
|
99
|
+
* generator leaves for the caller; for the other report types it is
|
|
100
|
+
* attached under the same key for a uniform surface contract.
|
|
101
|
+
*
|
|
102
|
+
* When the chain has no entries the report is still returned honestly
|
|
103
|
+
* with `totalAuditEntries: 0` (an empty-but-valid report), never a
|
|
104
|
+
* fabricated "compliant" verdict.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} type - 'soc2', 'iso27001', or 'gdpr'
|
|
107
|
+
* @param {object} [opts] - Report options (projectName, period, etc.)
|
|
108
|
+
* @returns {object} The compliance report object with chainIntegrity set.
|
|
109
|
+
*/
|
|
110
|
+
function getReport(type, opts) {
|
|
111
|
+
if (!_initialized) init();
|
|
112
|
+
var report = generateReport(type, opts);
|
|
113
|
+
// Fold the real tamper-evidence verdict into the report. Do not let a
|
|
114
|
+
// verification error fabricate a pass: capture it honestly instead.
|
|
115
|
+
try {
|
|
116
|
+
report.chainIntegrity = _log.verifyChain();
|
|
117
|
+
} catch (e) {
|
|
118
|
+
report.chainIntegrity = {
|
|
119
|
+
valid: false,
|
|
120
|
+
entries: report.totalAuditEntries || 0,
|
|
121
|
+
brokenAt: null,
|
|
122
|
+
error: 'chain verification failed: ' + String((e && e.message) || e),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return report;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* CLI shim so a non-Node surface (e.g. the Python dashboard) can fetch a
|
|
130
|
+
* compliance report for a given project directory as JSON on stdout.
|
|
131
|
+
*
|
|
132
|
+
* This mirrors the inverse of dashboard/audit.py's `_unified_cli()`
|
|
133
|
+
* (which lets the Node-side unified verifier read the Python chain).
|
|
134
|
+
*
|
|
135
|
+
* Invoked as:
|
|
136
|
+
* node src/audit/index.js report <type> <projectDir>
|
|
137
|
+
*
|
|
138
|
+
* <type> is one of soc2 | iso27001 | gdpr. <projectDir> is the project
|
|
139
|
+
* root whose .loki/audit/audit.jsonl chain is read. Prints a single JSON
|
|
140
|
+
* object to stdout. Returns exit 0 on success, 2 on usage error.
|
|
141
|
+
*
|
|
142
|
+
* The report is generated from the REAL chain; an absent/empty chain
|
|
143
|
+
* yields an honest empty report (totalAuditEntries: 0), not a fake pass.
|
|
144
|
+
*/
|
|
145
|
+
function _cli(argv) {
|
|
146
|
+
var args = argv || [];
|
|
147
|
+
var VALID_TYPES = { soc2: true, iso27001: true, gdpr: true };
|
|
148
|
+
if (args.length < 2 || args[0] !== 'report' || !VALID_TYPES[args[1]]) {
|
|
149
|
+
process.stdout.write(JSON.stringify({
|
|
150
|
+
error: 'usage: index.js report {soc2|iso27001|gdpr} <projectDir>',
|
|
151
|
+
}) + '\n');
|
|
152
|
+
return 2;
|
|
153
|
+
}
|
|
154
|
+
var type = args[1];
|
|
155
|
+
var projectDir = args[2] || process.cwd();
|
|
156
|
+
destroy();
|
|
157
|
+
init(projectDir);
|
|
158
|
+
var report = getReport(type);
|
|
159
|
+
destroy();
|
|
160
|
+
process.stdout.write(JSON.stringify(report) + '\n');
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
86
164
|
/**
|
|
87
165
|
* Check if a provider is allowed by data residency policy.
|
|
88
166
|
*/
|
|
@@ -167,6 +245,7 @@ module.exports = {
|
|
|
167
245
|
verifyChain: verifyChain,
|
|
168
246
|
generateReport: generateReport,
|
|
169
247
|
exportReport: exportReport,
|
|
248
|
+
getReport: getReport,
|
|
170
249
|
checkProvider: checkProvider,
|
|
171
250
|
isAirGapped: isAirGapped,
|
|
172
251
|
readEntries: readEntries,
|
|
@@ -177,3 +256,8 @@ module.exports = {
|
|
|
177
256
|
verifyUnified: verifyUnified,
|
|
178
257
|
writeWitness: writeWitness,
|
|
179
258
|
};
|
|
259
|
+
|
|
260
|
+
// CLI entry point: `node src/audit/index.js report <type> <projectDir>`.
|
|
261
|
+
if (require.main === module) {
|
|
262
|
+
process.exit(_cli(process.argv.slice(2)));
|
|
263
|
+
}
|