loki-mode 11.6.0 → 11.7.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/README.md +19 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/backlog.py +11 -2
- package/autonomy/lib/chat.sh +57 -1
- package/autonomy/lib/loki_yaml.py +8 -2
- package/autonomy/lib/workspace.py +267 -11
- package/autonomy/loki +315 -21
- package/autonomy/trigger-server.py +185 -2
- package/autonomy/verify.sh +4 -0
- package/completions/_loki +4 -0
- package/completions/loki.bash +20 -2
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +2 -1
- package/docs/DEPLOY-PLAN.md +17 -8
- package/docs/EVALUATING.md +8 -0
- package/docs/INSTALLATION.md +1 -1
- package/docs/WORKSPACES.md +15 -2
- package/docs/dev/architecture-reference.md +8 -0
- package/docs/sentry-intake.md +11 -0
- package/docs/v10/CLI-MODERN.md +1 -1
- package/loki-ts/dist/loki.js +1009 -975
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +618 -469
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/schemas/loki-yaml.schema.json +5 -2
- package/src/policies/engine.js +41 -2
- package/src/policies/types.js +49 -0
- package/web-app/dist/assets/{AdminPage-DhXdJNQk.js → AdminPage-S3BYeapm.js} +1 -1
- package/web-app/dist/assets/{Avatar-ixVj76fM.js → Avatar-C1rHwLgn.js} +1 -1
- package/web-app/dist/assets/{Badge-BGMSp22s.js → Badge-CzlnN5aQ.js} +1 -1
- package/web-app/dist/assets/{Button-pYJc32tG.js → Button-B1w5AeGC.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-C1JBBouA.js → CockpitPage-Dyeqjfkc.js} +1 -1
- package/web-app/dist/assets/{ComparePage-qD0NV4RK.js → ComparePage-C6y2GkGR.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C-yxO58j.js → ErrorBoundary-Dg2fcGkR.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-ByPzuE1S.js → EvidenceReceiptPanel-CxbyjvTj.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-BNv_2BEJ.js → GitHubIssuesPanel-Dpys1xDb.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CuLZQ9CS.js → GitHubPRsPanel-CRVjWoFl.js} +1 -1
- package/web-app/dist/assets/{HomePage-CS_FW8Je.js → HomePage-BKnv-oYw.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DwiNf1Wm.js → LoginPage-BGksmFRd.js} +1 -1
- package/web-app/dist/assets/{MagicPage-CmQybLPZ.js → MagicPage-OCvhp16M.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-D9ERdpv8.js → MetricsPage-CJOJpXWC.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-D9ccgdbF.js → NotFoundPage-C04p3QvN.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-B9Jslmgv.js → ProjectPage-BRB8txsY.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DnU99Go2.js → ProjectsPage-CoUYeQEh.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-DcTyx6wo.js → SettingsPage-BkjmJGfM.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-jEY9ghSr.js → ShowcasePage-B4vU9TjQ.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CSGltW-8.js → SystemSettingsPage-Z2jiVsni.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-gUuJXb9l.js → TeamsPage-Ulm8SISq.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-37Zhe--v.js → TemplatesPage-C_7MM6Ey.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-B09hRwaR.js → TerminalOutput-Cumd7Tgs.js} +1 -1
- package/web-app/dist/assets/{activity-q_4MY9J0.js → activity-CZAUkbiY.js} +1 -1
- package/web-app/dist/assets/{bell-X8OC_PFP.js → bell-BOvSXpEr.js} +1 -1
- package/web-app/dist/assets/{bot-C-N1PUOD.js → bot-DOTZxBYj.js} +1 -1
- package/web-app/dist/assets/{check-B2c76Jmp.js → check-BxpE0B5u.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BF76YLNw.js → chevron-left-D6KPvBOA.js} +1 -1
- package/web-app/dist/assets/{circle-alert-AeFKBD8h.js → circle-alert-Bajq86oe.js} +1 -1
- package/web-app/dist/assets/{clock-CiXRf-FH.js → clock-CnlHAVJc.js} +1 -1
- package/web-app/dist/assets/{cloud-DKQ8SRJ5.js → cloud-DjaymjwM.js} +1 -1
- package/web-app/dist/assets/{code-xml-DEb6jXWP.js → code-xml-BADEw2Ld.js} +1 -1
- package/web-app/dist/assets/{database-6W7e-E9c.js → database-B4aKb5Cq.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Ds2GOuyW.js → dollar-sign-DrldXpja.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CZU0jg52.js → file-code-corner-DVebDVR-.js} +1 -1
- package/web-app/dist/assets/{file-plus-DTVYnXCN.js → file-plus-BD-N_-Tl.js} +1 -1
- package/web-app/dist/assets/{globe-0mUzKYDe.js → globe-C7ieInC-.js} +1 -1
- package/web-app/dist/assets/{hammer-BMHZg3YX.js → hammer-BSugKIji.js} +1 -1
- package/web-app/dist/assets/{index-BvOTvjax.js → index-nLQgfvOn.js} +3 -3
- package/web-app/dist/assets/{layers-Df7TdcWB.js → layers-CHXxU2on.js} +1 -1
- package/web-app/dist/assets/{loader-circle-kPsVfrSY.js → loader-circle-BEEz0LbD.js} +1 -1
- package/web-app/dist/assets/{lock-CXYcbEc5.js → lock-CzQGfcnE.js} +1 -1
- package/web-app/dist/assets/{package-Cqhua1ID.js → package-CcHYQLGd.js} +1 -1
- package/web-app/dist/assets/{plus-lseXhsn-.js → plus-DdzgFEXn.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-OZriI6gD.js → refresh-cw-7x0HbH9Y.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CKCfjPg8.js → rotate-ccw-DBahX1Sp.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DoVyyfKb.js → scroll-text-BB8y8a9Z.js} +1 -1
- package/web-app/dist/assets/{server-DGy7TflW.js → server-CfkbW2MH.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BCzRcMXi.js → shield-alert-DUgD8rBF.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-C_znJd5g.js → thumbs-up-DGoyY6QC.js} +1 -1
- package/web-app/dist/assets/{trash-2-BosCbeeA.js → trash-2-B6xrSUMr.js} +1 -1
- package/web-app/dist/assets/{trending-down-CkR5zDkV.js → trending-down-B6RGKTOx.js} +1 -1
- package/web-app/dist/assets/{trending-up-DpUIfOH4.js → trending-up-B44DCrt1.js} +1 -1
- package/web-app/dist/assets/{upload-CH9aKc8L.js → upload-4kWm33ZY.js} +1 -1
- package/web-app/dist/assets/{usePolling-pzxrHJeg.js → usePolling-zkUtRQG6.js} +1 -1
- package/web-app/dist/assets/{user-B7OHa-VQ.js → user-04cEVIe2.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/autonomy/loki
CHANGED
|
@@ -1785,7 +1785,8 @@ show_help_all() {
|
|
|
1785
1785
|
echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
|
|
1786
1786
|
echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
|
|
1787
1787
|
echo " backlog <owner/repo> --all|--label X|--issues N,N Run every matching open issue, N in parallel"
|
|
1788
|
-
echo " workspace list | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=0 disables)"
|
|
1788
|
+
echo " workspace list | infer <name> | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=0 disables)"
|
|
1789
|
+
echo " start --workspace <name> \"<brief>\" Run a workspace; start --deploy [--health-url U] deploys after a VERIFIED run"
|
|
1789
1790
|
echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
|
|
1790
1791
|
echo ""
|
|
1791
1792
|
echo "Session:"
|
|
@@ -2542,6 +2543,71 @@ cmd_start() {
|
|
|
2542
2543
|
# version all return 0 without output), so no guard is needed here.
|
|
2543
2544
|
maybe_print_update_hint || true
|
|
2544
2545
|
|
|
2546
|
+
# PATH slice: --workspace NAME runs a multi-repo workspace through the one
|
|
2547
|
+
# workspace runner (no new logic here), and --deploy [--health-url U]
|
|
2548
|
+
# [--auto-rollback] chains the gated `loki deploy --execute` after a start
|
|
2549
|
+
# that ended 0. cmd_deploy owns every gate: no VERIFIED HEAD-anchored
|
|
2550
|
+
# receipt means a refusal, never a deploy.
|
|
2551
|
+
local _ps_ws="" _ps_deploy=0 _ps_health="" _ps_rb=0 _ps_rest=() _ps_brief=() _ps_a
|
|
2552
|
+
while [ $# -gt 0 ]; do
|
|
2553
|
+
case "$1" in
|
|
2554
|
+
--workspace)
|
|
2555
|
+
[ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--workspace requires a workspace name${NC}" >&2; exit 2; }
|
|
2556
|
+
_ps_ws="$2"; shift 2 ;;
|
|
2557
|
+
--workspace=*) _ps_ws="${1#*=}"; shift ;;
|
|
2558
|
+
--deploy) _ps_deploy=1; shift ;;
|
|
2559
|
+
--health-url)
|
|
2560
|
+
[ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--health-url requires a URL${NC}" >&2; exit 2; }
|
|
2561
|
+
_ps_health="$2"; shift 2 ;;
|
|
2562
|
+
--health-url=*) _ps_health="${1#*=}"; shift ;;
|
|
2563
|
+
--auto-rollback) _ps_rb=1; shift ;;
|
|
2564
|
+
*) _ps_rest+=("$1"); shift ;;
|
|
2565
|
+
esac
|
|
2566
|
+
done
|
|
2567
|
+
if { [ -n "$_ps_health" ] || [ "$_ps_rb" = 1 ]; } && [ "$_ps_deploy" != 1 ]; then
|
|
2568
|
+
echo -e "${RED}--health-url and --auto-rollback apply only with --deploy${NC}" >&2
|
|
2569
|
+
exit 2
|
|
2570
|
+
fi
|
|
2571
|
+
if [ -n "$_ps_ws" ]; then
|
|
2572
|
+
if [ "$_ps_deploy" = 1 ]; then
|
|
2573
|
+
echo -e "${RED}--workspace and --deploy cannot be combined: deploy each repo with loki deploy${NC}" >&2
|
|
2574
|
+
exit 2
|
|
2575
|
+
fi
|
|
2576
|
+
local _ps_skip=0
|
|
2577
|
+
for _ps_a in ${_ps_rest[@]+"${_ps_rest[@]}"}; do
|
|
2578
|
+
if [ "$_ps_skip" = 1 ]; then _ps_skip=0; continue; fi
|
|
2579
|
+
if [ "$_ps_skip" = 2 ]; then _ps_skip=0; _ps_brief+=("$_ps_a"); continue; fi
|
|
2580
|
+
case "$_ps_a" in
|
|
2581
|
+
--brief=*) _ps_brief+=("${_ps_a#*=}") ;;
|
|
2582
|
+
--brief) _ps_skip=2 ;;
|
|
2583
|
+
# Every value-taking cmd_start flag: its value is never brief text.
|
|
2584
|
+
--provider|--budget|--prd|--issue|--model|--max-cost|--isolation|--aider-model|--aider-flags|--cline-model|--compliance|--max-duration|--log-level|--bmad-project|--openspec|--mirofish-rounds|--mirofish-timeout|--output|--config|--vars|--env-file|--remote|--attempts|--max-wait) _ps_skip=1 ;;
|
|
2585
|
+
-*) ;;
|
|
2586
|
+
*) _ps_brief+=("$_ps_a") ;;
|
|
2587
|
+
esac
|
|
2588
|
+
done
|
|
2589
|
+
if [ "${#_ps_brief[@]}" -eq 0 ]; then
|
|
2590
|
+
echo -e "${RED}loki start --workspace $_ps_ws needs a brief or an issue ref${NC}" >&2
|
|
2591
|
+
exit 2
|
|
2592
|
+
fi
|
|
2593
|
+
cmd_workspace run "$_ps_ws" "${_ps_brief[*]}"
|
|
2594
|
+
return $?
|
|
2595
|
+
fi
|
|
2596
|
+
if [ "$_ps_deploy" = 1 ]; then
|
|
2597
|
+
local _ps_rc=0
|
|
2598
|
+
( cmd_start ${_ps_rest[@]+"${_ps_rest[@]}"} ) || _ps_rc=$?
|
|
2599
|
+
if [ "$_ps_rc" -ne 0 ]; then
|
|
2600
|
+
echo -e "${RED}start exited $_ps_rc: not deploying${NC}" >&2
|
|
2601
|
+
return "$_ps_rc"
|
|
2602
|
+
fi
|
|
2603
|
+
local _ps_dargs=(--execute)
|
|
2604
|
+
[ -n "$_ps_health" ] && _ps_dargs+=(--health-url "$_ps_health")
|
|
2605
|
+
[ "$_ps_rb" = 1 ] && _ps_dargs+=(--auto-rollback)
|
|
2606
|
+
cmd_deploy "${_ps_dargs[@]}"
|
|
2607
|
+
return $?
|
|
2608
|
+
fi
|
|
2609
|
+
set -- ${_ps_rest[@]+"${_ps_rest[@]}"}
|
|
2610
|
+
|
|
2545
2611
|
local args=()
|
|
2546
2612
|
local prd_file=""
|
|
2547
2613
|
local provider=""
|
|
@@ -9238,33 +9304,36 @@ _deploy_detect_type() {
|
|
|
9238
9304
|
# paraphrase). Project-dependent dirs stay as `<...>` placeholders the user fills.
|
|
9239
9305
|
# Note: the Fly CLI binary is `flyctl` but the canonical deploy verb is
|
|
9240
9306
|
# `fly deploy` -- "fly" not "flyctl" here is CORRECT, intentional, not a typo.
|
|
9307
|
+
# Row format: provider|cli|command|docs|rollback. rollback is a documented verb
|
|
9308
|
+
# (vercel rollback, fly releases rollback <captured-version>) or `manual:<note>`
|
|
9309
|
+
# when the provider documents no CLI rollback.
|
|
9241
9310
|
_deploy_options_for_type() {
|
|
9242
9311
|
local type="${1:-}"
|
|
9243
9312
|
case "$type" in
|
|
9244
9313
|
nextjs)
|
|
9245
|
-
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli"
|
|
9246
|
-
printf '%s\n' "Netlify|netlify|netlify deploy --prod|https://docs.netlify.com/cli/get-started
|
|
9314
|
+
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli|vercel rollback"
|
|
9315
|
+
printf '%s\n' "Netlify|netlify|netlify deploy --prod|https://docs.netlify.com/cli/get-started/|manual:Netlify documents no CLI rollback; restore the previous deploy from the Netlify dashboard"
|
|
9247
9316
|
# fly not flyctl - correct: binary is flyctl, deploy verb is `fly deploy`.
|
|
9248
|
-
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install
|
|
9317
|
+
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install/|fly releases rollback <captured-version>"
|
|
9249
9318
|
;;
|
|
9250
9319
|
static)
|
|
9251
|
-
printf '%s\n' "Netlify|netlify|netlify deploy --prod --dir=<build-output>|https://docs.netlify.com/cli/get-started
|
|
9252
|
-
printf '%s\n' "Cloudflare Pages|wrangler|wrangler pages deploy <build-output>|https://developers.cloudflare.com/workers/wrangler/install-and-update
|
|
9253
|
-
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli"
|
|
9320
|
+
printf '%s\n' "Netlify|netlify|netlify deploy --prod --dir=<build-output>|https://docs.netlify.com/cli/get-started/|manual:Netlify documents no CLI rollback; restore the previous deploy from the Netlify dashboard"
|
|
9321
|
+
printf '%s\n' "Cloudflare Pages|wrangler|wrangler pages deploy <build-output>|https://developers.cloudflare.com/workers/wrangler/install-and-update/|manual:Cloudflare rollback is not automated here; roll back from the dashboard or with the provider CLI yourself"
|
|
9322
|
+
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli|vercel rollback"
|
|
9254
9323
|
;;
|
|
9255
9324
|
docker)
|
|
9256
9325
|
# fly not flyctl - correct (see note above).
|
|
9257
|
-
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install
|
|
9258
|
-
printf '%s\n' "Cloudflare|wrangler|wrangler deploy|https://developers.cloudflare.com/workers/wrangler/install-and-update
|
|
9326
|
+
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install/|fly releases rollback <captured-version>"
|
|
9327
|
+
printf '%s\n' "Cloudflare|wrangler|wrangler deploy|https://developers.cloudflare.com/workers/wrangler/install-and-update/|manual:Cloudflare rollback is not automated here; roll back from the dashboard or with the provider CLI yourself"
|
|
9259
9328
|
;;
|
|
9260
9329
|
node)
|
|
9261
9330
|
# fly not flyctl - correct (see note above).
|
|
9262
|
-
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install
|
|
9263
|
-
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli"
|
|
9331
|
+
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install/|fly releases rollback <captured-version>"
|
|
9332
|
+
printf '%s\n' "Vercel|vercel|vercel --prod|https://vercel.com/docs/cli|vercel rollback"
|
|
9264
9333
|
;;
|
|
9265
9334
|
python)
|
|
9266
9335
|
# fly not flyctl - correct (see note above).
|
|
9267
|
-
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install
|
|
9336
|
+
printf '%s\n' "Fly.io|flyctl|fly deploy|https://fly.io/docs/flyctl/install/|fly releases rollback <captured-version>"
|
|
9268
9337
|
;;
|
|
9269
9338
|
esac
|
|
9270
9339
|
return 0
|
|
@@ -9339,9 +9408,9 @@ _deploy_print_install_hint() {
|
|
|
9339
9408
|
echo "Loki never accesses your cloud account or runs deploy for you -- you run"
|
|
9340
9409
|
echo "the printed command. Install one of the following, then re-run 'loki deploy':"
|
|
9341
9410
|
echo ""
|
|
9342
|
-
local options provider cli command docs
|
|
9411
|
+
local options provider cli command docs _rb
|
|
9343
9412
|
options="$(_deploy_options_for_type "$type")"
|
|
9344
|
-
while IFS='|' read -r provider cli command docs; do
|
|
9413
|
+
while IFS='|' read -r provider cli command docs _rb; do
|
|
9345
9414
|
[ -n "$provider" ] || continue
|
|
9346
9415
|
case "$cli" in
|
|
9347
9416
|
vercel) echo " Vercel: brew install vercel | ${docs}" ;;
|
|
@@ -9373,8 +9442,8 @@ _deploy_print_cloud_options() {
|
|
|
9373
9442
|
|
|
9374
9443
|
local printed=false
|
|
9375
9444
|
local first_cmd=""
|
|
9376
|
-
local provider cli command docs
|
|
9377
|
-
while IFS='|' read -r provider cli command docs; do
|
|
9445
|
+
local provider cli command docs _rb
|
|
9446
|
+
while IFS='|' read -r provider cli command docs _rb; do
|
|
9378
9447
|
[ -n "$cli" ] || continue
|
|
9379
9448
|
if command -v "$cli" >/dev/null 2>&1; then
|
|
9380
9449
|
if [ "$printed" != true ]; then
|
|
@@ -9636,6 +9705,9 @@ PYANCHOR
|
|
|
9636
9705
|
# verified tree, never tearing an environment down. Word-boundary matched so
|
|
9637
9706
|
# "wrangler pages deploy dist" is not caught by "destroy" appearing in a path.
|
|
9638
9707
|
_deploy_is_destructive() {
|
|
9708
|
+
# Allowlisted rollback verbs (the only ones auto-rollback may run) are never
|
|
9709
|
+
# destructive: they restore a prior release rather than tear anything down.
|
|
9710
|
+
printf '%s' "${1:-}" | grep -Eq '^(vercel rollback|fly releases rollback [0-9]+)$' && return 1
|
|
9639
9711
|
printf '%s' "${1:-}" | grep -Eqi '(^|[^[:alnum:]_-])(delete|destroy|teardown|down|drop|rm|remove|purge|prune|wipe|scale-down)([^[:alnum:]_-]|$)'
|
|
9640
9712
|
}
|
|
9641
9713
|
|
|
@@ -9694,12 +9766,16 @@ _deploy_record() {
|
|
|
9694
9766
|
# "refused" needs no value: a refusal never reaches this function, so the
|
|
9695
9767
|
# existence of a record already means one of the two above.
|
|
9696
9768
|
local anchor_state="${6:-anchored}"
|
|
9769
|
+
# Post-deploy health and rollback facts. "unchecked" never implies healthy.
|
|
9770
|
+
local health_status="${7:-unchecked}" health_url="${8:-}"
|
|
9771
|
+
local rb_attempted="${9:-false}" rb_exit="${10:-}"
|
|
9697
9772
|
local ddir="$dir/.loki/deploys"
|
|
9698
9773
|
[ -d "${LOKI_DIR:-}" ] && ddir="${LOKI_DIR}/deploys"
|
|
9699
9774
|
mkdir -p "$ddir" 2>/dev/null || return 0
|
|
9700
9775
|
local f="$ddir/$(date -u +%Y%m%dT%H%M%SZ)-$$.json"
|
|
9701
9776
|
DR_CMD="$command" DR_RC="$rc" DR_RUN="$run_id" DR_ANCHOR="$anchor" DR_OUT="$f" \
|
|
9702
|
-
DR_STATE="$anchor_state" \
|
|
9777
|
+
DR_STATE="$anchor_state" DR_HS="$health_status" DR_HU="$health_url" \
|
|
9778
|
+
DR_RBA="$rb_attempted" DR_RBX="$rb_exit" \
|
|
9703
9779
|
python3 -c '
|
|
9704
9780
|
import json, os, datetime
|
|
9705
9781
|
state = os.environ.get("DR_STATE") or "anchored"
|
|
@@ -9713,6 +9789,10 @@ json.dump({
|
|
|
9713
9789
|
# Spelled out so a reader months later does not have to know the vocabulary
|
|
9714
9790
|
# to judge how strong the authorization was.
|
|
9715
9791
|
"anchor_proven": state == "anchored",
|
|
9792
|
+
"health_status": os.environ.get("DR_HS") or "unchecked",
|
|
9793
|
+
"health_url": os.environ.get("DR_HU") or "",
|
|
9794
|
+
"rollback_attempted": os.environ.get("DR_RBA") == "true",
|
|
9795
|
+
"rollback_exit": int(os.environ["DR_RBX"]) if os.environ.get("DR_RBX", "") != "" else None,
|
|
9716
9796
|
}, open(os.environ["DR_OUT"], "w"), indent=2)
|
|
9717
9797
|
' 2>/dev/null || return 0
|
|
9718
9798
|
printf '%s' "$f"
|
|
@@ -9726,9 +9806,9 @@ json.dump({
|
|
|
9726
9806
|
# differs from the one the advisory showed. `command -v` only; no CLI is run.
|
|
9727
9807
|
_deploy_first_installed_command() {
|
|
9728
9808
|
local dir="${1:-.}" type="${2:-}"
|
|
9729
|
-
local options provider cli command docs
|
|
9809
|
+
local options provider cli command docs _rb
|
|
9730
9810
|
options="$(_deploy_options_for_type "$type")"
|
|
9731
|
-
while IFS='|' read -r provider cli command docs; do
|
|
9811
|
+
while IFS='|' read -r provider cli command docs _rb; do
|
|
9732
9812
|
[ -n "$cli" ] || continue
|
|
9733
9813
|
if command -v "$cli" >/dev/null 2>&1; then
|
|
9734
9814
|
printf '%s' "$command"
|
|
@@ -9741,6 +9821,110 @@ EOF
|
|
|
9741
9821
|
return 0
|
|
9742
9822
|
}
|
|
9743
9823
|
|
|
9824
|
+
# _deploy_rollback_for <type> <command>
|
|
9825
|
+
# Echoes the rollback column of the first option row whose deploy command is
|
|
9826
|
+
# <command>, or "" when none. Pure table lookup; runs nothing.
|
|
9827
|
+
_deploy_rollback_for() {
|
|
9828
|
+
local type="${1:-}" want="${2:-}" provider cli command docs rb
|
|
9829
|
+
while IFS='|' read -r provider cli command docs rb; do
|
|
9830
|
+
[ -n "$cli" ] || continue
|
|
9831
|
+
if [ "$command" = "$want" ]; then
|
|
9832
|
+
printf '%s' "$rb"
|
|
9833
|
+
return 0
|
|
9834
|
+
fi
|
|
9835
|
+
done <<EOF
|
|
9836
|
+
$(_deploy_options_for_type "$type")
|
|
9837
|
+
EOF
|
|
9838
|
+
printf '%s' ""
|
|
9839
|
+
return 0
|
|
9840
|
+
}
|
|
9841
|
+
|
|
9842
|
+
# _deploy_fly_capture_version <dir>
|
|
9843
|
+
# Echoes the newest Fly release version BEFORE a deploy so a rollback has a real
|
|
9844
|
+
# target, or "" when it cannot be read (then no rollback is attempted).
|
|
9845
|
+
_deploy_fly_capture_version() {
|
|
9846
|
+
local dir="${1:-.}" out
|
|
9847
|
+
out="$( cd "$dir" && fly releases --json 2>/dev/null )" || { printf '%s' ""; return 0; }
|
|
9848
|
+
printf '%s' "$out" | python3 -c '
|
|
9849
|
+
import json, sys
|
|
9850
|
+
try:
|
|
9851
|
+
rel = json.load(sys.stdin)
|
|
9852
|
+
v = rel[0].get("Version") if rel else None
|
|
9853
|
+
print(int(v) if v is not None else "", end="")
|
|
9854
|
+
except Exception:
|
|
9855
|
+
print("", end="")
|
|
9856
|
+
' 2>/dev/null || printf '%s' ""
|
|
9857
|
+
return 0
|
|
9858
|
+
}
|
|
9859
|
+
|
|
9860
|
+
# _deploy_probe_health <url>
|
|
9861
|
+
# Bounded poll: echoes the last HTTP status, returns 0 on any 2xx. Attempts and
|
|
9862
|
+
# spacing are env-tunable (LOKI_DEPLOY_HEALTH_ATTEMPTS / _INTERVAL) for tests.
|
|
9863
|
+
_deploy_probe_health() {
|
|
9864
|
+
local url="${1:-}" attempts="${LOKI_DEPLOY_HEALTH_ATTEMPTS:-5}" interval="${LOKI_DEPLOY_HEALTH_INTERVAL:-3}"
|
|
9865
|
+
local i=0 code="000"
|
|
9866
|
+
case "$attempts" in ''|*[!0-9]*) attempts=5 ;; esac
|
|
9867
|
+
case "$interval" in ''|*[!0-9]*) interval=3 ;; esac
|
|
9868
|
+
while [ "$i" -lt "$attempts" ]; do
|
|
9869
|
+
i=$((i + 1))
|
|
9870
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$url" 2>/dev/null)" || code="000"
|
|
9871
|
+
case "$code" in 2??) printf '%s' "$code"; return 0 ;; esac
|
|
9872
|
+
[ "$i" -lt "$attempts" ] && [ "$interval" -gt 0 ] && sleep "$interval"
|
|
9873
|
+
done
|
|
9874
|
+
printf '%s' "$code"
|
|
9875
|
+
return 1
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
# _deploy_policy_refusal <dir>
|
|
9879
|
+
# Echoes a refusal reason when the policy file does not permit a deploy, else "".
|
|
9880
|
+
# No policy file: "" (unchanged behaviour). File present and node or check.js
|
|
9881
|
+
# unavailable: refuse (fail-closed, mirrors run.sh check_policy). Exit 0 allows;
|
|
9882
|
+
# 2 (approval needed) and anything else refuse. Scoped env, no secrets forwarded.
|
|
9883
|
+
_deploy_policy_refusal() {
|
|
9884
|
+
local dir="${1:-.}" gdir="${LOKI_DATA_DIR:-$HOME/.loki}"
|
|
9885
|
+
if [ ! -f "$dir/.loki/policies.json" ] && [ ! -f "$dir/.loki/policies.yaml" ] \
|
|
9886
|
+
&& [ ! -f "$gdir/policies.json" ] && [ ! -f "$gdir/policies.yaml" ]; then
|
|
9887
|
+
printf '%s' ""
|
|
9888
|
+
return 0
|
|
9889
|
+
fi
|
|
9890
|
+
local checkjs="${SKILL_DIR:-}/src/policies/check.js"
|
|
9891
|
+
[ -f "$checkjs" ] || checkjs="${PROJECT_DIR:-.}/src/policies/check.js"
|
|
9892
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
9893
|
+
printf '%s' "policy file present but node is unavailable: cannot evaluate policy (fail-closed)"
|
|
9894
|
+
return 0
|
|
9895
|
+
fi
|
|
9896
|
+
if [ ! -f "$checkjs" ]; then
|
|
9897
|
+
printf '%s' "policy file present but check.js is missing (fail-closed)"
|
|
9898
|
+
return 0
|
|
9899
|
+
fi
|
|
9900
|
+
local out rc=0
|
|
9901
|
+
out="$(env -i PATH="$PATH" HOME="$HOME" LOKI_PROJECT_DIR="$dir" \
|
|
9902
|
+
${LOKI_DATA_DIR:+LOKI_DATA_DIR="$LOKI_DATA_DIR"} \
|
|
9903
|
+
node "$checkjs" pre_deployment '{"action":"deploy"}' 2>/dev/null)" || rc=$?
|
|
9904
|
+
if [ "$rc" -eq 0 ]; then
|
|
9905
|
+
# Exit 0 also means "nothing to evaluate". Deploy needs explicit permission:
|
|
9906
|
+
# the policy must set autonomy or actions.deploy, else refuse.
|
|
9907
|
+
local setsperm=""
|
|
9908
|
+
setsperm="$(env -i PATH="$PATH" HOME="$HOME" LOKI_PROJECT_DIR="$dir" \
|
|
9909
|
+
${LOKI_DATA_DIR:+LOKI_DATA_DIR="$LOKI_DATA_DIR"} \
|
|
9910
|
+
node -e 'var E=require(process.argv[1].replace(/check\.js$/,"engine.js")).PolicyEngine;var e=new E(process.env.LOKI_PROJECT_DIR);var p=e._policies;p=p&&(p.policies||p);var a=p&&p.actions&&typeof p.actions==="object"?p.actions.deploy:undefined;process.stdout.write(p&&(p.autonomy!==undefined||a!==undefined)?"yes":"");try{e.destroy()}catch(x){}process.exit(0)' "$checkjs" 2>/dev/null)" || setsperm=""
|
|
9911
|
+
if [ "$setsperm" != "yes" ]; then
|
|
9912
|
+
printf '%s' "policy file sets no autonomy/actions.deploy for deploy (no policy permission)"
|
|
9913
|
+
else
|
|
9914
|
+
printf '%s' ""
|
|
9915
|
+
fi
|
|
9916
|
+
return 0
|
|
9917
|
+
fi
|
|
9918
|
+
local why
|
|
9919
|
+
why="$(printf '%s' "$out" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("reason",""),end="")' 2>/dev/null || printf '')"
|
|
9920
|
+
if [ "$rc" -eq 2 ]; then
|
|
9921
|
+
printf '%s' "policy requires approval for deploy: ${why:-no reason given}"
|
|
9922
|
+
else
|
|
9923
|
+
printf '%s' "policy denied deploy: ${why:-policy check failed (fail-closed)}"
|
|
9924
|
+
fi
|
|
9925
|
+
return 0
|
|
9926
|
+
}
|
|
9927
|
+
|
|
9744
9928
|
# _deploy_try_execute <dir> <type>
|
|
9745
9929
|
# The ONLY place a deploy command is ever run. Evaluates the gate, and either
|
|
9746
9930
|
# executes + records, or REFUSES naming every failed check. Returns the deploy
|
|
@@ -9750,6 +9934,8 @@ EOF
|
|
|
9750
9934
|
# refusal still leaves the user with exactly the output they get today.
|
|
9751
9935
|
_deploy_try_execute() {
|
|
9752
9936
|
local dir="${1:-.}" type="${2:-}"
|
|
9937
|
+
# Set by cmd_deploy (dynamic scope); empty/false when called any other way.
|
|
9938
|
+
local health_url="${_LOKI_DEPLOY_HEALTH_URL:-}" auto_rollback="${_LOKI_DEPLOY_AUTO_ROLLBACK:-false}"
|
|
9753
9939
|
|
|
9754
9940
|
local command
|
|
9755
9941
|
command="$(_deploy_first_installed_command "$dir" "$type")"
|
|
@@ -9827,18 +10013,73 @@ _deploy_try_execute() {
|
|
|
9827
10013
|
# The string is NOT user input: it comes from the hardcoded
|
|
9828
10014
|
# _deploy_options_for_type table, and the only path here has already passed
|
|
9829
10015
|
# the destructive-operation check in _deploy_gate.
|
|
10016
|
+
# Resolve the rollback target BEFORE deploying: after it, the previous Fly
|
|
10017
|
+
# release is no longer the newest one and cannot be recovered.
|
|
10018
|
+
local rb_cmd="" rb_note=""
|
|
10019
|
+
if [ -n "$health_url" ]; then
|
|
10020
|
+
local rb_raw
|
|
10021
|
+
rb_raw="$(_deploy_rollback_for "$type" "$command")"
|
|
10022
|
+
case "$rb_raw" in
|
|
10023
|
+
manual:*) rb_note="${rb_raw#manual:}" ;;
|
|
10024
|
+
*"<captured-version>"*)
|
|
10025
|
+
local fv
|
|
10026
|
+
fv="$(_deploy_fly_capture_version "$dir")"
|
|
10027
|
+
if [ -n "$fv" ]; then rb_cmd="${rb_raw//<captured-version>/$fv}"
|
|
10028
|
+
else rb_note="could not read the current Fly release before deploying, so no rollback target is known"; fi
|
|
10029
|
+
;;
|
|
10030
|
+
"") rb_note="no rollback verb is documented for this command" ;;
|
|
10031
|
+
*) rb_cmd="$rb_raw" ;;
|
|
10032
|
+
esac
|
|
10033
|
+
fi
|
|
10034
|
+
|
|
9830
10035
|
local rc=0
|
|
9831
10036
|
( cd "$dir" && eval "$command" ) || rc=$?
|
|
9832
10037
|
|
|
10038
|
+
# Health probe only after a deploy that exited 0. Without a URL, or after a
|
|
10039
|
+
# failed deploy, status is "unchecked": never a claim the service is healthy.
|
|
10040
|
+
local health_status="unchecked" rb_attempted=false rb_exit="" hcode=""
|
|
10041
|
+
if [ "$rc" -eq 0 ] && [ -n "$health_url" ]; then
|
|
10042
|
+
echo "Probing health: ${health_url}"
|
|
10043
|
+
if hcode="$(_deploy_probe_health "$health_url")"; then
|
|
10044
|
+
health_status="healthy"
|
|
10045
|
+
else
|
|
10046
|
+
health_status="FAILED_HEALTH"
|
|
10047
|
+
fi
|
|
10048
|
+
fi
|
|
10049
|
+
|
|
10050
|
+
if [ "$health_status" = "FAILED_HEALTH" ]; then
|
|
10051
|
+
echo -e "${RED}FAILED_HEALTH: ${health_url} did not return 2xx (last status ${hcode:-000}).${NC}" >&2
|
|
10052
|
+
if [ "$auto_rollback" = true ] && [ -n "$rb_cmd" ] && ! _deploy_is_destructive "$rb_cmd"; then
|
|
10053
|
+
echo "Rolling back: ${rb_cmd}" >&2
|
|
10054
|
+
rb_attempted=true
|
|
10055
|
+
rb_exit=0
|
|
10056
|
+
( cd "$dir" && eval "$rb_cmd" ) || rb_exit=$?
|
|
10057
|
+
if [ "$rb_exit" -eq 0 ]; then
|
|
10058
|
+
echo "Rollback command exited 0. Health of the rolled-back service was NOT re-checked." >&2
|
|
10059
|
+
else
|
|
10060
|
+
echo -e "${RED}Rollback command exited ${rb_exit}.${NC}" >&2
|
|
10061
|
+
fi
|
|
10062
|
+
elif [ "$auto_rollback" = true ]; then
|
|
10063
|
+
echo " No automatic rollback ran: ${rb_note:-no allowlisted rollback command}." >&2
|
|
10064
|
+
elif [ -n "$rb_cmd" ]; then
|
|
10065
|
+
echo " Re-run with --auto-rollback to roll back automatically, or run: ${rb_cmd}" >&2
|
|
10066
|
+
fi
|
|
10067
|
+
fi
|
|
10068
|
+
|
|
9833
10069
|
local rec
|
|
9834
10070
|
rec="$(_deploy_record "$dir" "$command" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
|
|
9835
|
-
"$anchor_state")"
|
|
10071
|
+
"$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit")"
|
|
9836
10072
|
echo ""
|
|
9837
10073
|
if [ "$rc" -eq 0 ]; then
|
|
9838
10074
|
echo -e "${GREEN}Deploy command exited 0.${NC}"
|
|
9839
10075
|
else
|
|
9840
10076
|
echo -e "${RED}Deploy command exited ${rc}.${NC}" >&2
|
|
9841
10077
|
fi
|
|
10078
|
+
if [ "$health_status" = "healthy" ]; then
|
|
10079
|
+
echo -e "${GREEN}Health check passed${NC} (HTTP ${hcode})."
|
|
10080
|
+
elif [ "$health_status" = "unchecked" ]; then
|
|
10081
|
+
echo "Health: unchecked (no --health-url, or the deploy did not exit 0). Not a claim the service is healthy."
|
|
10082
|
+
fi
|
|
9842
10083
|
if [ -n "$rec" ]; then
|
|
9843
10084
|
echo "Deploy record: ${rec}"
|
|
9844
10085
|
else
|
|
@@ -9852,6 +10093,10 @@ _deploy_try_execute() {
|
|
|
9852
10093
|
echo " Authorizing receipt run_id: ${run_id:-unknown}; anchor: ${anchor:-unknown}" >&2
|
|
9853
10094
|
echo " Record this yourself: a deploy with no record of itself is unevidenced." >&2
|
|
9854
10095
|
fi
|
|
10096
|
+
# A deploy that exited 0 but failed its health probe is a failed deploy.
|
|
10097
|
+
if [ "$rc" -eq 0 ] && [ "$health_status" = "FAILED_HEALTH" ]; then
|
|
10098
|
+
return 4
|
|
10099
|
+
fi
|
|
9855
10100
|
return "$rc"
|
|
9856
10101
|
}
|
|
9857
10102
|
|
|
@@ -9937,6 +10182,11 @@ _deploy_gate() {
|
|
|
9937
10182
|
[ -n "$dirty" ] && reasons="${reasons}${dirty}
|
|
9938
10183
|
"
|
|
9939
10184
|
|
|
10185
|
+
local pol
|
|
10186
|
+
pol="$(_deploy_policy_refusal "$dir")"
|
|
10187
|
+
[ -n "$pol" ] && reasons="${reasons}${pol}
|
|
10188
|
+
"
|
|
10189
|
+
|
|
9940
10190
|
printf '%s' "$reasons"
|
|
9941
10191
|
return 0
|
|
9942
10192
|
}
|
|
@@ -9957,6 +10207,8 @@ cmd_deploy() {
|
|
|
9957
10207
|
# below sets it. Same rule --execute already follows -- no env var or config
|
|
9958
10208
|
# file alone weakens this gate.
|
|
9959
10209
|
local LOKI_DEPLOY_ALLOW_UNANCHORED=false
|
|
10210
|
+
# Per-invocation like --execute; local so an ambient env var cannot set them.
|
|
10211
|
+
local _LOKI_DEPLOY_HEALTH_URL="" _LOKI_DEPLOY_AUTO_ROLLBACK=false
|
|
9960
10212
|
|
|
9961
10213
|
# Arg parse (mirror cmd_preview: lenient on unknown args -> no behavior drift).
|
|
9962
10214
|
while [ $# -gt 0 ]; do
|
|
@@ -9964,7 +10216,7 @@ cmd_deploy() {
|
|
|
9964
10216
|
--help|-h|help)
|
|
9965
10217
|
echo -e "${BOLD}Loki Mode -- deploy command advisory (print-only by default)${NC}"
|
|
9966
10218
|
echo ""
|
|
9967
|
-
echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute]"
|
|
10219
|
+
echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute] [--health-url URL [--auto-rollback]]"
|
|
9968
10220
|
echo ""
|
|
9969
10221
|
echo "Detects your project type and your installed cloud CLI (and any"
|
|
9970
10222
|
echo "CI/CD pipeline), then PRINTS the exact deploy command for YOU to run."
|
|
@@ -9997,6 +10249,10 @@ cmd_deploy() {
|
|
|
9997
10249
|
echo " --allow-unanchored does NOT bypass it."
|
|
9998
10250
|
echo " 4. You passed --execute on THIS invocation. No env var or config"
|
|
9999
10251
|
echo " file alone can opt in."
|
|
10252
|
+
echo " 5. Policy permits it. If .loki/policies.json (or the global one) exists,"
|
|
10253
|
+
echo " deploy needs autonomy 'operate' or actions.deploy 'allow'. An"
|
|
10254
|
+
echo " approval-required, denied, unevaluable or permission-less (neither key set) policy REFUSES. No policy"
|
|
10255
|
+
echo " file leaves the gate unchanged."
|
|
10000
10256
|
echo ""
|
|
10001
10257
|
echo "It FAILS CLOSED: if a check cannot be evaluated at all (no receipt,"
|
|
10002
10258
|
echo "signing key or keys unavailable, anchor unresolvable, not a git repo), that is a"
|
|
@@ -10016,6 +10272,17 @@ cmd_deploy() {
|
|
|
10016
10272
|
echo "resolved anchor, and anchor_state -- so a reader can tell later"
|
|
10017
10273
|
echo "whether the anchor was proven or explicitly overridden."
|
|
10018
10274
|
echo ""
|
|
10275
|
+
echo "Health check and rollback (only with --execute):"
|
|
10276
|
+
echo " --health-url URL After a deploy that exits 0, poll URL (bounded). Any"
|
|
10277
|
+
echo " non-2xx marks the deploy FAILED_HEALTH and exits 4."
|
|
10278
|
+
echo " Without a URL, health is recorded as 'unchecked',"
|
|
10279
|
+
echo " never as healthy."
|
|
10280
|
+
echo " --auto-rollback On FAILED_HEALTH run the provider's documented rollback:"
|
|
10281
|
+
echo " 'vercel rollback' or 'fly releases rollback <version"
|
|
10282
|
+
echo " captured before the deploy>'. Netlify and Cloudflare"
|
|
10283
|
+
echo " print a manual note. Needs --health-url. The rolled-back"
|
|
10284
|
+
echo " service is not re-probed."
|
|
10285
|
+
echo ""
|
|
10019
10286
|
echo "Options:"
|
|
10020
10287
|
echo " --dir <path> Project directory to scan (default: current dir)"
|
|
10021
10288
|
echo " --no-clip Do not copy the idiomatic command to the clipboard"
|
|
@@ -10035,6 +10302,15 @@ cmd_deploy() {
|
|
|
10035
10302
|
# never persisted and never settable by config alone.
|
|
10036
10303
|
LOKI_DEPLOY_ALLOW_UNANCHORED=true
|
|
10037
10304
|
;;
|
|
10305
|
+
--health-url)
|
|
10306
|
+
case "${2:-}" in
|
|
10307
|
+
http://*|https://*) _LOKI_DEPLOY_HEALTH_URL="$2"; shift ;;
|
|
10308
|
+
*) echo "loki deploy --health-url requires an http(s) URL" >&2; return 1 ;;
|
|
10309
|
+
esac
|
|
10310
|
+
;;
|
|
10311
|
+
--auto-rollback)
|
|
10312
|
+
_LOKI_DEPLOY_AUTO_ROLLBACK=true
|
|
10313
|
+
;;
|
|
10038
10314
|
--dir)
|
|
10039
10315
|
# Guard against a missing OR flag-shaped value: an unguarded
|
|
10040
10316
|
# shift when --dir is the last arg underflows under set -e, and a
|
|
@@ -10052,6 +10328,14 @@ cmd_deploy() {
|
|
|
10052
10328
|
shift
|
|
10053
10329
|
done
|
|
10054
10330
|
|
|
10331
|
+
if [ "$_LOKI_DEPLOY_AUTO_ROLLBACK" = true ] && [ -z "$_LOKI_DEPLOY_HEALTH_URL" ]; then
|
|
10332
|
+
echo "loki deploy --auto-rollback requires --health-url (no health signal, no rollback)" >&2
|
|
10333
|
+
return 1
|
|
10334
|
+
fi
|
|
10335
|
+
if [ "$do_execute" != true ] && [ -n "$_LOKI_DEPLOY_HEALTH_URL" ]; then
|
|
10336
|
+
echo "Note: --health-url and --auto-rollback only apply with --execute; ignored on this advisory run." >&2
|
|
10337
|
+
fi
|
|
10338
|
+
|
|
10055
10339
|
# Resolve to '.' if the chosen dir does not exist (honest, no crash).
|
|
10056
10340
|
[ -d "$dir" ] || dir="."
|
|
10057
10341
|
|
|
@@ -17846,6 +18130,15 @@ cmd_verify() {
|
|
|
17846
18130
|
--pubkey|--pubkey=*) exec "$_LOKI_SCRIPT_DIR/../bin/loki" verify "$@" ;;
|
|
17847
18131
|
esac
|
|
17848
18132
|
done
|
|
18133
|
+
# PATH slice: a workspace composite (composite.json, or --seal-composite RUN_DIR) is
|
|
18134
|
+
# verified by the engine10 verifier directly; bin/loki sends a bare verify back here.
|
|
18135
|
+
for _pk_arg in "$@"; do
|
|
18136
|
+
case "$_pk_arg" in
|
|
18137
|
+
composite.json|*/composite.json|--seal-composite|--seal-composite=*)
|
|
18138
|
+
_loki_bun_delegate engine10 verify "$@"
|
|
18139
|
+
return $? ;;
|
|
18140
|
+
esac
|
|
18141
|
+
done
|
|
17849
18142
|
# --fast: deterministic checks only, in milliseconds. Measured on this repo
|
|
17850
18143
|
# (1,932 tracked files): 298 ms cold, 87 ms warm, 19 ms diff-scoped, against
|
|
17851
18144
|
# an 11,040 ms shell-based baseline.
|
|
@@ -24459,6 +24752,7 @@ except Exception as e:
|
|
|
24459
24752
|
echo " lessons [--json] PR review lessons with their uses and outcomes"
|
|
24460
24753
|
echo " learn <o/r#PR> Learn lessons from a merged PR's review comments"
|
|
24461
24754
|
echo " forget <prl-id> Delete one PR lesson and print its text"
|
|
24755
|
+
echo " brief [--task T] Print the lessons and learnings block injected into a run"
|
|
24462
24756
|
echo ""
|
|
24463
24757
|
echo "Memory System Commands:"
|
|
24464
24758
|
echo " index [rebuild] Show or rebuild the memory index layer"
|