loki-mode 9.54.2 → 9.56.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.
Files changed (109) hide show
  1. package/.github/actions/issue-to-pr/action.yml +165 -29
  2. package/.github/workflows/loki-issue-to-pr.yml +137 -24
  3. package/README.md +1 -1
  4. package/SKILL.md +2 -2
  5. package/VERSION +1 -1
  6. package/autonomy/completion-council.sh +110 -34
  7. package/autonomy/crash.sh +6 -6
  8. package/autonomy/lib/done-recognition.sh +7 -0
  9. package/autonomy/lib/prd-enrich.sh +7 -0
  10. package/autonomy/lib/proof-check.sh +9 -0
  11. package/autonomy/lib/proof-generator.py +12 -3
  12. package/autonomy/lib/proof-verify.py +93 -21
  13. package/autonomy/lib/voter-agents.sh +7 -0
  14. package/autonomy/lib/workspace_diff.py +18 -7
  15. package/autonomy/loki +537 -122
  16. package/autonomy/quickstart.sh +7 -0
  17. package/autonomy/run.sh +956 -37
  18. package/autonomy/verify.sh +114 -8
  19. package/bin/loki +12 -0
  20. package/dashboard/__init__.py +1 -1
  21. package/dashboard/api_keys.py +12 -5
  22. package/dashboard/build_supervisor.py +25 -1
  23. package/dashboard/server.py +46 -24
  24. package/dashboard/static/index.html +57 -57
  25. package/docs/INSTALLATION.md +6 -5
  26. package/docs/VERIFIED-COMPLETION-PLAN.md +7 -5
  27. package/docs/air-gapped.md +39 -17
  28. package/docs/enterprise/security.md +10 -13
  29. package/docs/environment-variables.md +2 -0
  30. package/docs/exit-codes.md +76 -2
  31. package/docs/v10/BACKLOG.md +56 -0
  32. package/docs/v10/BOARD.md +118 -32
  33. package/docs/v10/CONTROL.md +36 -0
  34. package/docs/v10/DECISIONS.md +74 -0
  35. package/docs/v10/METRICS.md +78 -1
  36. package/docs/v10/PROGRESS.md +928 -0
  37. package/loki-ts/dist/loki.js +331 -327
  38. package/mcp/__init__.py +1 -1
  39. package/package.json +1 -1
  40. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  41. package/providers/claude.sh +1 -1
  42. package/providers/loader.sh +10 -0
  43. package/providers/model_catalog.json +12 -5
  44. package/providers/models.sh +1 -1
  45. package/references/core-workflow.md +1 -1
  46. package/tools/receipt-attest.py +1 -1
  47. package/web-app/dist/assets/{AdminPage-CZrBkzRF.js → AdminPage-Oyx-w2Ch.js} +1 -1
  48. package/web-app/dist/assets/{Avatar-gQIp6FBI.js → Avatar-D60m65JB.js} +1 -1
  49. package/web-app/dist/assets/{Badge-D2_fBTFY.js → Badge-CqfZNaem.js} +1 -1
  50. package/web-app/dist/assets/{Button-D5qDd2ef.js → Button-BqjzE4fz.js} +1 -1
  51. package/web-app/dist/assets/CockpitPage-Czk7o_ou.js +47 -0
  52. package/web-app/dist/assets/ComparePage-BEfTrTb1.js +1 -0
  53. package/web-app/dist/assets/{ErrorBoundary-BRABI-BT.js → ErrorBoundary-BrLk5nkO.js} +1 -1
  54. package/web-app/dist/assets/EvidenceReceiptPanel-9vEpi5ir.js +1 -0
  55. package/web-app/dist/assets/{GitHubIssuesPanel-BC5GzKUN.js → GitHubIssuesPanel-D6izZx0w.js} +1 -1
  56. package/web-app/dist/assets/GitHubPRsPanel-BTkG2FgA.js +17 -0
  57. package/web-app/dist/assets/{HomePage-D_CDMoen.js → HomePage-nsr4kpN1.js} +2 -2
  58. package/web-app/dist/assets/{LoginPage-BBNA_N9t.js → LoginPage-BVblQrr9.js} +1 -1
  59. package/web-app/dist/assets/{MagicPage-CnFRZCMx.js → MagicPage-CmnSyLq7.js} +1 -1
  60. package/web-app/dist/assets/{MetricsPage-CPfLMRlh.js → MetricsPage-BVfwU81j.js} +1 -1
  61. package/web-app/dist/assets/{NotFoundPage-lA0XSGTz.js → NotFoundPage-AYQ6uqqI.js} +1 -1
  62. package/web-app/dist/assets/{ProjectPage-CNAPbr9z.js → ProjectPage-DMnbw9X9.js} +3 -3
  63. package/web-app/dist/assets/{ProjectsPage-CMtm8goN.js → ProjectsPage-CNVTAF2N.js} +1 -1
  64. package/web-app/dist/assets/{SettingsPage-BhIjVMYE.js → SettingsPage-D1_qasFD.js} +3 -3
  65. package/web-app/dist/assets/{ShowcasePage-BndtGeYo.js → ShowcasePage-2dBCjLXn.js} +1 -1
  66. package/web-app/dist/assets/{SystemSettingsPage-euXI-KXE.js → SystemSettingsPage-Djo6IE4u.js} +1 -1
  67. package/web-app/dist/assets/{TeamsPage-ClUKecun.js → TeamsPage-CY8p-yxD.js} +1 -1
  68. package/web-app/dist/assets/{TemplatesPage-BWHWVk0L.js → TemplatesPage-B6iFgH6t.js} +1 -1
  69. package/web-app/dist/assets/{TerminalOutput-Cv3N3Szf.js → TerminalOutput-B3yzZe-h.js} +1 -1
  70. package/web-app/dist/assets/{activity-dEhz-bXR.js → activity-CNfAjtRv.js} +1 -1
  71. package/web-app/dist/assets/{bell-B3XUMq1t.js → bell-CMETsHPH.js} +1 -1
  72. package/web-app/dist/assets/{bot-X-ssNlwR.js → bot-C9kXvk1i.js} +1 -1
  73. package/web-app/dist/assets/{check-C_9Nx7kb.js → check-BRK-zLB5.js} +1 -1
  74. package/web-app/dist/assets/{chevron-left-tvNswo3A.js → chevron-left-CxI5uD4g.js} +1 -1
  75. package/web-app/dist/assets/{circle-alert-CSB88nEs.js → circle-alert-CVFRPWpv.js} +1 -1
  76. package/web-app/dist/assets/{clock-C8p2e4NI.js → clock-CLYV8a_x.js} +1 -1
  77. package/web-app/dist/assets/{cloud-CKp2ZvhG.js → cloud-CiISiIOa.js} +1 -1
  78. package/web-app/dist/assets/{code-xml-dY70LCHD.js → code-xml-DHsgqwV-.js} +1 -1
  79. package/web-app/dist/assets/{database-DObn5fmn.js → database-DbvUqtz3.js} +1 -1
  80. package/web-app/dist/assets/{dollar-sign-DSu6n3K3.js → dollar-sign-Ct0lRPHw.js} +1 -1
  81. package/web-app/dist/assets/{file-code-corner-DpCnS73z.js → file-code-corner-D1sjFEh7.js} +1 -1
  82. package/web-app/dist/assets/{file-plus-D3IPKCf1.js → file-plus-DnmX18Fi.js} +1 -1
  83. package/web-app/dist/assets/{globe-C9QXkgH_.js → globe-bSpDNgoA.js} +1 -1
  84. package/web-app/dist/assets/{hammer-D_FtWe1y.js → hammer-Bj41Kvlq.js} +1 -1
  85. package/web-app/dist/assets/{index-CI54rara.css → index-BfQ0f3-S.css} +1 -1
  86. package/web-app/dist/assets/{index-Cz7q1bvC.js → index-CxajNJVu.js} +3 -3
  87. package/web-app/dist/assets/{layers-DsW7jnJX.js → layers-BNtBwYER.js} +1 -1
  88. package/web-app/dist/assets/{loader-circle-B3HspjKR.js → loader-circle-YY_VZofG.js} +1 -1
  89. package/web-app/dist/assets/{lock-DSI29pct.js → lock-rpjEPVoC.js} +1 -1
  90. package/web-app/dist/assets/{package-CLNxtA2x.js → package-desXU6XA.js} +1 -1
  91. package/web-app/dist/assets/{plus-DyI4WC4_.js → plus-7AoOU-I9.js} +1 -1
  92. package/web-app/dist/assets/{refresh-cw-D2dv0xA0.js → refresh-cw-oNj-Xn4B.js} +1 -1
  93. package/web-app/dist/assets/{rotate-ccw-CBvihA-a.js → rotate-ccw-DeiFjfVO.js} +1 -1
  94. package/web-app/dist/assets/{scroll-text-DvoOvAl_.js → scroll-text-Q9AzWiNV.js} +1 -1
  95. package/web-app/dist/assets/{server-DenWvSUt.js → server-DKgVwfek.js} +1 -1
  96. package/web-app/dist/assets/{shield-alert-40cX_FvG.js → shield-alert-G6x-bS-I.js} +1 -1
  97. package/web-app/dist/assets/{thumbs-up-BrjZ6QuV.js → thumbs-up-Qhk0yvw0.js} +1 -1
  98. package/web-app/dist/assets/{trash-2-C6evwkEH.js → trash-2-D23gvznl.js} +1 -1
  99. package/web-app/dist/assets/{trending-down-CfbZis3O.js → trending-down-CSQClPrr.js} +1 -1
  100. package/web-app/dist/assets/{trending-up-DWuEK3WC.js → trending-up-sjMSd1wc.js} +1 -1
  101. package/web-app/dist/assets/{upload-B9DFBcPG.js → upload-Dchovhld.js} +1 -1
  102. package/web-app/dist/assets/{usePolling-BryVC-T1.js → usePolling-DwLceXwF.js} +1 -1
  103. package/web-app/dist/assets/{user-DMRXwZiw.js → user-BqZM8CmD.js} +1 -1
  104. package/web-app/dist/index.html +2 -2
  105. package/web-app/server.py +52 -17
  106. package/web-app/dist/assets/CockpitPage-DVMk2RUK.js +0 -47
  107. package/web-app/dist/assets/ComparePage-NMLyE1xL.js +0 -1
  108. package/web-app/dist/assets/EvidenceReceiptPanel-D2LNKqUx.js +0 -1
  109. package/web-app/dist/assets/GitHubPRsPanel-BSlmoVTS.js +0 -17
@@ -1,11 +1,34 @@
1
1
  name: 'Loki Issue to PR'
2
- description: 'Hand Loki Mode a GitHub issue; it opens a pull request with a verifiable evidence receipt.'
2
+ description: 'Hand Loki Mode a GitHub issue; it produces a patch and a PR body with a verifiable evidence receipt. Open the PR from a separate job that runs no agent.'
3
3
  author: 'asklokesh'
4
4
 
5
5
  branding:
6
6
  icon: 'git-pull-request'
7
7
  color: 'purple'
8
8
 
9
+ # Rule of Two: the issue text is untrusted, so the job that runs this action
10
+ # must not hold a write token. Run it with `permissions: contents: read,
11
+ # issues: read` and `actions/checkout` with `persist-credentials: false`, upload
12
+ # `outputs.patch`, and push and open the PR from a second job that runs no
13
+ # agent. .github/workflows/loki-issue-to-pr.yml in asklokesh/loki-mode is the
14
+ # reference. Earlier versions pushed and opened the PR from the same step that
15
+ # ran the agent; this one does not.
16
+ #
17
+ # Token co-location within THIS job: the fetch step below holds GH_TOKEN and
18
+ # runs no agent; the agent step runs `loki start` on the pre-fetched PRD file
19
+ # and is given no token (GH_TOKEN/GITHUB_TOKEN/GH_ENTERPRISE_TOKEN/
20
+ # GITHUB_ENTERPRISE_TOKEN all explicitly blanked, overriding anything the
21
+ # caller's `uses:` step set at a level this composite's steps would otherwise
22
+ # inherit). This narrows exposure to the same-step case (a prompt-injected
23
+ # agent invoking `gh`/git with the token directly) but does NOT provide
24
+ # process isolation: fetch and agent steps still share one runner VM and one
25
+ # Actions Runner.Worker process holding the job token in memory, reachable via
26
+ # /proc/<pid>/environ or ptrace with the passwordless sudo hosted runners
27
+ # grant (see the March 2025 tj-actions/changed-files compromise, which used
28
+ # exactly this). The real isolation boundary is the caller's job split: keep
29
+ # this job's permissions read-only (contents: read, issues: read) and push
30
+ # from a separate job/runner, as the reference workflow does.
31
+
9
32
  inputs:
10
33
  issue:
11
34
  description: 'Issue reference. Defaults to the issue that triggered this workflow.'
@@ -29,13 +52,29 @@ inputs:
29
52
  default: 'latest'
30
53
 
31
54
  outputs:
55
+ patch:
56
+ description: 'Path to the patch Loki committed on top of the checked-out commit (empty file when nothing changed).'
57
+ value: ${{ steps.run.outputs.patch }}
58
+ pr_body:
59
+ description: 'Path to a PR body carrying the evidence receipt.'
60
+ value: ${{ steps.run.outputs.pr_body }}
32
61
  pr_url:
33
- description: 'URL of the pull request Loki opened, empty if none was opened.'
34
- value: ${{ steps.run.outputs.pr_url }}
62
+ description: 'Always empty. This action no longer opens the pull request; open it from a job that runs no agent.'
63
+ value: ''
35
64
 
36
65
  runs:
37
66
  using: 'composite'
38
67
  steps:
68
+ - name: Refuse a checkout that left a push token behind
69
+ shell: bash
70
+ run: |
71
+ # actions/checkout persists the job token in git config unless told
72
+ # not to. The agent could push with it, whatever this action does.
73
+ if git config --get-regexp '^http\..*\.extraheader$' >/dev/null 2>&1; then
74
+ echo "::error::actions/checkout left a token in git config that the agent could push with. Set 'persist-credentials: false' on the checkout step of this job."
75
+ exit 1
76
+ fi
77
+
39
78
  - name: Install Loki Mode
40
79
  shell: bash
41
80
  run: npm install -g loki-mode@${{ inputs.version }}
@@ -46,7 +85,7 @@ runs:
46
85
  run: |
47
86
  # Prefer the explicit input; otherwise use the triggering issue. Failing
48
87
  # CLOSED here is deliberate: a run with no issue would otherwise start
49
- # from the repository's own state and produce an unrelated PR.
88
+ # from the repository's own state and produce an unrelated change.
50
89
  REF="${{ inputs.issue }}"
51
90
  if [ -z "$REF" ]; then
52
91
  NUM="${{ github.event.issue.number }}"
@@ -59,45 +98,142 @@ runs:
59
98
  echo "ref=$REF" >> "$GITHUB_OUTPUT"
60
99
  echo "Resolved issue reference: $REF"
61
100
 
62
- - name: Resolve the issue to a pull request
63
- id: run
101
+ - name: Fetch the issue (holds the token, runs no agent)
102
+ id: fetch
64
103
  shell: bash
65
104
  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.
105
+ # Used only to read the issue in this step. Never exported to the
106
+ # agent step below.
68
107
  GH_TOKEN: ${{ github.token }}
108
+ ISSUE_REF: ${{ steps.ref.outputs.ref }}
109
+ run: |
110
+ set -euo pipefail
111
+
112
+ case "$ISSUE_REF" in
113
+ */*'#'*) : ;;
114
+ *) echo "::error::Malformed issue reference: $ISSUE_REF (expected owner/repo#number)"; exit 1 ;;
115
+ esac
116
+ REPO="${ISSUE_REF%%#*}"
117
+ NUMBER="${ISSUE_REF##*#}"
118
+ case "$NUMBER" in
119
+ ''|*[!0-9]*) echo "::error::Issue number must be digits, got '$NUMBER' from '$ISSUE_REF'"; exit 1 ;;
120
+ esac
121
+
122
+ # Same fields as fetch_github_issue() in autonomy/issue-providers.sh:
123
+ # number,title,body,labels,author,createdAt,url. No comments -- the
124
+ # PRD the agent works from carries the same content the CLI's own
125
+ # issue-mode path would have handed it, nothing wider.
126
+ ISSUE_JSON="$(gh issue view "$NUMBER" --repo "$REPO" \
127
+ --json number,title,body,labels,author,createdAt,url)"
128
+
129
+ OUT="$RUNNER_TEMP/loki-out"
130
+ mkdir -p "$OUT"
131
+ PRD_FILE="$OUT/prd-issue-${NUMBER}.md"
132
+
133
+ REPO="$REPO" ISSUE_JSON="$ISSUE_JSON" PRD_FILE="$PRD_FILE" python3 -c "
134
+ import json, os
135
+
136
+ data = json.loads(os.environ['ISSUE_JSON'])
137
+ repo = os.environ['REPO']
138
+ title = data.get('title', '')
139
+ body = data.get('body') or ''
140
+ labels = ', '.join(l['name'] for l in data.get('labels', []))
141
+ author = (data.get('author') or {}).get('login', '')
142
+ url = data.get('url', '')
143
+ created_at = data.get('createdAt', '')
144
+ number = data.get('number', '')
145
+
146
+ lines = [
147
+ '# PRD: ' + title,
148
+ '',
149
+ '**Source:** GitHub Issue [#' + str(number) + '](' + url + ')',
150
+ '**Author:** @' + author,
151
+ '**Created:** ' + created_at,
152
+ '**Repository:** ' + repo,
153
+ ]
154
+ if labels:
155
+ lines.append('**Labels:** ' + labels)
156
+ lines += [
157
+ '',
158
+ '---',
159
+ '',
160
+ '## Overview',
161
+ '',
162
+ body,
163
+ '',
164
+ '---',
165
+ '',
166
+ '## Acceptance Criteria',
167
+ '',
168
+ 'Based on the issue description, implement the following:',
169
+ '',
170
+ '1. Address all requirements specified in the issue body above',
171
+ '2. Ensure backward compatibility (unless explicitly breaking changes are requested)',
172
+ '3. Add appropriate tests for new functionality',
173
+ '4. Update documentation as needed',
174
+ '',
175
+ '---',
176
+ '',
177
+ '## References',
178
+ '',
179
+ '- Original Issue: ' + url,
180
+ '',
181
+ ]
182
+ with open(os.environ['PRD_FILE'], 'w') as f:
183
+ f.write(chr(10).join(lines))
184
+ "
185
+ echo "prd=$PRD_FILE" >> "$GITHUB_OUTPUT"
186
+ echo "issue_number=$NUMBER" >> "$GITHUB_OUTPUT"
187
+
188
+ - name: Resolve the issue to a patch (no token in this step's environment)
189
+ id: run
190
+ shell: bash
191
+ env:
69
192
  LOKI_PROVIDER: ${{ inputs.provider }}
70
193
  LOKI_BUDGET_LIMIT: ${{ inputs.budget_limit }}
71
194
  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'
195
+ # The agent step never pushes or opens a PR (Rule of Two).
196
+ LOKI_DELEGATE_PR: '0'
197
+ ISSUE_REF: ${{ steps.ref.outputs.ref }}
198
+ PRD_FILE: ${{ steps.fetch.outputs.prd }}
199
+ # Explicitly blanked, not merely omitted: this overrides any value the
200
+ # caller's `uses:` step set, which a composite action's steps would
201
+ # otherwise inherit. These are the same four run.sh withholds
202
+ # (_loki_withhold_github_tokens).
203
+ GH_TOKEN: ''
204
+ GITHUB_TOKEN: ''
205
+ GH_ENTERPRISE_TOKEN: ''
206
+ GITHUB_ENTERPRISE_TOKEN: ''
76
207
  run: |
77
208
  set -uo pipefail
78
209
 
79
- # git identity: gh pr create needs commits to have an author.
210
+ # git identity: Loki commits the session's work on its own branch.
80
211
  git config user.name "${GITHUB_ACTOR:-loki-mode}"
81
212
  git config user.email "${GITHUB_ACTOR:-loki-mode}@users.noreply.github.com"
82
213
 
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"
214
+ START_SHA="$(git rev-parse HEAD)"
215
+ RC=0
216
+ loki start "$PRD_FILE" || RC=$?
93
217
 
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
218
+ OUT="$RUNNER_TEMP/loki-out"
219
+ mkdir -p "$OUT"
220
+ git diff --binary "$START_SHA" HEAD -- . ':(exclude).loki' > "$OUT/loki.patch"
221
+ {
222
+ echo "Resolves ${ISSUE_REF}."
223
+ echo ""
224
+ ID="$(cat .loki/state/last-proof-id.txt 2>/dev/null || true)"
225
+ LIB="$(npm root -g)/loki-mode/autonomy/lib/proof-pr.sh"
226
+ if [ -n "$ID" ] && [ -f ".loki/proofs/$ID/proof.json" ] && [ -f "$LIB" ]; then
227
+ # shellcheck source=/dev/null
228
+ . "$LIB" && render_evidence_receipt_md ".loki/proofs/$ID/proof.json" "" ""
229
+ else
230
+ echo "Evidence Receipt: unavailable for this run."
231
+ fi
232
+ } > "$OUT/pr-body.md"
233
+ echo "patch=$OUT/loki.patch" >> "$GITHUB_OUTPUT"
234
+ echo "pr_body=$OUT/pr-body.md" >> "$GITHUB_OUTPUT"
99
235
 
100
- # Surface the receipt path so a reviewer can check the evidence.
236
+ echo "::warning::This action does not open the pull request (Rule of Two). Upload outputs.patch and open the PR from a job that holds the write token and runs no agent; see .github/workflows/loki-issue-to-pr.yml in asklokesh/loki-mode."
101
237
  if [ -d .loki/proofs ]; then
102
238
  echo "Evidence receipts under .loki/proofs/"
103
239
  fi
@@ -1,10 +1,21 @@
1
1
  # Hand Loki an issue, get a pull request.
2
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.
3
+ # Label an issue `loki` (or comment `/loki` as a repository owner, member or
4
+ # collaborator) and this opens a PR with an evidence receipt. Copy this file
5
+ # into .github/workflows/ of any repository.
5
6
  #
6
7
  # REQUIRED: set ANTHROPIC_API_KEY in repository secrets. Without it the run
7
8
  # cannot call a model and fails fast rather than burning minutes.
9
+ #
10
+ # Rule of Two: issue text is untrusted, so the agent that reads it never holds
11
+ # a write token. The work is split in two jobs on two fresh runners:
12
+ # agent reads the issue and runs Loki with a read-only token. Its only
13
+ # output is an artifact: a patch and a PR body.
14
+ # publish holds the write token and runs no agent. It applies the patch as
15
+ # data, pushes a branch and opens the PR.
16
+ # A patch that touches .github/workflows/ cannot be pushed with this token
17
+ # (GitHub requires the `workflows` permission), so the publish job fails
18
+ # visibly rather than letting an agent rewrite CI.
8
19
  name: Loki - Issue to PR
9
20
 
10
21
  on:
@@ -24,24 +35,29 @@ concurrency:
24
35
  group: loki-issue-${{ github.event.issue.number || inputs.issue }}
25
36
  cancel-in-progress: false
26
37
 
27
- permissions:
28
- contents: write # push the agent branch
29
- pull-requests: write # open the PR
30
- issues: read
38
+ # Nothing by default; each job asks for exactly what it needs.
39
+ permissions: {}
31
40
 
32
41
  jobs:
33
- resolve:
34
- # Gate on an explicit signal so the agent never fires on unrelated activity.
42
+ agent:
43
+ # Gate on an explicit signal so the agent never fires on unrelated
44
+ # activity. A label needs triage access; a `/loki` comment must come from
45
+ # someone with write-level association, not any GitHub user.
35
46
  if: >-
36
47
  (github.event_name == 'issues' && github.event.label.name == 'loki') ||
37
- (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/loki')) ||
48
+ (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/loki') &&
49
+ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
38
50
  github.event_name == 'workflow_dispatch'
39
51
  runs-on: ubuntu-latest
40
52
  timeout-minutes: 45
53
+ permissions:
54
+ contents: read # checkout
55
+ issues: read # Loki reads the issue
41
56
  steps:
42
57
  - uses: actions/checkout@v4
43
58
  with:
44
59
  fetch-depth: 0 # the agent branches and diffs; a shallow clone breaks both
60
+ persist-credentials: false # leave no token in .git/config for the agent
45
61
 
46
62
  - uses: actions/setup-node@v4
47
63
  with:
@@ -56,32 +72,129 @@ jobs:
56
72
  exit 1
57
73
  fi
58
74
 
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.49.4
75
+ - name: Install Loki Mode
76
+ run: npm install -g loki-mode
77
+
78
+ - name: Resolve the issue (no write access)
65
79
  env:
66
80
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
81
+ # Read-only in this job (contents: read, issues: read). Loki uses it
82
+ # to fetch the issue and withholds it from the agent session.
83
+ GH_TOKEN: ${{ github.token }}
84
+ ISSUE: ${{ github.event.issue.number || inputs.issue }}
85
+ LOKI_BUDGET_LIMIT: '10.00'
86
+ LOKI_MAX_ITERATIONS: '20'
87
+ # Never push or open a PR from here; the publish job does that.
88
+ LOKI_DELEGATE_PR: '0'
89
+ run: |
90
+ set -uo pipefail
91
+ case "$ISSUE" in
92
+ ''|*[!0-9]*) echo "::error::Issue number must be digits, got '$ISSUE'."; exit 1 ;;
93
+ esac
94
+
95
+ # git identity: Loki commits the session's work on its own branch.
96
+ git config user.name "${GITHUB_ACTOR:-loki-mode}"
97
+ git config user.email "${GITHUB_ACTOR:-loki-mode}@users.noreply.github.com"
98
+
99
+ RC=0
100
+ loki start "${GITHUB_REPOSITORY}#${ISSUE}" || RC=$?
101
+
102
+ OUT="$RUNNER_TEMP/loki-out"
103
+ mkdir -p "$OUT"
104
+ # The deliverable is what the session committed on top of the commit
105
+ # this run started from. .loki/ is run state, never part of the change.
106
+ git diff --binary "$GITHUB_SHA" HEAD -- . ':(exclude).loki' > "$OUT/loki.patch"
107
+
108
+ # PR body: the evidence receipt this run wrote, when there is one.
109
+ {
110
+ echo "Resolves #${ISSUE}."
111
+ echo ""
112
+ ID="$(cat .loki/state/last-proof-id.txt 2>/dev/null || true)"
113
+ LIB="$(npm root -g)/loki-mode/autonomy/lib/proof-pr.sh"
114
+ if [ -n "$ID" ] && [ -f ".loki/proofs/$ID/proof.json" ] && [ -f "$LIB" ]; then
115
+ # shellcheck source=/dev/null
116
+ . "$LIB" && render_evidence_receipt_md ".loki/proofs/$ID/proof.json" "" ""
117
+ else
118
+ echo "Evidence Receipt: unavailable for this run."
119
+ fi
120
+ } > "$OUT/pr-body.md"
121
+
122
+ if [ -s "$OUT/loki.patch" ]; then
123
+ echo "Loki produced a change; the publish job opens the pull request."
124
+ else
125
+ echo "::warning::Loki produced no committed change, so no pull request will be opened."
126
+ fi
127
+ exit "$RC"
128
+
129
+ - name: Hand the patch to the publish job
130
+ uses: actions/upload-artifact@v4
67
131
  with:
68
- issue: ${{ github.event.issue.number || inputs.issue }}
69
- budget_limit: '10.00'
132
+ name: loki-patch
133
+ path: ${{ runner.temp }}/loki-out/
134
+ if-no-files-found: error
135
+ retention-days: 7
70
136
 
71
- - name: Report back on the issue
72
- if: always() && steps.loki.outputs.pr_url != ''
137
+ publish:
138
+ # Runs only after the agent job succeeded. Everything it reads from that
139
+ # job is attacker-controllable data: no job outputs, no `${{ }}` of agent
140
+ # values in a script, nothing from the artifact is executed.
141
+ needs: agent
142
+ runs-on: ubuntu-latest
143
+ timeout-minutes: 10
144
+ permissions:
145
+ contents: write # push the branch
146
+ pull-requests: write # open the PR
147
+ issues: write # report back on the issue
148
+ steps:
149
+ - uses: actions/checkout@v4
150
+ with:
151
+ ref: ${{ github.sha }} # the commit the agent started from
152
+ persist-credentials: false
153
+
154
+ - uses: actions/download-artifact@v4
155
+ with:
156
+ name: loki-patch
157
+ path: ${{ runner.temp }}/loki-out
158
+
159
+ - name: Open the pull request
73
160
  env:
74
161
  GH_TOKEN: ${{ github.token }}
75
- PR_URL: ${{ steps.loki.outputs.pr_url }}
76
162
  ISSUE: ${{ github.event.issue.number || inputs.issue }}
163
+ BASE: ${{ github.ref_name }}
77
164
  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.
165
+ set -euo pipefail
166
+ case "$ISSUE" in
167
+ ''|*[!0-9]*) echo "::error::Issue number must be digits, got '$ISSUE'."; exit 1 ;;
168
+ esac
169
+
170
+ PATCH="$RUNNER_TEMP/loki-out/loki.patch"
171
+ BODY="$RUNNER_TEMP/loki-out/pr-body.md"
172
+ if [ ! -s "$PATCH" ]; then
173
+ echo "The agent produced no change; nothing to open."
174
+ exit 0
175
+ fi
176
+ [ -s "$BODY" ] || echo "Resolves #${ISSUE}." > "$BODY"
177
+
178
+ BRANCH="loki/issue-${ISSUE}-${GITHUB_RUN_ID}"
179
+ git config user.name "github-actions[bot]"
180
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
181
+ git switch -c "$BRANCH"
182
+ git apply --index "$PATCH"
183
+ git commit -q -m "Loki: resolve #${ISSUE}"
184
+ gh auth setup-git
185
+ git push origin "HEAD:refs/heads/${BRANCH}"
186
+
187
+ PR_URL="$(gh pr create --base "$BASE" --head "$BRANCH" \
188
+ --title "Loki: resolve #${ISSUE}" --body-file "$BODY")"
189
+ echo "Pull request: $PR_URL"
190
+
191
+ # Build the comment in a file: a blank line inside an inline YAML
192
+ # string terminates the block scalar and breaks the workflow parse.
80
193
  {
81
194
  echo "Loki opened a pull request: $PR_URL"
82
195
  echo ""
83
196
  echo "The PR body carries an evidence receipt stating what was verified,"
84
197
  echo "what was skipped, and what is degraded. Check it rather than taking"
85
198
  echo "the result on trust."
86
- } > /tmp/loki-issue-comment.md
87
- gh issue comment "$ISSUE" --body-file /tmp/loki-issue-comment.md || true
199
+ } > "$RUNNER_TEMP/loki-issue-comment.md"
200
+ gh issue comment "$ISSUE" --body-file "$RUNNER_TEMP/loki-issue-comment.md" || true
package/README.md CHANGED
@@ -748,7 +748,7 @@ env vars. See [Enterprise Identity Roadmap](docs/ENTERPRISE-IDENTITY-ROADMAP.md)
748
748
  | 8 quality gates | Yes | No | No | No |
749
749
  | Blind code review | Yes | No | No | No |
750
750
  | Enterprise auth (OIDC token + scoped RBAC) | Yes | No | Yes | No |
751
- | Air-gapped deployment | Yes | No | No | No |
751
+ | Air-gapped deployment | Verification and the build pipeline, yes; builds need a model you host ([details](docs/air-gapped.md)) | No | No | No |
752
752
  | Docker + CI/CD generation | Yes | No | Yes | No |
753
753
  | Source-available (BUSL-1.1) | Yes | No | No | No |
754
754
  | Free tier | Source-available | Yes | Yes | Yes |
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.54.2
6
+ # Loki Mode v9.56.0
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.54.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.56.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.54.2
1
+ 9.56.0