loki-mode 9.22.9 → 9.22.11

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/README.md CHANGED
@@ -399,6 +399,19 @@ honest verdict (VERIFIED / VERIFIED WITH GAPS / NOT VERIFIED), the key facts
399
399
  only when the receipt's own headline is VERIFIED. This is on by default whenever
400
400
  Loki opens or advises a PR; opt out with `LOKI_PROVEN_PR=0`.
401
401
 
402
+ For a review-before-publish workflow, preparation and GitHub mutation are two
403
+ explicit steps:
404
+
405
+ ```bash
406
+ loki start owner/repo#42 --prepare-pr # builds and writes exact title/body locally
407
+ loki ship --publish # pushes that issue branch and opens the PR
408
+ ```
409
+
410
+ The second command consumes `.loki/state/pr-title.txt` and
411
+ `.loki/state/pr-body.md` byte-for-byte. Plain `loki ship`, previews, and default
412
+ start paths remain non-publishing. A failed PR creation preserves both files and
413
+ prints the exact remote-branch rollback command.
414
+
402
415
  An optional advisory status check (`loki: verified-completion`) maps the verdict
403
416
  to a GitHub check-run. It is opt-in (`LOKI_PROVEN_PR_CHECK=1`) and can never block
404
417
  a merge on its own. To make verified-completion blocking, add it as a required
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.22.9
6
+ # Loki Mode v9.22.11
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.22.9 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.22.11 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.22.9
1
+ 9.22.11
@@ -265,7 +265,7 @@ _gp_criteria_lines() {
265
265
  }
266
266
 
267
267
  # write_journey_context <owner> <repo> <number> <title> <url> <acceptance>
268
- # <files> <type> <priority>
268
+ # <files> <type> <priority> [journey-start-epoch]
269
269
  #
270
270
  # Writes .loki/state/issue-context.json (feature 2: the exact acceptance context)
271
271
  # and .loki/state/journey-plan.json (feature 3: the early machine-readable
@@ -277,6 +277,7 @@ _gp_criteria_lines() {
277
277
  write_journey_context() {
278
278
  local owner="$1" repo="$2" number="$3" title="$4" url="$5"
279
279
  local acceptance="$6" files="$7" issue_type="$8" priority="$9"
280
+ local journey_started_epoch="${10:-}"
280
281
 
281
282
  local state_dir="${LOKI_DIR:-.loki}/state"
282
283
  mkdir -p "$state_dir" 2>/dev/null || return 0
@@ -348,6 +349,34 @@ write_journey_context() {
348
349
  rm -f "$plan_tmp" 2>/dev/null || true
349
350
  fi
350
351
 
352
+ # The parsed, acceptance-bound plan is the first useful result: it tells the
353
+ # user what Loki understood and what it will attempt before provider latency
354
+ # begins. Measure it honestly from command entry when the caller supplied a
355
+ # valid epoch. This is explicitly a proposed plan, never represented as a
356
+ # verified patch. Later first-artifact evidence supersedes it in receipts.
357
+ case "$journey_started_epoch" in
358
+ ''|*[!0-9]*) ;;
359
+ *)
360
+ local now_epoch elapsed first_tmp
361
+ now_epoch="$(date +%s)"
362
+ elapsed=$((now_epoch >= journey_started_epoch ? now_epoch - journey_started_epoch : 0))
363
+ first_tmp="$state_dir/.first-useful-result.$$.json"
364
+ if jq -n --argjson seconds "$elapsed" \
365
+ --arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
366
+ '{schema_version:"1.0", kind:"proposed_solution_plan",
367
+ seconds_to_first_result:$seconds,
368
+ under_60_seconds:($seconds < 60),
369
+ verified_patch:false, generated_at:$generated_at,
370
+ artifact:".loki/state/journey-plan.json"}' \
371
+ > "$first_tmp" 2>/dev/null; then
372
+ mv -f "$first_tmp" "$state_dir/first-useful-result.json" 2>/dev/null \
373
+ || rm -f "$first_tmp"
374
+ else
375
+ rm -f "$first_tmp" 2>/dev/null || true
376
+ fi
377
+ ;;
378
+ esac
379
+
351
380
  return 0
352
381
  }
353
382
 
@@ -1031,13 +1031,24 @@ def _collect_journey(loki_dir):
1031
1031
  },
1032
1032
  }
1033
1033
 
1034
- # Time to first useful result. Reuses the number run.sh already writes; we
1035
- # do not add a second writer and we never synthesize the value.
1034
+ # Time to first useful result. Prefer the first verified code artifact when
1035
+ # one exists. Before that, issue mode can truthfully report its acceptance-
1036
+ # bound proposed plan, explicitly labelled as NOT a verified patch.
1036
1037
  fa = _read_json(os.path.join(state, "first-artifact.json"), default=None)
1037
1038
  if isinstance(fa, dict):
1038
1039
  v = fa.get("seconds_to_first_artifact")
1039
1040
  if isinstance(v, (int, float)) and v >= 0:
1040
1041
  out["time_to_first_result_sec"] = int(v)
1042
+ out["first_result_kind"] = "code_change"
1043
+ out["first_result_verified_patch"] = True
1044
+ else:
1045
+ fr = _read_json(os.path.join(state, "first-useful-result.json"), default=None)
1046
+ if isinstance(fr, dict):
1047
+ v = fr.get("seconds_to_first_result")
1048
+ if isinstance(v, (int, float)) and v >= 0:
1049
+ out["time_to_first_result_sec"] = int(v)
1050
+ out["first_result_kind"] = str(fr.get("kind") or "")
1051
+ out["first_result_verified_patch"] = bool(fr.get("verified_patch") is True)
1041
1052
 
1042
1053
  # Human interventions. Fills the socket trust_trajectory.py:145 already
1043
1054
  # reads and documents as "no per-run counter persisted today". Absent file
@@ -220,7 +220,12 @@ def main():
220
220
 
221
221
  ttfr = journey.get("time_to_first_result_sec")
222
222
  if isinstance(ttfr, int):
223
- _line("| Time to first result | " + str(ttfr) + "s |")
223
+ first_kind = str(journey.get("first_result_kind") or "").strip()
224
+ if first_kind == "proposed_solution_plan":
225
+ _line("| First useful result | proposed plan in " + str(ttfr)
226
+ + "s (not a verified patch) |")
227
+ else:
228
+ _line("| Time to first result | " + str(ttfr) + "s |")
224
229
 
225
230
  ivs = journey.get("interventions")
226
231
  if isinstance(ivs, int):
package/autonomy/loki CHANGED
@@ -4617,6 +4617,89 @@ cmd_next() {
4617
4617
  }
4618
4618
 
4619
4619
  # loki ship -- the build is done, now get it out (review + PR advice in one flow).
4620
+ # Publish an already-prepared PR. This is deliberately a separate, explicit
4621
+ # mutation boundary from the default print-only `loki ship` flow. Preparation
4622
+ # writes the exact reviewed title/body under .loki/state; publishing consumes
4623
+ # those bytes without rebuilding, re-rendering, or inventing evidence.
4624
+ publish_prepared_pr() {
4625
+ local state_dir="${LOKI_DIR:-.loki}/state"
4626
+ local title_file="$state_dir/pr-title.txt"
4627
+ local body_file="$state_dir/pr-body.md"
4628
+ local pr_state="$state_dir/pr.json"
4629
+
4630
+ if [ ! -s "$title_file" ] || [ ! -s "$body_file" ]; then
4631
+ echo "Error: no prepared PR found. Run 'loki start <issue> --prepare-pr' first." >&2
4632
+ return 2
4633
+ fi
4634
+ if ! command -v git >/dev/null 2>&1 || ! git rev-parse --git-dir >/dev/null 2>&1; then
4635
+ echo "Error: publishing a prepared PR requires a git repository." >&2
4636
+ return 2
4637
+ fi
4638
+ if ! command -v gh >/dev/null 2>&1; then
4639
+ echo "Error: publishing a prepared PR requires the GitHub CLI (gh)." >&2
4640
+ return 2
4641
+ fi
4642
+
4643
+ local branch base title existing_url pr_url tmp_state
4644
+ branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
4645
+ case "$branch" in
4646
+ ""|HEAD|main|master)
4647
+ echo "Error: refusing to publish prepared work from '$branch'; check out its issue branch." >&2
4648
+ return 2 ;;
4649
+ esac
4650
+ if ! git remote get-url origin >/dev/null 2>&1; then
4651
+ echo "Error: publishing a prepared PR requires an 'origin' remote." >&2
4652
+ return 2
4653
+ fi
4654
+ title="$(cat "$title_file")"
4655
+ if [ -z "$title" ]; then
4656
+ echo "Error: prepared PR title must be one non-empty line." >&2
4657
+ return 2
4658
+ fi
4659
+ case "$title" in
4660
+ *$'\n'*)
4661
+ echo "Error: prepared PR title must be one non-empty line." >&2
4662
+ return 2 ;;
4663
+ esac
4664
+
4665
+ # Idempotence: an interrupted/retried publish reuses the open PR and never
4666
+ # creates a duplicate. This read-only query occurs only after --publish.
4667
+ existing_url="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty' 2>/dev/null || true)"
4668
+ if [ -n "$existing_url" ]; then
4669
+ pr_url="$existing_url"
4670
+ else
4671
+ # Do not attempt PR creation after a failed push. The prepared body
4672
+ # remains local and byte-identical, so the operator can fix auth/network
4673
+ # and retry. A successful push followed by create failure is reversible
4674
+ # with the exact command printed below.
4675
+ if ! git push --set-upstream origin "$branch"; then
4676
+ echo "Error: branch push failed; prepared PR files were preserved." >&2
4677
+ return 1
4678
+ fi
4679
+ if ! pr_url="$(gh pr create --head "$branch" --title "$title" --body-file "$body_file")"; then
4680
+ echo "Error: PR creation failed; prepared PR files were preserved." >&2
4681
+ echo "Rollback remote branch: git push origin --delete $branch" >&2
4682
+ return 1
4683
+ fi
4684
+ pr_url="$(printf '%s\n' "$pr_url" | grep -Eo 'https://[^[:space:]]+' | head -1)"
4685
+ if [ -z "$pr_url" ]; then
4686
+ echo "Error: gh reported success without a PR URL; prepared files were preserved." >&2
4687
+ echo "Inspect: gh pr list --head $branch" >&2
4688
+ return 1
4689
+ fi
4690
+ fi
4691
+
4692
+ mkdir -p "$state_dir" || return 1
4693
+ tmp_state="$pr_state.tmp.$$"
4694
+ if command -v jq >/dev/null 2>&1; then
4695
+ jq -n --arg url "$pr_url" --arg branch "$branch" --arg title "$title" \
4696
+ '{state:"created", url:$url, branch:$branch, title:$title,
4697
+ body_path:".loki/state/pr-body.md"}' > "$tmp_state" \
4698
+ && mv "$tmp_state" "$pr_state" || { rm -f "$tmp_state"; return 1; }
4699
+ fi
4700
+ echo "PR ready: $pr_url"
4701
+ }
4702
+
4620
4703
  # Composes existing surfaces only: optionally opens the running app (--preview),
4621
4704
  # runs the SAME diff quality gates as cmd_review, and on a clean-enough result
4622
4705
  # prints the exact branch-aware PR command via the shared print_pr_advice (the
@@ -4626,6 +4709,7 @@ cmd_next() {
4626
4709
  # and points at the findings instead of advising a PR.
4627
4710
  cmd_ship() {
4628
4711
  local do_preview=false
4712
+ local publish=false
4629
4713
  local review_args=()
4630
4714
  # Tracks whether the caller passed an explicit scope flag (--staged, --since,
4631
4715
  # ...). Passthrough flags like --yes/--severity do NOT set this, so they do
@@ -4637,6 +4721,7 @@ cmd_ship() {
4637
4721
  echo -e "${BOLD}Loki Mode -- finish the build: review, then PR advice (print-only)${NC}"
4638
4722
  echo ""
4639
4723
  echo "Usage: loki ship [--preview] [--yes] [review options]"
4724
+ echo " loki ship --publish"
4640
4725
  echo ""
4641
4726
  echo "Runs the natural finish line in one command:"
4642
4727
  echo " 1. (with --preview) opens the app Loki built and started locally"
@@ -4645,13 +4730,15 @@ cmd_ship() {
4645
4730
  echo " 3. on a clean-enough result, PRINTS the exact branch-aware"
4646
4731
  echo " git push + pull-request command for YOU to run"
4647
4732
  echo ""
4648
- echo "It is advisory only for shipping: it NEVER runs 'git push', NEVER"
4649
- echo "creates a PR, and NEVER deploys. You run the printed command."
4733
+ echo "The default flow is advisory only: it NEVER runs 'git push' or"
4734
+ echo "creates a PR. --publish is the explicit mutation boundary: it"
4735
+ echo "publishes the exact title/body previously written by --prepare-pr."
4650
4736
  echo "If review finds HIGH or CRITICAL findings it stops and points you"
4651
4737
  echo "at the findings instead of advising a PR (exit code 1)."
4652
4738
  echo ""
4653
4739
  echo "Options:"
4654
4740
  echo " --preview Open the running local app first (loki preview)"
4741
+ echo " --publish Push the current issue branch and publish its prepared PR"
4655
4742
  echo " --yes, -y Pass through to review (skip its confirmation prompt)"
4656
4743
  echo " --help, -h Show this help and exit"
4657
4744
  echo ""
@@ -4660,6 +4747,7 @@ cmd_ship() {
4660
4747
  return 0
4661
4748
  ;;
4662
4749
  --preview) do_preview=true ;;
4750
+ --publish) publish=true ;;
4663
4751
  # Only a known set of NON-scope passthrough flags are exempt from
4664
4752
  # "explicit scope". Everything else the caller passes -- an explicit
4665
4753
  # scope flag (--staged/--since/...), a positional <file-or-dir>, or any
@@ -4689,6 +4777,15 @@ cmd_ship() {
4689
4777
  shift
4690
4778
  done
4691
4779
 
4780
+ if [ "$publish" = true ]; then
4781
+ if [ "$do_preview" = true ] || [ "${#review_args[@]}" -gt 0 ]; then
4782
+ echo "Error: --publish cannot be combined with preview/review options." >&2
4783
+ return 2
4784
+ fi
4785
+ publish_prepared_pr
4786
+ return $?
4787
+ fi
4788
+
4692
4789
  # 1. Optional preview of the running local app. Only when an app-runner state
4693
4790
  # file exists, so we never imply an app is running when it is not.
4694
4791
  if [ "$do_preview" = true ]; then
@@ -9694,6 +9791,12 @@ cmd_issue_view() {
9694
9791
  #===============================================================================
9695
9792
 
9696
9793
  cmd_run() {
9794
+ # Wall-clock anchor for the issue journey. Captured before argument parsing,
9795
+ # auth, or issue fetch so the first-result measurement includes the wait the
9796
+ # user actually experiences. Exported only through the explicit writer call
9797
+ # below; it does not alter runner/provider behavior.
9798
+ local _issue_journey_started_epoch
9799
+ _issue_journey_started_epoch="$(date +%s)"
9697
9800
  # v6.84.0: `loki run` is now a deprecated alias for `loki start <issue-ref>`.
9698
9801
  # When invoked directly (not via the unified start dispatcher), emit a
9699
9802
  # deprecation notice and a telemetry event so we can track adoption of the
@@ -10060,7 +10163,8 @@ cmd_run() {
10060
10163
  _gp_acc=$(extract_acceptance_criteria "$_gp_body" 2>/dev/null || true)
10061
10164
  fi
10062
10165
  write_journey_context "$_gp_owner" "$_gp_repo" "${number:-}" "$title" \
10063
- "$url" "$_gp_acc" "" "${issue_provider:-github}" "" 2>/dev/null || true
10166
+ "$url" "$_gp_acc" "" "${issue_provider:-github}" "" \
10167
+ "$_issue_journey_started_epoch" 2>/dev/null || true
10064
10168
  fi
10065
10169
 
10066
10170
  # Per-session locking (v6.4.0): export session ID so run.sh uses
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.22.9"
10
+ __version__ = "9.22.11"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: