loki-mode 9.48.2 → 9.49.3
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/workflows/loki-issue-to-pr.yml +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/claude-flags.sh +18 -2
- package/autonomy/loki +137 -12
- package/autonomy/notification-checker.py +45 -17
- package/autonomy/provider-offer.sh +47 -0
- package/autonomy/quickstart.sh +306 -9
- package/autonomy/run.sh +43 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +7 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/KNOWN-ISSUES.md +42 -0
- package/docs/cost-controls.md +1 -1
- package/docs/environment-variables.md +1 -1
- package/loki-ts/dist/loki.js +317 -317
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-PyyVXg4b.js → AdminPage-BA6etdwL.js} +1 -1
- package/web-app/dist/assets/{Avatar-CTUlr_Xf.js → Avatar-CAtbcD23.js} +1 -1
- package/web-app/dist/assets/{Badge-BQeXvmg8.js → Badge-sb_JbKl0.js} +1 -1
- package/web-app/dist/assets/{Button-Dc6-P6pz.js → Button-BamXbIWa.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BspOJ3Fv.js → CockpitPage-CUYizBtG.js} +4 -4
- package/web-app/dist/assets/{ComparePage-CFXIM--Z.js → ComparePage-BnmNTggM.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C7vQYDW9.js → ErrorBoundary-BjX4_KOW.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CqjsjTOs.js → EvidenceReceiptPanel-Bq6pmNUw.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DyuNDMgD.js → GitHubIssuesPanel-DD8c6RVj.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BtAq20xF.js → GitHubPRsPanel-4NawTXRl.js} +2 -2
- package/web-app/dist/assets/{HomePage-BuVFOrO8.js → HomePage-DTREi8OE.js} +1 -1
- package/web-app/dist/assets/{LoginPage-CuhhrxGb.js → LoginPage-DavfeQxC.js} +1 -1
- package/web-app/dist/assets/{MagicPage-D2KZPX3F.js → MagicPage-BIbTFlwh.js} +1 -1
- package/web-app/dist/assets/MetricsPage-Cn3UPKpU.js +6 -0
- package/web-app/dist/assets/{NotFoundPage-CTnnaWlL.js → NotFoundPage-BVs_nuBv.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-uJFaoM8-.js → ProjectPage-CLplvOHe.js} +25 -25
- package/web-app/dist/assets/{ProjectsPage-8lKAOBIW.js → ProjectsPage-BBFzBBG_.js} +2 -2
- package/web-app/dist/assets/{SettingsPage-D1BBd5qy.js → SettingsPage-WPsJ_K9r.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CmQ0n8iO.js → ShowcasePage-ZaLaJl5J.js} +1 -1
- package/web-app/dist/assets/SystemSettingsPage-CCORP-uG.js +1 -0
- package/web-app/dist/assets/{TeamsPage-BS0TtVUa.js → TeamsPage-B6IGT_K2.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C2zPnnf2.js → TemplatesPage-RrYYC3CM.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Dle1klqq.js → TerminalOutput-CeTvKnkl.js} +1 -1
- package/web-app/dist/assets/{activity-DQp5U9f9.js → activity-CySdOdDP.js} +1 -1
- package/web-app/dist/assets/{bell-BqkTmgL7.js → bell-BHdr5yat.js} +1 -1
- package/web-app/dist/assets/{bot-DILhaIGH.js → bot-BaB4q_4m.js} +1 -1
- package/web-app/dist/assets/{check-BC9x7oPm.js → check-BAgbzVrK.js} +1 -1
- package/web-app/dist/assets/{chevron-left--yqvAfjx.js → chevron-left-CrVUY29C.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DOH6PcgZ.js → circle-alert-YrWgH49w.js} +1 -1
- package/web-app/dist/assets/{clock-DKSDFsE6.js → clock-DN8LAN-0.js} +1 -1
- package/web-app/dist/assets/{cloud-DRBU3JYs.js → cloud-D6C4b1KY.js} +1 -1
- package/web-app/dist/assets/{code-xml-D2LqszR9.js → code-xml-xBWyKfux.js} +1 -1
- package/web-app/dist/assets/{database-D7ceedlh.js → database-BYqcS6kn.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-D0A47egY.js → dollar-sign--WOh4k_m.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CH_Qbtjo.js → file-code-corner-B0rLPjW1.js} +1 -1
- package/web-app/dist/assets/{file-plus-CUNMjHk9.js → file-plus-BNhUQ1TQ.js} +1 -1
- package/web-app/dist/assets/folder-open-DliiOgFl.js +11 -0
- package/web-app/dist/assets/{globe-gZnaGg8N.js → globe-BV-hx0u_.js} +1 -1
- package/web-app/dist/assets/{hammer-BhJC5DMK.js → hammer-DhmP4GLw.js} +1 -1
- package/web-app/dist/assets/index-D98GzaW8.css +1 -0
- package/web-app/dist/assets/{index-BMmYYws0.js → index-x0TESFYr.js} +3 -3
- package/web-app/dist/assets/{layers-glXeoUbJ.js → layers-DXWDMSYj.js} +1 -1
- package/web-app/dist/assets/{lightbulb-gXn8H0Lf.js → lightbulb-PBw6hhLS.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Do2ke8xm.js → loader-circle-B7KiGAVU.js} +1 -1
- package/web-app/dist/assets/{lock-Bgplb1_n.js → lock-BUTRYdDQ.js} +1 -1
- package/web-app/dist/assets/{mail-j4Lo8hfB.js → mail-DaLiNrPb.js} +1 -1
- package/web-app/dist/assets/{minus-dhrzKSK5.js → minus-DB_hlGEk.js} +1 -1
- package/web-app/dist/assets/{package-CInWQQ97.js → package-CymmXoiQ.js} +1 -1
- package/web-app/dist/assets/{plus-BpYvoLlZ.js → plus-Cp4AO4I2.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-F6koqEgF.js → refresh-cw-ygYTmZTE.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CQ2W5S8f.js → rotate-ccw-Ba3QxM9K.js} +1 -1
- package/web-app/dist/assets/{save-DMCYGm06.js → save-DPm96BnR.js} +1 -1
- package/web-app/dist/assets/{server-Bj8EX4Z1.js → server-BapOz_vf.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CiYuulCU.js → shield-alert-DYzfVqWS.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BqEh1puP.js → thumbs-up-DxOTplej.js} +11 -6
- package/web-app/dist/assets/{trash-2-CCwouZJJ.js → trash-2-Br8dh_6B.js} +1 -1
- package/web-app/dist/assets/{trending-down-Cx1vhE4o.js → trending-down-CxSP6MU3.js} +1 -1
- package/web-app/dist/assets/{trending-up-BUhVYJ2T.js → trending-up-CXx0ywZb.js} +1 -1
- package/web-app/dist/assets/{upload-D5IsZeFM.js → upload-CpEO3qNc.js} +1 -1
- package/web-app/dist/assets/{usePolling-CJbfhFcH.js → usePolling-BuzXwjmC.js} +1 -1
- package/web-app/dist/assets/{user-BRLhezsD.js → user-DUUYT47B.js} +1 -1
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +121 -1
- package/web-app/dist/assets/MetricsPage-DFEYJl9z.js +0 -6
- package/web-app/dist/assets/SystemSettingsPage-CSHaVyuD.js +0 -6
- package/web-app/dist/assets/copy-BKW4U4Qr.js +0 -6
- package/web-app/dist/assets/folder-open-CQ_SVq94.js +0 -6
- package/web-app/dist/assets/git-commit-horizontal-DQUBe0oD.js +0 -6
- package/web-app/dist/assets/index-84JUN_aA.css +0 -1
package/autonomy/quickstart.sh
CHANGED
|
@@ -703,6 +703,175 @@ PY
|
|
|
703
703
|
return 0
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
# _qs_is_rejection <answer>: true when the user explicitly declined the offered
|
|
707
|
+
# templates. Before this existed the picker's `*)` arm mapped ANY unrecognized
|
|
708
|
+
# answer -- "none", "no", "skip", "q" -- onto top3[0], so an explicit rejection
|
|
709
|
+
# was silently converted into a selection and quickstart built a todo app for a
|
|
710
|
+
# user who had just said none of them fit. An explicit no must never become a
|
|
711
|
+
# yes; these answers route to the brief-as-spec path instead.
|
|
712
|
+
_qs_is_rejection() {
|
|
713
|
+
local a
|
|
714
|
+
a=$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')
|
|
715
|
+
case "$a" in
|
|
716
|
+
none|no|n|skip|q|quit|neither|nope|"none of these"|"none of them") return 0;;
|
|
717
|
+
esac
|
|
718
|
+
return 1
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
# _qs_detect_brownfield: print a short human reason when the CWD is an existing
|
|
722
|
+
# project, and return 0. Return 1 (printing nothing) for a greenfield directory.
|
|
723
|
+
#
|
|
724
|
+
# Signals are EXPLICIT MANIFESTS plus a git repo with at least one commit. A
|
|
725
|
+
# loose "any source file present" check was deliberately rejected: the test
|
|
726
|
+
# harnesses write helper scripts and stray .md files into their working
|
|
727
|
+
# directories, so a broad check would classify an empty scratch dir as an
|
|
728
|
+
# existing project and silently reroute the greenfield flow. A manifest or a
|
|
729
|
+
# commit is a deliberate artifact of a real project, which is the claim being
|
|
730
|
+
# made on screen.
|
|
731
|
+
#
|
|
732
|
+
# bash 3.2 safe: no associative arrays, no mapfile.
|
|
733
|
+
_qs_detect_brownfield() {
|
|
734
|
+
local reasons="" m
|
|
735
|
+
for m in package.json pyproject.toml go.mod Cargo.toml pom.xml Gemfile composer.json build.gradle setup.py; do
|
|
736
|
+
if [ -f "./$m" ]; then
|
|
737
|
+
if [ -z "$reasons" ]; then reasons="$m"; else reasons="$reasons, $m"; fi
|
|
738
|
+
fi
|
|
739
|
+
done
|
|
740
|
+
# A git repo counts only WITH a commit: `git init` alone in an empty folder
|
|
741
|
+
# is where a brand-new greenfield build legitimately starts, and treating
|
|
742
|
+
# that as "existing code" would break the first-run flow.
|
|
743
|
+
if git rev-parse --git-dir >/dev/null 2>&1 && git rev-parse HEAD >/dev/null 2>&1; then
|
|
744
|
+
if [ -z "$reasons" ]; then reasons="git repo"; else reasons="$reasons, git repo"; fi
|
|
745
|
+
fi
|
|
746
|
+
[ -n "$reasons" ] || return 1
|
|
747
|
+
printf '%s' "$reasons"
|
|
748
|
+
return 0
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
# _qs_classify_invoke <prompt>: the single model-call primitive for intent
|
|
752
|
+
# classification. Its own function so tests can override it, mirroring
|
|
753
|
+
# _loki_prd_enrich_invoke (autonomy/lib/prd-enrich.sh:43).
|
|
754
|
+
#
|
|
755
|
+
# Calls `claude -p` directly rather than the provider_invoke shell function
|
|
756
|
+
# because `timeout` execs a real command, not a function -- the same documented
|
|
757
|
+
# reason prd-enrich gives. No HTTP: this repo is provider-agnostic over CLIs and
|
|
758
|
+
# has zero chat/completions calls in source.
|
|
759
|
+
#
|
|
760
|
+
# The timeout is deliberately short. Quickstart is the first thing a new user
|
|
761
|
+
# runs, so a stalled provider must degrade to the offline scorer in seconds
|
|
762
|
+
# rather than inherit prd-enrich's 120s budget.
|
|
763
|
+
: "${LOKI_QUICKSTART_CLASSIFY_TIMEOUT:=15}"
|
|
764
|
+
_qs_classify_invoke() {
|
|
765
|
+
local prompt="$1"
|
|
766
|
+
command -v claude >/dev/null 2>&1 || return 1
|
|
767
|
+
local rc=0 out=""
|
|
768
|
+
local to=""
|
|
769
|
+
if command -v timeout >/dev/null 2>&1; then to="timeout"
|
|
770
|
+
elif command -v gtimeout >/dev/null 2>&1; then to="gtimeout"; fi
|
|
771
|
+
if [ -n "$to" ]; then
|
|
772
|
+
out=$(CAVEMAN_DEFAULT_MODE=off "$to" "${LOKI_QUICKSTART_CLASSIFY_TIMEOUT}" \
|
|
773
|
+
claude --dangerously-skip-permissions -p "$prompt" 2>/dev/null) || rc=$?
|
|
774
|
+
else
|
|
775
|
+
out=$(CAVEMAN_DEFAULT_MODE=off \
|
|
776
|
+
claude --dangerously-skip-permissions -p "$prompt" 2>/dev/null) || rc=$?
|
|
777
|
+
fi
|
|
778
|
+
[ "$rc" -ne 0 ] && return 1
|
|
779
|
+
[ -z "$out" ] && return 1
|
|
780
|
+
printf '%s' "$out"
|
|
781
|
+
return 0
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
# _qs_classify_intent <brief>: print new_project | change_to_existing_code when
|
|
785
|
+
# the model answers cleanly, and return 0. Return 1 on ANY miss.
|
|
786
|
+
#
|
|
787
|
+
# FAIL-CLOSED IS LOAD-BEARING. No provider, no network, a slow call, a non-zero
|
|
788
|
+
# exit, an empty body, or an unrecognized word all return 1, and every caller
|
|
789
|
+
# falls back to the offline keyword ranking that shipped before this existed.
|
|
790
|
+
# Quickstart must never hang or hard-fail on the model path.
|
|
791
|
+
_qs_classify_intent() {
|
|
792
|
+
local brief="$1"
|
|
793
|
+
[ -n "$brief" ] || return 1
|
|
794
|
+
local out=""
|
|
795
|
+
out=$(_qs_classify_invoke "Classify this software request as exactly one word.
|
|
796
|
+
|
|
797
|
+
Answer new_project if the user wants a brand-new application built from scratch.
|
|
798
|
+
Answer change_to_existing_code if the user wants to modify, update, fix, restyle,
|
|
799
|
+
refactor, or add to code that already exists.
|
|
800
|
+
Answer unclear if you genuinely cannot tell.
|
|
801
|
+
|
|
802
|
+
Reply with ONE word and nothing else: new_project, change_to_existing_code, or unclear.
|
|
803
|
+
|
|
804
|
+
Request: $brief") || return 1
|
|
805
|
+
# Take the first bare word; a chatty model must not defeat the match.
|
|
806
|
+
out=$(printf '%s' "$out" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z_' ' ')
|
|
807
|
+
case " $out " in
|
|
808
|
+
*" change_to_existing_code "*) printf 'change_to_existing_code'; return 0;;
|
|
809
|
+
*" new_project "*) printf 'new_project'; return 0;;
|
|
810
|
+
esac
|
|
811
|
+
return 1
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
# _qs_write_change_prd <path> <brief> <mode>: render a spec from the user's own
|
|
815
|
+
# brief. <mode> is "change" (a change against an existing codebase) or "new" (a
|
|
816
|
+
# brand-new project).
|
|
817
|
+
#
|
|
818
|
+
# The mode argument is load-bearing and must never be inferred from
|
|
819
|
+
# $brownfield_reason. That variable stays populated after the classifier flips a
|
|
820
|
+
# brownfield verdict back to a new build, so a user in an existing repo who asks
|
|
821
|
+
# for "a brand new X" and then rejects the template would be handed
|
|
822
|
+
# existing-codebase framing for a project that does not exist yet. Both callers
|
|
823
|
+
# set the mode explicitly at the point they decide what the spec IS.
|
|
824
|
+
#
|
|
825
|
+
# Why not reuse cmd_start's existing seams: `--brief` runs synthesize_brief_prd
|
|
826
|
+
# (loki:15759), which says "Build the smallest working version of the above" and
|
|
827
|
+
# stamps "Mode: Brief (zero-config first run)" -- greenfield framing that would
|
|
828
|
+
# reproduce the original bug one layer down. `loki start <dir>` runs
|
|
829
|
+
# synthesize_repo_prd (loki:15802), a quoted heredoc carrying NO caller text: it
|
|
830
|
+
# tells the agent to pick its own improvement and would discard what the user
|
|
831
|
+
# actually asked for. Neither carries a user-specified change against existing
|
|
832
|
+
# code, so this writes that spec and hands it to the normal PRD path.
|
|
833
|
+
#
|
|
834
|
+
# The brief is written via printf argument, never expanded into the heredoc, so
|
|
835
|
+
# no shell metacharacter in user input is interpreted.
|
|
836
|
+
_qs_write_change_prd() {
|
|
837
|
+
local out_file="$1" brief="$2" mode="${3:-change}"
|
|
838
|
+
{
|
|
839
|
+
if [ "$mode" = "new" ]; then
|
|
840
|
+
printf '# Build Request\n\n'
|
|
841
|
+
printf '## Goal\n'
|
|
842
|
+
printf '%s\n\n' "$brief"
|
|
843
|
+
printf '## Context\n'
|
|
844
|
+
printf 'This is a NEW project. Build it from scratch in the working directory.\n'
|
|
845
|
+
printf 'No starting template was chosen, so the Goal above is the whole spec.\n\n'
|
|
846
|
+
printf '## Requirements\n'
|
|
847
|
+
printf -- '- Scaffold the new application described in the Goal above.\n'
|
|
848
|
+
printf -- '- Build the smallest version that genuinely works end to end.\n'
|
|
849
|
+
printf -- '- Choose a conventional, well-supported stack for this kind of app.\n\n'
|
|
850
|
+
printf '## Success Criteria\n'
|
|
851
|
+
printf -- '- The application described in the Goal runs and is usable.\n'
|
|
852
|
+
printf -- '- Everything the Goal explicitly asks for is present.\n\n'
|
|
853
|
+
else
|
|
854
|
+
printf '# Change Request\n\n'
|
|
855
|
+
printf '## Goal\n'
|
|
856
|
+
printf '%s\n\n' "$brief"
|
|
857
|
+
printf '## Context\n'
|
|
858
|
+
printf 'This is an EXISTING codebase, not a new project. The working directory\n'
|
|
859
|
+
printf 'already contains the application this change applies to.\n\n'
|
|
860
|
+
printf '## Requirements\n'
|
|
861
|
+
printf -- '- Read the existing code first and follow its established conventions.\n'
|
|
862
|
+
printf -- '- Make the change described in the Goal above, and only that change.\n'
|
|
863
|
+
printf -- '- Do NOT scaffold a new project, and do NOT replace the existing app.\n'
|
|
864
|
+
printf -- '- Preserve all unrelated existing behavior and files.\n\n'
|
|
865
|
+
printf '## Success Criteria\n'
|
|
866
|
+
printf -- '- The change described in the Goal is visible when the app runs.\n'
|
|
867
|
+
printf -- '- Existing functionality still works; nothing unrelated regressed.\n\n'
|
|
868
|
+
fi
|
|
869
|
+
printf '## Constraints\n'
|
|
870
|
+
printf -- '- Keep the diff bounded and easy to review.\n'
|
|
871
|
+
printf -- '- No emojis, no em dashes in code, comments, or docs.\n'
|
|
872
|
+
} > "$out_file"
|
|
873
|
+
}
|
|
874
|
+
|
|
706
875
|
# _qs_help: concise usage for `loki quickstart --help`.
|
|
707
876
|
_qs_help() {
|
|
708
877
|
printf '%sloki quickstart%s - guided first build (setup, idea, template, plan, go)\n' "$_QS_BOLD" "$_QS_NC"
|
|
@@ -749,8 +918,18 @@ _qs_help() {
|
|
|
749
918
|
printf ' 3. Template Pick the closest starting template (offline keyword match)\n'
|
|
750
919
|
printf ' 4. Plan Review the honest cost/time estimate, then confirm\n'
|
|
751
920
|
printf '\n'
|
|
921
|
+
printf 'Existing projects:\n'
|
|
922
|
+
printf ' If the current directory is already a project (package.json,\n'
|
|
923
|
+
printf ' pyproject.toml, go.mod, Cargo.toml, a git repo with commits, and\n'
|
|
924
|
+
printf ' similar), quickstart treats your description as a CHANGE to that\n'
|
|
925
|
+
printf ' code instead of a new build. No template is offered and no file is\n'
|
|
926
|
+
printf ' written into your project directory.\n'
|
|
927
|
+
printf ' At the template prompt you can always answer "none" (or skip/no/q)\n'
|
|
928
|
+
printf ' to use your description directly as the spec.\n'
|
|
929
|
+
printf '\n'
|
|
752
930
|
printf 'The PRD is written to ./prd.md in the current directory (or the next\n'
|
|
753
931
|
printf 'free prd-quickstart*.md name if that exists), then the build starts.\n'
|
|
932
|
+
printf 'The change-request path above writes no PRD into your project.\n'
|
|
754
933
|
return 0
|
|
755
934
|
}
|
|
756
935
|
|
|
@@ -1122,7 +1301,50 @@ cmd_quickstart() {
|
|
|
1122
1301
|
fi
|
|
1123
1302
|
|
|
1124
1303
|
# ----- Step 3 of 4: Pick a template (skipped if a PRD path was given) ----
|
|
1125
|
-
|
|
1304
|
+
#
|
|
1305
|
+
# BROWNFIELD FIRST. Offering greenfield templates inside an existing project
|
|
1306
|
+
# is how "update the color theme of this app" became a brand-new todo app.
|
|
1307
|
+
# When the CWD is already a project, a brief is a CHANGE REQUEST against the
|
|
1308
|
+
# code that is here, so the template picker is skipped entirely.
|
|
1309
|
+
#
|
|
1310
|
+
# Gating:
|
|
1311
|
+
# - dry_run is excluded: preview must stay the deterministic template/plan
|
|
1312
|
+
# preview its JSON schema promises, and must run no provider code.
|
|
1313
|
+
# - an explicit --template is excluded: the user was specific, and an
|
|
1314
|
+
# explicit choice outranks detection.
|
|
1315
|
+
# spec_mode records WHY the brief became the spec, decided at each site that
|
|
1316
|
+
# decides it. It is not derived from $brownfield_reason: that stays set
|
|
1317
|
+
# after the classifier flip below, and reusing it would frame a brand-new
|
|
1318
|
+
# build as a change to code that does not exist.
|
|
1319
|
+
local brownfield_reason="" use_brief_as_spec=false spec_mode="change"
|
|
1320
|
+
if [ -z "$prd_source" ] && [ -z "$template_override" ] && [ "$dry_run" != true ] && [ -n "$brief" ]; then
|
|
1321
|
+
brownfield_reason="$(_qs_detect_brownfield)" || brownfield_reason=""
|
|
1322
|
+
if [ -n "$brownfield_reason" ]; then
|
|
1323
|
+
use_brief_as_spec=true
|
|
1324
|
+
spec_mode="change"
|
|
1325
|
+
# The model gets the final say when a provider CLI is available, so
|
|
1326
|
+
# "build me a brand new dashboard" typed inside an existing repo is
|
|
1327
|
+
# still treated as a new build. Fail-closed: any miss keeps the
|
|
1328
|
+
# filesystem verdict, which is deterministic and offline.
|
|
1329
|
+
local _qs_intent=""
|
|
1330
|
+
_qs_intent="$(_qs_classify_intent "$brief")" || _qs_intent=""
|
|
1331
|
+
if [ "$_qs_intent" = "new_project" ]; then
|
|
1332
|
+
use_brief_as_spec=false
|
|
1333
|
+
# This path falls through to the template picker and can reach
|
|
1334
|
+
# the rejection arm below, so the mode must not stay "change".
|
|
1335
|
+
spec_mode="new"
|
|
1336
|
+
fi
|
|
1337
|
+
fi
|
|
1338
|
+
fi
|
|
1339
|
+
|
|
1340
|
+
if [ "$use_brief_as_spec" = true ]; then
|
|
1341
|
+
printf '%sStep 3 of 4: Existing project detected%s\n' "$_QS_BOLD" "$_QS_NC"
|
|
1342
|
+
printf ' Detected an existing project (%s). Treating this as a change\n' "$brownfield_reason"
|
|
1343
|
+
printf ' to your code, not a new build.\n'
|
|
1344
|
+
printf ' No starting template is being used.\n\n'
|
|
1345
|
+
input_kind="change"
|
|
1346
|
+
template_name="change-request"
|
|
1347
|
+
elif [ -z "$prd_source" ]; then
|
|
1126
1348
|
if [ -n "$template_override" ]; then
|
|
1127
1349
|
template_name="$template_override"
|
|
1128
1350
|
printf '%sStep 3 of 4: Template%s\n' "$_QS_BOLD" "$_QS_NC"
|
|
@@ -1174,25 +1396,85 @@ cmd_quickstart() {
|
|
|
1174
1396
|
printf ' Selected %s (top match) for "%s".\n\n' "${top3[0]}" "$brief"
|
|
1175
1397
|
fi
|
|
1176
1398
|
|
|
1399
|
+
# An explicit rejection is honored, never converted into a selection.
|
|
1400
|
+
# This arm used to be absent, so "none" fell through to `*)` and became
|
|
1401
|
+
# top3[0] -- the founder-reported bug where typing "none" still built a
|
|
1402
|
+
# todo app. With no template, the user's own brief becomes the spec.
|
|
1403
|
+
if _qs_is_rejection "$pick"; then
|
|
1404
|
+
if [ -z "$brief" ]; then
|
|
1405
|
+
# Nothing to build from: no template AND no description. Refuse
|
|
1406
|
+
# rather than invent a spec or silently fall back to a template.
|
|
1407
|
+
printf '%sNo template selected and no description given; nothing to build.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1408
|
+
printf 'Re-run: loki quickstart "<what you want>"\n' >&2
|
|
1409
|
+
exit 2
|
|
1410
|
+
fi
|
|
1411
|
+
use_brief_as_spec=true
|
|
1412
|
+
# Reaching the picker at all means this is NOT an existing codebase
|
|
1413
|
+
# being changed: the brownfield arm skips the picker entirely, and
|
|
1414
|
+
# the classifier-override arm above already set "new". Rejecting a
|
|
1415
|
+
# template asks for a new project with no starting template.
|
|
1416
|
+
spec_mode="new"
|
|
1417
|
+
input_kind="change"
|
|
1418
|
+
template_name="no-template"
|
|
1419
|
+
printf ' No template. Using your description as the spec.\n\n'
|
|
1420
|
+
else
|
|
1177
1421
|
case "$pick" in
|
|
1178
1422
|
""|1) template_name="${top3[0]}";;
|
|
1179
1423
|
2) template_name="${top3[1]:-${top3[0]}}";;
|
|
1180
1424
|
3) template_name="${top3[2]:-${top3[0]}}";;
|
|
1181
|
-
*) template_name="${top3[0]}";; # any
|
|
1425
|
+
*) template_name="${top3[0]}";; # any unrecognized NON-rejection input
|
|
1182
1426
|
esac
|
|
1427
|
+
fi
|
|
1183
1428
|
|
|
1184
1429
|
fi
|
|
1185
1430
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1431
|
+
if [ "$use_brief_as_spec" != true ]; then
|
|
1432
|
+
local tdir; tdir="$(_qs_templates_dir)"
|
|
1433
|
+
prd_source="$tdir/$template_name.md"
|
|
1434
|
+
if [ ! -f "$prd_source" ]; then
|
|
1435
|
+
printf '%sTemplate file not found: %s%s\n' "$_QS_RED" "$prd_source" "$_QS_NC" >&2
|
|
1436
|
+
exit 1
|
|
1437
|
+
fi
|
|
1191
1438
|
fi
|
|
1192
1439
|
else
|
|
1193
1440
|
template_name="$(basename "$prd_source")"
|
|
1194
1441
|
fi
|
|
1195
1442
|
|
|
1443
|
+
# The change-request spec is staged OUTSIDE the working tree. It is derived
|
|
1444
|
+
# from what the user typed, not authored by them, so landing it in the CWD
|
|
1445
|
+
# would drop an unrequested file into an existing project -- and the ./prd.md
|
|
1446
|
+
# overwrite prompt below exists precisely to keep quickstart from writing
|
|
1447
|
+
# over a file the user already has. Staging in a temp file keeps the
|
|
1448
|
+
# brownfield path write-free in the user's directory.
|
|
1449
|
+
if [ "$use_brief_as_spec" = true ]; then
|
|
1450
|
+
# The .md suffix is load-bearing, not cosmetic. cmd_start classifies a
|
|
1451
|
+
# positional via detect_arg_type (loki:1675): an extensionless path is
|
|
1452
|
+
# only reached by the late `[ -e "$arg" ]` branch, and BOTH PRD
|
|
1453
|
+
# existence guards (loki:2962, loki:2985) are `case` arms matching
|
|
1454
|
+
# *.md|*.json|*.txt|*.yaml|*.yml, so an extensionless spec silently
|
|
1455
|
+
# skips the fail-fast checks. Naming it .md puts classification on the
|
|
1456
|
+
# explicit extension branch and under those guards.
|
|
1457
|
+
# BSD mktemp (stock macOS) does NOT substitute the X-run when a suffix
|
|
1458
|
+
# follows it: `mktemp foo.XXXXXX.md` creates the LITERAL file
|
|
1459
|
+
# "foo.XXXXXX.md". That is a fixed name, so a second concurrent
|
|
1460
|
+
# quickstart would fail to create it and the build would never start.
|
|
1461
|
+
# Create with a bare X-run (portable, unique), then rename to add .md.
|
|
1462
|
+
local _qs_stage=""
|
|
1463
|
+
_qs_stage="$(mktemp "${TMPDIR:-/tmp}/loki-quickstart-change.XXXXXX")" || {
|
|
1464
|
+
printf '%sCould not stage the change request.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1465
|
+
exit 1
|
|
1466
|
+
}
|
|
1467
|
+
if mv "$_qs_stage" "$_qs_stage.md" 2>/dev/null; then
|
|
1468
|
+
prd_source="$_qs_stage.md"
|
|
1469
|
+
else
|
|
1470
|
+
prd_source="$_qs_stage"
|
|
1471
|
+
fi
|
|
1472
|
+
# Not trap-cleaned on purpose: cmd_start EXECS the runner and never
|
|
1473
|
+
# returns here, and the runner must still be able to read this spec.
|
|
1474
|
+
# It lives in TMPDIR under a mktemp name, so the OS reaps it.
|
|
1475
|
+
_qs_write_change_prd "$prd_source" "$brief" "$spec_mode"
|
|
1476
|
+
fi
|
|
1477
|
+
|
|
1196
1478
|
# ----- Step 4 of 4: Review the plan (reuse the slice-A estimator) --------
|
|
1197
1479
|
printf '%sStep 4 of 4: Review the plan%s\n' "$_QS_BOLD" "$_QS_NC"
|
|
1198
1480
|
# The plan is ALWAYS rendered, on both paths, from the same real estimator.
|
|
@@ -1280,8 +1562,14 @@ cmd_quickstart() {
|
|
|
1280
1562
|
fi
|
|
1281
1563
|
|
|
1282
1564
|
# ----- Land the PRD at ./prd.md (design 3.6) ----------------------------
|
|
1565
|
+
# The change-request path deliberately lands NOTHING in the working tree.
|
|
1566
|
+
# The user asked for a change to code that already exists; dropping a
|
|
1567
|
+
# generated prd.md into their project is the unrequested write that made the
|
|
1568
|
+
# original bug worse. The staged spec is handed to cmd_start directly.
|
|
1283
1569
|
local target="./prd.md"
|
|
1284
|
-
if [
|
|
1570
|
+
if [ "$use_brief_as_spec" = true ]; then
|
|
1571
|
+
target="$prd_source"
|
|
1572
|
+
elif [ -e "$target" ]; then
|
|
1285
1573
|
local overwrite=""
|
|
1286
1574
|
# Non-interactive never asks and never overwrites. Leaving $overwrite
|
|
1287
1575
|
# empty falls into the existing suffix walk below, which is already the
|
|
@@ -1338,6 +1626,10 @@ cmd_quickstart() {
|
|
|
1338
1626
|
return 2
|
|
1339
1627
|
fi
|
|
1340
1628
|
rm -f "$preview_stage"
|
|
1629
|
+
elif [ "$use_brief_as_spec" = true ]; then
|
|
1630
|
+
# $target IS $prd_source here (the staged change request); copying a
|
|
1631
|
+
# file onto itself would truncate it.
|
|
1632
|
+
:
|
|
1341
1633
|
elif ! cp "$prd_source" "$target" 2>/dev/null; then
|
|
1342
1634
|
printf '%sCould not write the PRD to the current directory. Try a writable directory.%s\n' "$_QS_RED" "$_QS_NC" >&2
|
|
1343
1635
|
exit 1
|
|
@@ -1345,7 +1637,12 @@ cmd_quickstart() {
|
|
|
1345
1637
|
|
|
1346
1638
|
printf '\n'
|
|
1347
1639
|
printf 'Starting your build. Progress streams here in the terminal.\n'
|
|
1348
|
-
|
|
1640
|
+
if [ "$use_brief_as_spec" = true ]; then
|
|
1641
|
+
printf ' Change request: %s\n' "$brief"
|
|
1642
|
+
printf ' No file was written to your project directory.\n'
|
|
1643
|
+
else
|
|
1644
|
+
printf ' PRD saved to: %s\n' "$target"
|
|
1645
|
+
fi
|
|
1349
1646
|
# `loki dashboard` with no subcommand prints help and exits -- it does not
|
|
1350
1647
|
# start. Naming the bare command here sent every first-run user to a help
|
|
1351
1648
|
# screen at the exact moment they wanted to watch their build.
|
package/autonomy/run.sh
CHANGED
|
@@ -1181,10 +1181,12 @@ PERPETUAL_MODE=${LOKI_PERPETUAL_MODE:-false}
|
|
|
1181
1181
|
#
|
|
1182
1182
|
# MEASURED, not chosen. Real per-iteration wall clock across every recorded run
|
|
1183
1183
|
# on this machine: median 718s, max 1746s. So the 1000 default is an 8.3-DAY
|
|
1184
|
-
# ceiling
|
|
1185
|
-
# disabled
|
|
1186
|
-
#
|
|
1187
|
-
# "
|
|
1184
|
+
# ceiling. When this analysis was written it was also the ONLY backstop,
|
|
1185
|
+
# because the other two valves shipped disabled. That is no longer true of the
|
|
1186
|
+
# budget: since v9.42.0 LOKI_BUDGET_LIMIT defaults to "100.00" (see :527 --
|
|
1187
|
+
# note `-` not `:-`, so only an EXPLICIT "" means unlimited), and breaching it
|
|
1188
|
+
# pauses the run. LOKI_MAX_DURATION does still default to 0 (check_max_duration
|
|
1189
|
+
# returns "never stop" at :18278).
|
|
1188
1190
|
#
|
|
1189
1191
|
# It also contradicts our own documentation. SETUP.md tells users to RAISE the
|
|
1190
1192
|
# budget for large work with LOKI_MAX_ITERATIONS=40, and the demo uses 10 -- so
|
|
@@ -17860,6 +17862,43 @@ BUDGETUPD_EOF
|
|
|
17860
17862
|
return 0
|
|
17861
17863
|
fi
|
|
17862
17864
|
|
|
17865
|
+
# LIVE COST VISIBILITY. The loop has always computed cumulative spend here,
|
|
17866
|
+
# every iteration, and printed NOTHING until 80% of the cap. That silence is
|
|
17867
|
+
# the "predatory / un-capped" feeling users report about agentic tools: the
|
|
17868
|
+
# enforcement was never missing (the cap pauses at 100.00 by default), only
|
|
17869
|
+
# the visibility was, so spend was discoverable solely after the fact.
|
|
17870
|
+
#
|
|
17871
|
+
# Reuses the $current_cost computed above; deliberately NOT a second reader.
|
|
17872
|
+
# Two readers of one number disagreeing is the exact defect class fixed in
|
|
17873
|
+
# 405da630, and a per-iteration cost line is not worth reintroducing it.
|
|
17874
|
+
#
|
|
17875
|
+
# Safe on stdout: this function has exactly two non-comment references in
|
|
17876
|
+
# the file -- its own definition, and the single loop call site, which
|
|
17877
|
+
# tests the exit code and never captures output. Verified with a
|
|
17878
|
+
# comment-excluding grep plus a positive control.
|
|
17879
|
+
#
|
|
17880
|
+
# This comment deliberately does NOT spell out the call-site expression.
|
|
17881
|
+
# tests/test-max-duration.sh:151 counts that exact string across this file
|
|
17882
|
+
# and asserts there is exactly ONE, so quoting it here made the count 2 and
|
|
17883
|
+
# turned a green suite red -- a text guard firing on prose written to
|
|
17884
|
+
# explain it. Describe the call site; never reproduce it.
|
|
17885
|
+
#
|
|
17886
|
+
# log_info is additionally gated by _loki_log_enabled, so quiet modes stay
|
|
17887
|
+
# quiet.
|
|
17888
|
+
# The zero test must be NUMERIC. `[ "$current_cost" != "0" ]` is a string
|
|
17889
|
+
# compare and the python above emits "0.0", so a run with nothing recorded
|
|
17890
|
+
# would announce "Cost so far: $0.0" -- claiming a measurement we do not
|
|
17891
|
+
# have, which is the exact fabrication this codebase deletes on sight.
|
|
17892
|
+
#
|
|
17893
|
+
# No else-branch: BUDGET_LIMIT empty means unlimited, and check_budget_limit
|
|
17894
|
+
# returns at its first line in that case, so any "no cap set" arm here would
|
|
17895
|
+
# be unreachable code advertising a message that can never print. The
|
|
17896
|
+
# no-cap disclosure already lives at run-start (show_run_start_estimate).
|
|
17897
|
+
if [ -n "$current_cost" ] \
|
|
17898
|
+
&& awk -v c="$current_cost" 'BEGIN{exit !(c+0 > 0)}' 2>/dev/null; then
|
|
17899
|
+
log_info "Cost so far: \$${current_cost} of \$${BUDGET_LIMIT} cap (iteration ${ITERATION_COUNT:-0})."
|
|
17900
|
+
fi
|
|
17901
|
+
|
|
17863
17902
|
# Update budget.json with current usage (not exceeded)
|
|
17864
17903
|
if [ -n "$current_cost" ] && [ "$current_cost" != "0" ]; then
|
|
17865
17904
|
cat > ".loki/metrics/budget.json" << BUDGETUPD_EOF
|
package/dashboard/__init__.py
CHANGED
|
@@ -2444,7 +2444,11 @@ var LokiDashboard=(()=>{var Ie=Object.defineProperty;var zt=Object.getOwnPropert
|
|
|
2444
2444
|
PASSED
|
|
2445
2445
|
</div>
|
|
2446
2446
|
</div>
|
|
2447
|
-
`}
|
|
2447
|
+
`}_renderDisconnected(){return`<div class="journey-state" role="status">
|
|
2448
|
+
Not connected to the Loki API. No session numbers can be shown until it
|
|
2449
|
+
reconnects. Start it with <code>loki dashboard start</code>, or check the
|
|
2450
|
+
API URL in Settings.
|
|
2451
|
+
</div>`}_renderJourney(){if(this._journeyState==="unavailable")return'<div class="journey-state" role="status">Issue-to-PR evidence is unavailable. No readiness claim can be made.</div>';if(this._journeyState!=="ready"||!this._journeyProof)return'<div class="journey-state" role="status">No issue-to-PR receipt yet. Start an issue run to record its first result, proof, and PR state.</div>';let e=this._journeyProof,t=e?.facts?.journey;if(!t||!t.issue)return'<div class="journey-state" role="status">Latest receipt is not an issue run. Issue-to-PR readiness is not applicable.</div>';let i=t.time_to_first_result_sec,a=typeof i=="number"&&i>=0,s=t.first_result_kind==="code_change"?"verified patch":t.first_result_kind==="proposed_solution_plan"?"proposed solution":"result",r=a?`${Math.round(i)}s to ${s}`:"Not measured",o=typeof e?.honesty?.headline=="string"?e.honesty.headline:null,n=Array.isArray(e?.honesty?.degraded)?e.honesty.degraded.length:null,l=o||"Not evaluated",c=n===null?"Uncertainty not measured":n===0?"No recorded gaps":`${n} recorded gap${n===1?"":"s"}`,p=t.pull_request,u="Not prepared",m="";p&&p.state&&(u=String(p.state).replace(/_/g," "),this._isSafeWebUrl(p.url)&&(m=`<a class="journey-link" href="${this._escapeHtml(p.url)}" target="_blank" rel="noopener noreferrer">Open PR</a>`));let g=this._data.phase||(this._data.status==="running"?"In progress":"Run complete");return`
|
|
2448
2452
|
<section class="journey" aria-labelledby="journey-heading">
|
|
2449
2453
|
<div class="journey-heading" id="journey-heading">Issue to PR</div>
|
|
2450
2454
|
<div class="journey-steps">
|
|
@@ -2633,6 +2637,7 @@ var LokiDashboard=(()=>{var Ie=Object.defineProperty;var zt=Object.getOwnPropert
|
|
|
2633
2637
|
<span class="overview-title">Overview</span>
|
|
2634
2638
|
</div>
|
|
2635
2639
|
|
|
2640
|
+
${this._data.connected?`
|
|
2636
2641
|
${this._renderJourney()}
|
|
2637
2642
|
|
|
2638
2643
|
<div class="overview-grid">
|
|
@@ -2687,6 +2692,7 @@ var LokiDashboard=(()=>{var Ie=Object.defineProperty;var zt=Object.getOwnPropert
|
|
|
2687
2692
|
<div class="card-value small-text">${p}</div>
|
|
2688
2693
|
</div>
|
|
2689
2694
|
</div>
|
|
2695
|
+
`:this._renderDisconnected()}
|
|
2690
2696
|
</div>
|
|
2691
2697
|
`}};customElements.get("loki-overview")||customElements.define("loki-overview",X);var Ft=[{id:"pending",label:"Pending",status:"pending",color:"var(--loki-text-muted)"},{id:"in_progress",label:"In Progress",status:"in_progress",color:"var(--loki-blue)"},{id:"review",label:"In Review",status:"review",color:"var(--loki-purple)"},{id:"done",label:"Completed",status:"done",color:"var(--loki-green)"}],Z=class d extends v{static get observedAttributes(){return["api-url","project-id","theme","readonly"]}constructor(){super(),this._tasks=[],this._loading=!0,this._error=null,this._draggedTask=null,this._selectedTask=null,this._expandedCards=new Set,this._selectedTasks=new Set,this._bulkMode=!1,this._activeFilter="all",this._searchQuery="",this._visibleCounts={},this._api=null,this._state=N()}static get PAGE_SIZE(){return 10}_getVisibleCount(e){let t=this._visibleCounts[e];return typeof t=="number"&&t>0?t:d.PAGE_SIZE}_showMore(e){this._visibleCounts[e]=this._getVisibleCount(e)+d.PAGE_SIZE,this.render()}_columnIcon(e){switch(e){case"pending":return'<circle cx="12" cy="12" r="10"/>';case"in_progress":return'<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>';case"review":return'<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>';case"done":return'<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>';default:return'<circle cx="12" cy="12" r="10"/>'}}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadTasks()}disconnectedCallback(){super.disconnectedCallback(),this._teardownApiListeners()}_teardownApiListeners(){this._api&&this._onTaskEvent&&(this._api.removeEventListener(f.TASK_CREATED,this._onTaskEvent),this._api.removeEventListener(f.TASK_UPDATED,this._onTaskEvent),this._api.removeEventListener(f.TASK_DELETED,this._onTaskEvent))}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._teardownApiListeners(),this._setupApi(),this._loadTasks()),e==="project-id"&&this._loadTasks(),e==="theme"&&this._applyTheme())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e}),this._onTaskEvent=()=>this._loadTasks(),this._api.addEventListener(f.TASK_CREATED,this._onTaskEvent),this._api.addEventListener(f.TASK_UPDATED,this._onTaskEvent),this._api.addEventListener(f.TASK_DELETED,this._onTaskEvent)}async _loadTasks(){let e=this._api;this._loading=!0,this._error=null,this.render();try{let t=this.getAttribute("project-id"),i=t?{projectId:parseInt(t)}:{},a=await e.listTasks(i);if(e!==this._api)return;this._tasks=a;let s=this._state.get("localTasks")||[];s.length>0&&(this._tasks=[...this._tasks,...s.map(r=>({...r,isLocal:!0}))]),this._state.update({"cache.tasks":this._tasks},!1)}catch(t){if(e!==this._api)return;this._error=t.message,this._tasks=(this._state.get("localTasks")||[]).map(i=>({...i,isLocal:!0}))}this._loading=!1,this.render()}_getTasksByStatus(e){return this._getFilteredTasks().filter(i=>i.status?.toLowerCase().replace(/-/g,"_")===e)}_handleDragStart(e,t){this.hasAttribute("readonly")||(this._draggedTask=t,e.target.classList.add("dragging"),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",t.id.toString()))}_handleDragEnd(e){e.target.classList.remove("dragging"),this._draggedTask=null,this.shadowRoot.querySelectorAll(".kanban-tasks").forEach(t=>{t.classList.remove("drag-over")})}_handleDragOver(e){e.preventDefault(),e.dataTransfer.dropEffect="move"}_handleDragEnter(e){e.preventDefault(),e.currentTarget.classList.add("drag-over")}_handleDragLeave(e){e.currentTarget.contains(e.relatedTarget)||e.currentTarget.classList.remove("drag-over")}async _handleDrop(e,t){if(e.preventDefault(),e.currentTarget.classList.remove("drag-over"),!this._draggedTask||this.hasAttribute("readonly"))return;let i=this._draggedTask.id,a=this._tasks.find(r=>r.id===i);if(!a)return;let s=a.status;if(s!==t){a.status=t,this.render();try{a.isLocal?this._state.moveLocalTask(i,t):await this._api.moveTask(i,t,0),this.dispatchEvent(new CustomEvent("task-moved",{detail:{taskId:i,oldStatus:s,newStatus:t}}))}catch(r){a.status=s,this.render(),console.error("Failed to move task:",r)}}}_toggleCardExpand(e){this._expandedCards.has(e)?this._expandedCards.delete(e):this._expandedCards.add(e),this.render()}_toggleTaskSelection(e,t){t&&t.stopPropagation(),this._selectedTasks.has(e)?this._selectedTasks.delete(e):this._selectedTasks.add(e),this.render()}_toggleBulkMode(){this._bulkMode=!this._bulkMode,this._bulkMode||this._selectedTasks.clear(),this.render()}async _bulkMove(e){let t=[...this._selectedTasks];for(let i of t){let a=this._tasks.find(s=>String(s.id)===String(i));if(a&&a.status!==e)try{a.isLocal?this._state.moveLocalTask(i,e):await this._api.moveTask(i,e,0),a.status=e}catch(s){console.error("Failed to bulk move task:",i,s)}}this._selectedTasks.clear(),this._bulkMode=!1,this.render(),this._loadTasks()}async _bulkDelete(){let e=[...this._selectedTasks];for(let t of e)try{await this._api.deleteTask(t)}catch(i){console.error("Failed to delete task:",t,i)}this._selectedTasks.clear(),this._bulkMode=!1,this._loadTasks()}_setFilter(e){this._activeFilter=e,this._visibleCounts={},this.render()}_setSearch(e){this._searchQuery=e||"",this._visibleCounts={},this._renderTaskRegion();let t=this.shadowRoot.getElementById("task-search");if(t){t.focus();let i=t.value.length;try{t.setSelectionRange(i,i)}catch{}}}_getFilteredTasks(){let e=[...this._tasks],t=new Date,i=new Date(t.getFullYear(),t.getMonth(),t.getDate()),a=new Date(i.getTime()-7*24*60*60*1e3);switch(this._activeFilter){case"today":e=e.filter(r=>{let o=r.created_at?new Date(r.created_at):null;return o&&o>=i});break;case"this-week":e=e.filter(r=>{let o=r.created_at?new Date(r.created_at):null;return o&&o>=a});break;case"running":e=e.filter(r=>r.status==="in_progress");break;case"failed":e=e.filter(r=>r.status==="failed"||r.status==="error");break;default:break}let s=this._searchQuery.trim().toLowerCase();return s&&(e=e.filter(r=>[r.id,r.title,r.description,r.type].map(n=>String(n??"").toLowerCase()).join(" ").includes(s))),e}_openAddTaskModal(e="pending"){this.dispatchEvent(new CustomEvent("add-task",{detail:{status:e}}))}_openTaskDetail(e){this._selectedTask=e,this.render(),this.dispatchEvent(new CustomEvent("task-click",{detail:{task:e}}))}_closeTaskDetail(){this._selectedTask=null,this.render()}_renderMarkdown(e){return C(e)}_formatTimestamp(e){if(!e)return"";try{let t=new Date(e);return isNaN(t.getTime())?this._escapeHtml(String(e)):t.toLocaleString()}catch{return this._escapeHtml(String(e))}}_phaseClass(e){let t=String(e||"").toLowerCase();return["reason","plan","planning"].includes(t)?"phase-reason":["act","execute","execution","implement"].includes(t)?"phase-act":["reflect","review"].includes(t)?"phase-reflect":["verify","test","gate"].includes(t)?"phase-verify":"phase-default"}_logLevelClass(e){let t=String(e||"info").toLowerCase();return t==="error"||t==="fatal"?"log-error":t==="warn"||t==="warning"?"log-warn":t==="debug"||t==="trace"?"log-debug":"log-info"}_renderTaskDetailModal(e){if(!e)return"";let t=(e.priority||"medium").toLowerCase(),i=t.charAt(0).toUpperCase()+t.slice(1),a=e.status||"pending",s=a.replace(/_/g," ").replace(/\b\w/g,g=>g.toUpperCase()),r=e.metadata||{},o=Array.isArray(e.acceptance_criteria)?e.acceptance_criteria:[],n=Array.isArray(e.context_files)?e.context_files:[],l=e.specification||"",c=e.description||"",p=Array.isArray(e.notes)?e.notes:[],u=Array.isArray(e.logs)?e.logs:[],m=e.full_content||"";return`
|
|
2692
2698
|
<div class="modal-overlay" id="task-detail-overlay">
|
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.49.3
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
## doctor --json writes 108 bytes to stderr on a provider-less host
|
|
3
|
+
|
|
4
|
+
`bash autonomy/loki doctor --json` on a host with NO provider CLI emits:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
autonomy/loki: line 14066: required: command not found
|
|
8
|
+
autonomy/loki: line 14066: status: command not found
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Not user-affecting, and not a JSON defect.** stdout is 5176 bytes of valid
|
|
12
|
+
JSON; only stderr is polluted. Every `doctor --json` assertion in the repo
|
|
13
|
+
discards stderr (`tests/test-airgap-commands.sh:50`,
|
|
14
|
+
`tests/test-doctor-blocker-parity.sh:129,132`,
|
|
15
|
+
`tests/test-doctor-ci-gateable.sh:46,84,86`).
|
|
16
|
+
`tests/test-e2e-features.sh:104` was the lone outlier using `2>&1` and has been
|
|
17
|
+
brought into line.
|
|
18
|
+
|
|
19
|
+
**Attribution:** introduced this session. `git show 0416810d:autonomy/loki`
|
|
20
|
+
(v9.48.2) emits 0 bytes on the same host; HEAD emits 108. The region was last
|
|
21
|
+
touched by `56e03374 feat(doctor): show provider availability`.
|
|
22
|
+
|
|
23
|
+
**Six hypotheses tested and REFUTED, so the next person does not repeat them:**
|
|
24
|
+
|
|
25
|
+
1. Word-splitting of `$_provider_availability` at `:14066` -- the assignment is
|
|
26
|
+
correctly quoted; reproduced in isolation as OK.
|
|
27
|
+
2. `provider_availability_json` emitting to stderr -- 0 bytes, clean 353-byte
|
|
28
|
+
JSON in isolation.
|
|
29
|
+
3. Its callees `auto_detect_provider` / `check_provider_installed` -- both
|
|
30
|
+
silent.
|
|
31
|
+
4. The JSON value's content -- `required` and `status` appear 0 times in it.
|
|
32
|
+
5. The value being multi-line and breaking the env-assignment chain -- forcing
|
|
33
|
+
`LOKI_PROVIDER_AVAILABILITY=""` as a literal STILL reproduces.
|
|
34
|
+
6. `render_provider_availability` -- silent in isolation, and disabling the call
|
|
35
|
+
still reproduces.
|
|
36
|
+
|
|
37
|
+
**Also known:** `bash -n` parses the file clean; sourcing the file and calling
|
|
38
|
+
`cmd_doctor_json` directly emits 0 bytes, while executing the file emits 108.
|
|
39
|
+
That difference (streaming read vs single parse) is the strongest remaining
|
|
40
|
+
lead. The words `required` and `status` are python identifiers at `:14085-14098`
|
|
41
|
+
inside the `python3 -c "` string that opens at `:14069`.
|
|
42
|
+
|
package/docs/cost-controls.md
CHANGED
|
@@ -25,7 +25,7 @@ They bound different things, and a run that stalls needs all three.
|
|
|
25
25
|
|
|
26
26
|
| Cap | Bounds | Default |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| `LOKI_BUDGET_LIMIT` | Total spend, in USD |
|
|
28
|
+
| `LOKI_BUDGET_LIMIT` | Total spend, in USD | `100.00` (breaching pauses the run) |
|
|
29
29
|
| `LOKI_MAX_ITERATIONS` | Number of iterations | 1000 |
|
|
30
30
|
| `LOKI_MAX_DURATION` | Wall-clock time | unset (no cap) |
|
|
31
31
|
|
|
@@ -23,7 +23,7 @@ with nowhere to look them up.
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| `LOKI_PRD_FILE` | none | Path to a spec file, as an alternative to the positional argument. |
|
|
25
25
|
| `LOKI_MAX_ITERATIONS` | `1000` | Hard cap on iterations. Reaching it is a deterministic terminal failure, not a success. Also in `config schema`. |
|
|
26
|
-
| `LOKI_BUDGET_LIMIT` |
|
|
26
|
+
| `LOKI_BUDGET_LIMIT` | `100.00` | Spend cap in USD. Breaching it **pauses** the run (writes `.loki/PAUSE` and saves state); it never kills work or discards a deliverable. Remove `.loki/PAUSE` to resume, or raise the cap. Set to `""` for no cap. A warning is logged at 80%. |
|
|
27
27
|
| `LOKI_MAX_DURATION` | unset (no cap) | Wall-clock cap in seconds. Stops cleanly at the next iteration boundary with a `max_duration_reached` terminal status. `loki start --max-duration` also accepts `90m` / `2h`. |
|
|
28
28
|
| `LOKI_AUTO_CONFIRM` | unset | `true`/`false` to control prompts. Takes precedence over `CI`. |
|
|
29
29
|
| `LOKI_CONFIG_DUMP` | `0` | `1` prints the resolved configuration and exits **without starting a run or spending anything**. |
|