loki-mode 9.46.0 → 9.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/issue-to-pr/action.yml +105 -0
- package/.github/workflows/loki-issue-to-pr.yml +87 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +63 -6
- package/autonomy/run.sh +58 -4
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +3 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +29 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
name: 'Loki Issue to PR'
|
|
2
|
+
description: 'Hand Loki Mode a GitHub issue; it opens a pull request with a verifiable evidence receipt.'
|
|
3
|
+
author: 'asklokesh'
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: 'git-pull-request'
|
|
7
|
+
color: 'purple'
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
issue:
|
|
11
|
+
description: 'Issue reference. Defaults to the issue that triggered this workflow.'
|
|
12
|
+
required: false
|
|
13
|
+
default: ''
|
|
14
|
+
provider:
|
|
15
|
+
description: 'AI provider: claude, codex, cline, aider, or opencode'
|
|
16
|
+
required: false
|
|
17
|
+
default: 'claude'
|
|
18
|
+
budget_limit:
|
|
19
|
+
description: 'Max spend in USD before the run pauses. Breaching PAUSES; it never discards work.'
|
|
20
|
+
required: false
|
|
21
|
+
default: '10.00'
|
|
22
|
+
max_iterations:
|
|
23
|
+
description: 'Hard ceiling on RARV iterations.'
|
|
24
|
+
required: false
|
|
25
|
+
default: '20'
|
|
26
|
+
version:
|
|
27
|
+
description: 'loki-mode version to install. Pin this for reproducible runs.'
|
|
28
|
+
required: false
|
|
29
|
+
default: 'latest'
|
|
30
|
+
|
|
31
|
+
outputs:
|
|
32
|
+
pr_url:
|
|
33
|
+
description: 'URL of the pull request Loki opened, empty if none was opened.'
|
|
34
|
+
value: ${{ steps.run.outputs.pr_url }}
|
|
35
|
+
|
|
36
|
+
runs:
|
|
37
|
+
using: 'composite'
|
|
38
|
+
steps:
|
|
39
|
+
- name: Install Loki Mode
|
|
40
|
+
shell: bash
|
|
41
|
+
run: npm install -g loki-mode@${{ inputs.version }}
|
|
42
|
+
|
|
43
|
+
- name: Resolve the issue reference
|
|
44
|
+
id: ref
|
|
45
|
+
shell: bash
|
|
46
|
+
run: |
|
|
47
|
+
# Prefer the explicit input; otherwise use the triggering issue. Failing
|
|
48
|
+
# CLOSED here is deliberate: a run with no issue would otherwise start
|
|
49
|
+
# from the repository's own state and produce an unrelated PR.
|
|
50
|
+
REF="${{ inputs.issue }}"
|
|
51
|
+
if [ -z "$REF" ]; then
|
|
52
|
+
NUM="${{ github.event.issue.number }}"
|
|
53
|
+
if [ -z "$NUM" ]; then
|
|
54
|
+
echo "::error::No issue to work on. Pass the 'issue' input, or trigger this action from an issue event."
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
REF="${{ github.repository }}#${NUM}"
|
|
58
|
+
fi
|
|
59
|
+
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
|
60
|
+
echo "Resolved issue reference: $REF"
|
|
61
|
+
|
|
62
|
+
- name: Resolve the issue to a pull request
|
|
63
|
+
id: run
|
|
64
|
+
shell: bash
|
|
65
|
+
env:
|
|
66
|
+
# gh is what opens the PR. Without a token the run still builds and the
|
|
67
|
+
# PR step no-ops, so this is not fatal, but it IS the common misconfig.
|
|
68
|
+
GH_TOKEN: ${{ github.token }}
|
|
69
|
+
LOKI_PROVIDER: ${{ inputs.provider }}
|
|
70
|
+
LOKI_BUDGET_LIMIT: ${{ inputs.budget_limit }}
|
|
71
|
+
LOKI_MAX_ITERATIONS: ${{ inputs.max_iterations }}
|
|
72
|
+
# The PR is the deliverable, so the PR path is on. Default-on since
|
|
73
|
+
# v9.43.0; set explicitly here so the action does not depend on the
|
|
74
|
+
# installed version's default.
|
|
75
|
+
LOKI_DELEGATE_PR: '1'
|
|
76
|
+
run: |
|
|
77
|
+
set -uo pipefail
|
|
78
|
+
|
|
79
|
+
# git identity: gh pr create needs commits to have an author.
|
|
80
|
+
git config user.name "${GITHUB_ACTOR:-loki-mode}"
|
|
81
|
+
git config user.email "${GITHUB_ACTOR:-loki-mode}@users.noreply.github.com"
|
|
82
|
+
|
|
83
|
+
loki start "${{ steps.ref.outputs.ref }}" || RC=$?
|
|
84
|
+
RC="${RC:-0}"
|
|
85
|
+
|
|
86
|
+
# Report the PR if one was opened. Read it from the state Loki writes
|
|
87
|
+
# rather than re-deriving it, so this reports what ACTUALLY happened.
|
|
88
|
+
PR_URL=""
|
|
89
|
+
if [ -f .loki/state/pr-url.txt ]; then
|
|
90
|
+
PR_URL="$(cat .loki/state/pr-url.txt 2>/dev/null || true)"
|
|
91
|
+
fi
|
|
92
|
+
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
|
|
93
|
+
|
|
94
|
+
if [ -n "$PR_URL" ]; then
|
|
95
|
+
echo "Pull request: $PR_URL"
|
|
96
|
+
else
|
|
97
|
+
echo "::warning::No pull request was opened. Check that gh is authenticated, the branch is not a default branch, and the run produced changes."
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Surface the receipt path so a reviewer can check the evidence.
|
|
101
|
+
if [ -d .loki/proofs ]; then
|
|
102
|
+
echo "Evidence receipts under .loki/proofs/"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
exit "$RC"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Hand Loki an issue, get a pull request.
|
|
2
|
+
#
|
|
3
|
+
# Label an issue `loki` (or comment `/loki`) and this opens a PR with an
|
|
4
|
+
# evidence receipt. Copy this file into .github/workflows/ of any repository.
|
|
5
|
+
#
|
|
6
|
+
# REQUIRED: set ANTHROPIC_API_KEY in repository secrets. Without it the run
|
|
7
|
+
# cannot call a model and fails fast rather than burning minutes.
|
|
8
|
+
name: Loki - Issue to PR
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
issues:
|
|
12
|
+
types: [labeled]
|
|
13
|
+
issue_comment:
|
|
14
|
+
types: [created]
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
inputs:
|
|
17
|
+
issue:
|
|
18
|
+
description: 'Issue number to resolve'
|
|
19
|
+
required: true
|
|
20
|
+
|
|
21
|
+
# Serialize per issue. Two agents racing on one issue would open two PRs from
|
|
22
|
+
# two branches and neither would see the other's work.
|
|
23
|
+
concurrency:
|
|
24
|
+
group: loki-issue-${{ github.event.issue.number || inputs.issue }}
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: write # push the agent branch
|
|
29
|
+
pull-requests: write # open the PR
|
|
30
|
+
issues: read
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
resolve:
|
|
34
|
+
# Gate on an explicit signal so the agent never fires on unrelated activity.
|
|
35
|
+
if: >-
|
|
36
|
+
(github.event_name == 'issues' && github.event.label.name == 'loki') ||
|
|
37
|
+
(github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/loki')) ||
|
|
38
|
+
github.event_name == 'workflow_dispatch'
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 45
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
fetch-depth: 0 # the agent branches and diffs; a shallow clone breaks both
|
|
45
|
+
|
|
46
|
+
- uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: '20'
|
|
49
|
+
|
|
50
|
+
- name: Fail fast without a model key
|
|
51
|
+
env:
|
|
52
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
53
|
+
run: |
|
|
54
|
+
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
|
|
55
|
+
echo "::error::ANTHROPIC_API_KEY is not set in repository secrets. Add it under Settings > Secrets and variables > Actions."
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
- name: Resolve the issue
|
|
60
|
+
id: loki
|
|
61
|
+
# Pinned to a TAG, not @main. A moving ref would hand every user
|
|
62
|
+
# whatever main holds at the moment their issue is labeled, including a
|
|
63
|
+
# half-landed release. Bump this line deliberately.
|
|
64
|
+
uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.48.1
|
|
65
|
+
env:
|
|
66
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
67
|
+
with:
|
|
68
|
+
issue: ${{ github.event.issue.number || inputs.issue }}
|
|
69
|
+
budget_limit: '10.00'
|
|
70
|
+
|
|
71
|
+
- name: Report back on the issue
|
|
72
|
+
if: always() && steps.loki.outputs.pr_url != ''
|
|
73
|
+
env:
|
|
74
|
+
GH_TOKEN: ${{ github.token }}
|
|
75
|
+
PR_URL: ${{ steps.loki.outputs.pr_url }}
|
|
76
|
+
ISSUE: ${{ github.event.issue.number || inputs.issue }}
|
|
77
|
+
run: |
|
|
78
|
+
# Build the body in a file: a blank line inside an inline YAML string
|
|
79
|
+
# terminates the block scalar and breaks the workflow parse.
|
|
80
|
+
{
|
|
81
|
+
echo "Loki opened a pull request: $PR_URL"
|
|
82
|
+
echo ""
|
|
83
|
+
echo "The PR body carries an evidence receipt stating what was verified,"
|
|
84
|
+
echo "what was skipped, and what is degraded. Check it rather than taking"
|
|
85
|
+
echo "the result on trust."
|
|
86
|
+
} > /tmp/loki-issue-comment.md
|
|
87
|
+
gh issue comment "$ISSUE" --body-file /tmp/loki-issue-comment.md || true
|
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 v9.
|
|
6
|
+
# Loki Mode v9.48.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v9.
|
|
473
|
+
**v9.48.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.48.1
|
package/autonomy/loki
CHANGED
|
@@ -4502,10 +4502,27 @@ cmd_steer() {
|
|
|
4502
4502
|
# duplicating them here would contradict it.
|
|
4503
4503
|
#
|
|
4504
4504
|
# Read-only: reads state, writes nothing, spends nothing.
|
|
4505
|
+
# Print the "you can pick this up" hint for a run that STOPPED WITHOUT A VERDICT.
|
|
4506
|
+
#
|
|
4507
|
+
# THE DEFECT this widening fixes, reproduced: cmd_next maps
|
|
4508
|
+
# max_iterations_reached and budget_exceeded to `loki resume`, but this helper
|
|
4509
|
+
# accepted ONLY "interrupted", so cmd_resume fell through to "No session to
|
|
4510
|
+
# resume. Start a session with: loki start" -- rc=0. The two commands whose
|
|
4511
|
+
# whole job is "do the right next thing" contradicted each other on every run
|
|
4512
|
+
# that hit a limit.
|
|
4513
|
+
#
|
|
4514
|
+
# WHAT MUST NOT BE ADDED HERE: council_approved, council_force_approved and
|
|
4515
|
+
# completion_promise_fulfilled. Those carry a VERDICT and route to `loki ship`.
|
|
4516
|
+
# Accepting them would send an approved build back into the loop.
|
|
4517
|
+
# failed / policy_blocked / force_stopped route to `loki why` and stay out too.
|
|
4518
|
+
# The set below is exactly "stopped on a limit, no verdict reached".
|
|
4505
4519
|
_loki_print_interrupted_resume_hint() {
|
|
4506
4520
|
local status
|
|
4507
4521
|
status="$(_loki_resolve_run_status 2>/dev/null)" || return 1
|
|
4508
|
-
|
|
4522
|
+
case "$status" in
|
|
4523
|
+
interrupted|max_iterations_reached|budget_exceeded) ;;
|
|
4524
|
+
*) return 1 ;;
|
|
4525
|
+
esac
|
|
4509
4526
|
|
|
4510
4527
|
local state_file="$LOKI_DIR/autonomy-state.json"
|
|
4511
4528
|
if [ -n "${LOKI_SESSION_ID:-}" ] && [ -f "$LOKI_DIR/sessions/${LOKI_SESSION_ID}/autonomy-state.json" ]; then
|
|
@@ -4542,12 +4559,52 @@ print('\t'.join([
|
|
|
4542
4559
|
resume_cmd="loki start \"$prd_path\""
|
|
4543
4560
|
fi
|
|
4544
4561
|
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4562
|
+
# A capped run needs DIFFERENT advice than an interrupted one: re-running it
|
|
4563
|
+
# unchanged hits the very same limit on the next iteration. cmd_next's
|
|
4564
|
+
# action_text already says to raise the cap first; saying less here would
|
|
4565
|
+
# move the contradiction rather than close it.
|
|
4566
|
+
case "$status" in
|
|
4567
|
+
max_iterations_reached)
|
|
4568
|
+
# Deliberately does NOT print the iteration it reached.
|
|
4569
|
+
# `load_state` resets ITERATION_COUNT=0 for this terminal status
|
|
4570
|
+
# (autonomy/run.sh, the failure-terminals case arm), so a fresh
|
|
4571
|
+
# `loki start` is a NEW session from iteration 0. Advertising the
|
|
4572
|
+
# old number would promise a continuation the runtime will not
|
|
4573
|
+
# honour -- the exact false claim tests/test-resume-discoverability.sh
|
|
4574
|
+
# guards, and it caught this in CI.
|
|
4575
|
+
echo -e "${YELLOW}Iteration limit reached.${NC} (last activity: ${last_run})."
|
|
4576
|
+
echo "The committed work is intact, but this starts a NEW session from"
|
|
4577
|
+
echo "iteration 0 -- it does not continue the old count."
|
|
4578
|
+
echo "Raise the cap (or narrow the spec), then run:"
|
|
4579
|
+
echo ""
|
|
4580
|
+
echo -e " ${BOLD}LOKI_MAX_ITERATIONS=<higher> ${resume_cmd}${NC}"
|
|
4581
|
+
;;
|
|
4582
|
+
budget_exceeded)
|
|
4583
|
+
# Same reasoning as max_iterations_reached: this terminal status is
|
|
4584
|
+
# in load_state's reset list, so the prior iteration count is gone.
|
|
4585
|
+
echo -e "${YELLOW}Budget limit reached.${NC} (last activity: ${last_run})."
|
|
4586
|
+
echo "The committed work is intact, but this starts a NEW session from"
|
|
4587
|
+
echo "iteration 0 -- it does not continue the old count."
|
|
4588
|
+
echo "Raise the budget (or narrow the spec), then run:"
|
|
4589
|
+
echo ""
|
|
4590
|
+
echo -e " ${BOLD}LOKI_BUDGET_LIMIT=<higher> ${resume_cmd}${NC}"
|
|
4591
|
+
;;
|
|
4592
|
+
*)
|
|
4593
|
+
echo -e "${YELLOW}Interrupted run found.${NC} Stopped at iteration ${BOLD}${iteration}${NC} (last activity: ${last_run})."
|
|
4594
|
+
echo "Saved progress is intact. Resume it with:"
|
|
4595
|
+
echo ""
|
|
4596
|
+
echo -e " ${BOLD}${resume_cmd}${NC}"
|
|
4597
|
+
;;
|
|
4598
|
+
esac
|
|
4549
4599
|
echo ""
|
|
4550
|
-
|
|
4600
|
+
# Only an INTERRUPTED run actually resumes its iteration count. The capped
|
|
4601
|
+
# terminals above are reset to 0 by load_state, so the pick-up sentence must
|
|
4602
|
+
# not be printed for them.
|
|
4603
|
+
if [ "$status" = "interrupted" ]; then
|
|
4604
|
+
echo -e "${DIM}It picks up from iteration ${iteration}; verification re-runs, so nothing inherits a stale PASS.${NC}"
|
|
4605
|
+
else
|
|
4606
|
+
echo -e "${DIM}Verification re-runs from scratch, so nothing inherits a stale PASS.${NC}"
|
|
4607
|
+
fi
|
|
4551
4608
|
return 0
|
|
4552
4609
|
}
|
|
4553
4610
|
|
package/autonomy/run.sh
CHANGED
|
@@ -5223,6 +5223,24 @@ except Exception:
|
|
|
5223
5223
|
# is already true we DEFER to that path and do nothing here, so a user who set
|
|
5224
5224
|
# both knobs never gets a double PR.
|
|
5225
5225
|
#===============================================================================
|
|
5226
|
+
# Write the PR url where an OUT-OF-PROCESS caller can read it.
|
|
5227
|
+
#
|
|
5228
|
+
# _LOKI_DELEGATE_PR_URL is exported, which reaches children but NOT a sibling
|
|
5229
|
+
# step. A GitHub composite action runs `loki start` in one step and reports the
|
|
5230
|
+
# result in the next, so an exported variable is invisible to it and the action
|
|
5231
|
+
# would have to re-derive the url and could get it wrong. Persisting it means a
|
|
5232
|
+
# caller reports what ACTUALLY happened.
|
|
5233
|
+
#
|
|
5234
|
+
# Best-effort by construction: a failure here must never affect a run whose PR
|
|
5235
|
+
# was already opened successfully.
|
|
5236
|
+
_loki_persist_pr_url() {
|
|
5237
|
+
local _u="${1:-}"
|
|
5238
|
+
[ -n "$_u" ] || return 0
|
|
5239
|
+
mkdir -p ".loki/state" 2>/dev/null || return 0
|
|
5240
|
+
printf '%s\n' "$_u" > ".loki/state/pr-url.txt" 2>/dev/null || true
|
|
5241
|
+
return 0
|
|
5242
|
+
}
|
|
5243
|
+
|
|
5226
5244
|
on_run_complete() {
|
|
5227
5245
|
# DEFAULT ON as of v9.43.0.
|
|
5228
5246
|
#
|
|
@@ -5290,6 +5308,7 @@ on_run_complete() {
|
|
|
5290
5308
|
if [ -n "$existing_pr" ]; then
|
|
5291
5309
|
_LOKI_DELEGATE_PR_URL="$existing_pr"
|
|
5292
5310
|
export _LOKI_DELEGATE_PR_URL
|
|
5311
|
+
_loki_persist_pr_url "$existing_pr"
|
|
5293
5312
|
log_info "LOKI_DELEGATE_PR=1: PR already exists for branch '$branch': $existing_pr (skipping create)."
|
|
5294
5313
|
return 0
|
|
5295
5314
|
fi
|
|
@@ -5321,6 +5340,7 @@ ${_del_receipt}"
|
|
|
5321
5340
|
# Export so build_completion_summary folds the url into the summary.
|
|
5322
5341
|
_LOKI_DELEGATE_PR_URL="$pr_url"
|
|
5323
5342
|
export _LOKI_DELEGATE_PR_URL
|
|
5343
|
+
_loki_persist_pr_url "$pr_url"
|
|
5324
5344
|
log_info "Pull request opened: $pr_url"
|
|
5325
5345
|
else
|
|
5326
5346
|
log_warn "LOKI_DELEGATE_PR=1: gh pr create did not return a URL (a PR may already exist for this branch)."
|
|
@@ -19440,6 +19460,7 @@ load_queue_tasks() {
|
|
|
19440
19460
|
# Handles both formats, includes description, acceptance criteria, and user stories
|
|
19441
19461
|
local extract_script='
|
|
19442
19462
|
import json
|
|
19463
|
+
import os
|
|
19443
19464
|
import sys
|
|
19444
19465
|
|
|
19445
19466
|
def extract_tasks(filepath, prefix):
|
|
@@ -19451,7 +19472,23 @@ def extract_tasks(filepath, prefix):
|
|
|
19451
19472
|
return ""
|
|
19452
19473
|
|
|
19453
19474
|
results = []
|
|
19454
|
-
|
|
19475
|
+
# BOUND BY CHARACTERS, NOT BY AN ARBITRARY TASK COUNT.
|
|
19476
|
+
#
|
|
19477
|
+
# This was `tasks[:3]`, applied SEPARATELY to in-progress.json and
|
|
19478
|
+
# pending.json. A release doc decomposed into 5 tasks silently lost 2
|
|
19479
|
+
# from each file: the agent received a plan it was never told was
|
|
19480
|
+
# truncated, and the founder-facing case ("hand it a release doc") was
|
|
19481
|
+
# quietly capped at 3.
|
|
19482
|
+
#
|
|
19483
|
+
# A count is the wrong bound anyway: one rich PRD task with a 300-char
|
|
19484
|
+
# description plus acceptance criteria can outweigh ten legacy one-liners.
|
|
19485
|
+
# The real constraint is prompt budget, so bound on that and say so when
|
|
19486
|
+
# the budget is hit, rather than truncating in silence.
|
|
19487
|
+
_budget = int(os.environ.get("LOKI_QUEUE_TASK_CHARS", "6000") or "6000")
|
|
19488
|
+
_max_tasks = int(os.environ.get("LOKI_QUEUE_MAX_TASKS", "25") or "25")
|
|
19489
|
+
_used = 0
|
|
19490
|
+
_shown = 0
|
|
19491
|
+
for i, task in enumerate(tasks[:_max_tasks]):
|
|
19455
19492
|
if not isinstance(task, dict):
|
|
19456
19493
|
continue
|
|
19457
19494
|
task_id = task.get("id") or "unknown"
|
|
@@ -19475,7 +19512,11 @@ def extract_tasks(filepath, prefix):
|
|
|
19475
19512
|
story = task.get("user_story", "")
|
|
19476
19513
|
if story:
|
|
19477
19514
|
lines.append(f" User Story: {story}")
|
|
19478
|
-
|
|
19515
|
+
_entry = "\n".join(lines)
|
|
19516
|
+
if _used + len(_entry) > _budget and _shown > 0:
|
|
19517
|
+
break
|
|
19518
|
+
results.append(_entry)
|
|
19519
|
+
_used += len(_entry); _shown += 1
|
|
19479
19520
|
else:
|
|
19480
19521
|
# Legacy format: extract action from payload
|
|
19481
19522
|
task_type = task.get("type") or "unknown"
|
|
@@ -19491,8 +19532,21 @@ def extract_tasks(filepath, prefix):
|
|
|
19491
19532
|
action = str(action).replace("\n", " ").replace("\r", "")[:500]
|
|
19492
19533
|
if len(str(action)) > 500:
|
|
19493
19534
|
action += "..."
|
|
19494
|
-
|
|
19495
|
-
|
|
19535
|
+
_entry = f"{prefix}[{i+1}] id={task_id} type={task_type}: {action}"
|
|
19536
|
+
if _used + len(_entry) > _budget and _shown > 0:
|
|
19537
|
+
break
|
|
19538
|
+
results.append(_entry)
|
|
19539
|
+
_used += len(_entry); _shown += 1
|
|
19540
|
+
|
|
19541
|
+
# Disclose truncation instead of hiding it. An agent told it has the
|
|
19542
|
+
# whole plan when it does not will confidently build the wrong subset.
|
|
19543
|
+
_remaining = len(tasks) - _shown
|
|
19544
|
+
if _remaining > 0:
|
|
19545
|
+
results.append(
|
|
19546
|
+
"[... %d more task(s) not shown: prompt budget %d chars reached. "
|
|
19547
|
+
"Raise LOKI_QUEUE_TASK_CHARS or LOKI_QUEUE_MAX_TASKS to include them.]"
|
|
19548
|
+
% (_remaining, _budget)
|
|
19549
|
+
)
|
|
19496
19550
|
return "\n".join(results)
|
|
19497
19551
|
except:
|
|
19498
19552
|
return ""
|
package/dashboard/__init__.py
CHANGED
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:** v9.
|
|
5
|
+
**Version:** v9.48.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.
|
|
2
|
+
var _t=Object.create;var{getPrototypeOf:vt,defineProperty:jG,getOwnPropertyNames:ht}=Object;var gt=Object.prototype.hasOwnProperty;function mt($){return this[$]}var ut,dt,pt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?ut??=new WeakMap:dt??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?_t(vt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ht($))if(!gt.call(J,q))jG(J,q,{get:mt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var ct=($)=>$;function lt($,X){this[$]=ct.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:lt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var UR={};B1(UR,{lokiDir:()=>h0,homeLokiDir:()=>WQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as it}from"url";import{existsSync as Zq}from"fs";import{homedir as at}from"os";function ot(){let $=YR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(YR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function WQ(){return h2(at(),".loki")}var YR,L1;var k1=s(()=>{YR=LG(it(import.meta.url));L1=ot()});import{readFileSync as st}from"fs";import{resolve as nt,dirname as rt}from"path";import{fileURLToPath as tt}from"url";function j9(){if(n3!==null)return n3;let $="9.48.1";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=rt(tt(import.meta.url)),Q=AG(X);n3=st(nt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var OR={};B1(OR,{runOrThrow:()=>Be,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>Me,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>MR});async function Jq($,X=MR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Be($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Ne($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ne($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Me($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var MR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Oe?"":$}var Oe,p0,$5,q1,D61,A1,f1,m5,r;var t7=s(()=>{Oe=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),D61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as we}from"fs";async function Z2(){if(GQ!==void 0)return GQ;let $="/opt/homebrew/bin/python3.12";if(we($))return GQ=$,$;let X=await g5("python3.12");if(X)return GQ=X,X;let Q=await g5("python3");return GQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var GQ;var m2=s(()=>{y8()});var vR={};B1(vR,{runStatus:()=>te});import{existsSync as u5,readFileSync as C9,readdirSync as ER,statSync as xR}from"fs";import{resolve as A5,basename as pe}from"path";import{homedir as ce}from"os";function kR($){let X=Math.trunc($);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 SR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=kR($),Y=kR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function ie(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
|
|
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)
|
|
@@ -1337,4 +1337,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1337
1337
|
`),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (yt(),St));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1338
1338
|
`),process.stderr.write(bt),2}}wR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var K61=await Z61(Bun.argv.slice(2));process.exit(K61);
|
|
1339
1339
|
|
|
1340
|
-
//# debugId=
|
|
1340
|
+
//# debugId=A9DE68F2C9E971A7D44B638160BAE6F1
|
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": "9.
|
|
4
|
+
"version": "9.48.1",
|
|
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, opencode).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"tools/",
|
|
70
70
|
"plugins/",
|
|
71
71
|
".claude-plugin/marketplace.json",
|
|
72
|
+
".github/workflows/loki-issue-to-pr.yml",
|
|
73
|
+
".github/actions/issue-to-pr/",
|
|
72
74
|
"claude/hooks/",
|
|
73
75
|
"autonomy/",
|
|
74
76
|
"providers/",
|
|
@@ -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": "9.
|
|
5
|
+
"version": "9.48.1",
|
|
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/providers/claude.sh
CHANGED
|
@@ -204,6 +204,35 @@ _loki_build_claude_auto_flags() {
|
|
|
204
204
|
_LOKI_CLAUDE_AUTO_FLAGS+=("--exclude-dynamic-system-prompt-sections")
|
|
205
205
|
fi
|
|
206
206
|
|
|
207
|
+
# --add-dir: grant the agent READ access to sibling repositories.
|
|
208
|
+
#
|
|
209
|
+
# THE DEFECT THIS CLOSES: an agent asked to change a shared type in
|
|
210
|
+
# ../service-b could not read it, did not error, and GUESSED. The user got a
|
|
211
|
+
# change that does not compile with no signal why. Silent wrong output is
|
|
212
|
+
# the worst failure mode this product has.
|
|
213
|
+
#
|
|
214
|
+
# LOKI_ADD_DIRS is a colon-separated list, matching PATH convention so an
|
|
215
|
+
# operator does not have to learn a new separator. Each entry is passed as
|
|
216
|
+
# its own `--add-dir <path>` pair.
|
|
217
|
+
#
|
|
218
|
+
# Only EXISTING directories are passed. A typo would otherwise abort the CLI
|
|
219
|
+
# and take the whole run with it, turning a convenience into an outage.
|
|
220
|
+
# Skipped entries are announced, because silently dropping a directory the
|
|
221
|
+
# operator asked for is the same silent-wrong-output defect in a new place.
|
|
222
|
+
if [ -n "${LOKI_ADD_DIRS:-}" ] && loki_claude_flag_supported "--add-dir"; then
|
|
223
|
+
local _ad_old_ifs="$IFS"
|
|
224
|
+
IFS=':'
|
|
225
|
+
for _ad in ${LOKI_ADD_DIRS}; do
|
|
226
|
+
[ -n "$_ad" ] || continue
|
|
227
|
+
if [ -d "$_ad" ]; then
|
|
228
|
+
_LOKI_CLAUDE_AUTO_FLAGS+=("--add-dir" "$_ad")
|
|
229
|
+
else
|
|
230
|
+
printf 'loki: LOKI_ADD_DIRS entry is not a directory, skipping: %s\n' "$_ad" >&2
|
|
231
|
+
fi
|
|
232
|
+
done
|
|
233
|
+
IFS="$_ad_old_ifs"
|
|
234
|
+
fi
|
|
235
|
+
|
|
207
236
|
# --mcp-config (Phase D, v7.5.22). Variadic flag (Commander `<configs...>`):
|
|
208
237
|
# Claude expects SEPARATE argv elements per path, not one space-joined
|
|
209
238
|
# value. Per Dev-C parity concern -- spread each path as its own argv
|