loki-mode 9.48.2 → 9.49.2
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/loki +137 -12
- package/autonomy/provider-offer.sh +47 -0
- package/autonomy/quickstart.sh +306 -9
- 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/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-DpV2vW3y.js} +1 -1
- package/web-app/dist/assets/{Avatar-CTUlr_Xf.js → Avatar-BXeMYK1_.js} +1 -1
- package/web-app/dist/assets/{Badge-BQeXvmg8.js → Badge-D8WddQk1.js} +1 -1
- package/web-app/dist/assets/{Button-Dc6-P6pz.js → Button-CFzTTVeJ.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BspOJ3Fv.js → CockpitPage-D1eQe9QF.js} +4 -4
- package/web-app/dist/assets/{ComparePage-CFXIM--Z.js → ComparePage-uX3OPphr.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C7vQYDW9.js → ErrorBoundary-wE3oxaG6.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CqjsjTOs.js → EvidenceReceiptPanel-B_Wouz6N.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DyuNDMgD.js → GitHubIssuesPanel-DRlEeFuw.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BtAq20xF.js → GitHubPRsPanel-CCv6_I74.js} +2 -2
- package/web-app/dist/assets/{HomePage-BuVFOrO8.js → HomePage-EG-jq8nR.js} +1 -1
- package/web-app/dist/assets/{LoginPage-CuhhrxGb.js → LoginPage-CEOnBICH.js} +1 -1
- package/web-app/dist/assets/{MagicPage-D2KZPX3F.js → MagicPage-DeWaFdsG.js} +1 -1
- package/web-app/dist/assets/MetricsPage-CG8CuSSJ.js +6 -0
- package/web-app/dist/assets/{NotFoundPage-CTnnaWlL.js → NotFoundPage-Mz5rOat6.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-uJFaoM8-.js → ProjectPage-D2Fh6tVw.js} +8 -8
- package/web-app/dist/assets/{ProjectsPage-8lKAOBIW.js → ProjectsPage-B8cRWkWa.js} +2 -2
- package/web-app/dist/assets/{SettingsPage-D1BBd5qy.js → SettingsPage-DKpsst6x.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CmQ0n8iO.js → ShowcasePage-qab065PM.js} +1 -1
- package/web-app/dist/assets/SystemSettingsPage-9qmEH2Y6.js +1 -0
- package/web-app/dist/assets/{TeamsPage-BS0TtVUa.js → TeamsPage-CW6lvW-0.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C2zPnnf2.js → TemplatesPage-B-a61T6a.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Dle1klqq.js → TerminalOutput-CJy13rAn.js} +1 -1
- package/web-app/dist/assets/{activity-DQp5U9f9.js → activity-CA4moIvC.js} +1 -1
- package/web-app/dist/assets/{bell-BqkTmgL7.js → bell-JyOeobLN.js} +1 -1
- package/web-app/dist/assets/{bot-DILhaIGH.js → bot-Cu4UwP5w.js} +1 -1
- package/web-app/dist/assets/{check-BC9x7oPm.js → check-Drzd8-9c.js} +1 -1
- package/web-app/dist/assets/{chevron-left--yqvAfjx.js → chevron-left-ClvdXm3Y.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DOH6PcgZ.js → circle-alert-CstyZfKG.js} +1 -1
- package/web-app/dist/assets/{clock-DKSDFsE6.js → clock-Bnz6tlQN.js} +1 -1
- package/web-app/dist/assets/{cloud-DRBU3JYs.js → cloud-IPIgE4P3.js} +1 -1
- package/web-app/dist/assets/{code-xml-D2LqszR9.js → code-xml-BOZbQiOt.js} +1 -1
- package/web-app/dist/assets/{database-D7ceedlh.js → database-CHclPjog.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-D0A47egY.js → dollar-sign-D6RFNgdZ.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CH_Qbtjo.js → file-code-corner-CNayCvOY.js} +1 -1
- package/web-app/dist/assets/{file-plus-CUNMjHk9.js → file-plus-BmVfFwsX.js} +1 -1
- package/web-app/dist/assets/folder-open-ChzGfLRr.js +11 -0
- package/web-app/dist/assets/{globe-gZnaGg8N.js → globe-DtXgCG3Z.js} +1 -1
- package/web-app/dist/assets/{hammer-BhJC5DMK.js → hammer-DCa0I_7V.js} +1 -1
- package/web-app/dist/assets/{index-BMmYYws0.js → index-Cu-njJ8o.js} +3 -3
- package/web-app/dist/assets/index-D98GzaW8.css +1 -0
- package/web-app/dist/assets/{layers-glXeoUbJ.js → layers-DxIhn7NZ.js} +1 -1
- package/web-app/dist/assets/{lightbulb-gXn8H0Lf.js → lightbulb-D7Pzhd06.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Do2ke8xm.js → loader-circle-DXgc_u5I.js} +1 -1
- package/web-app/dist/assets/{lock-Bgplb1_n.js → lock-qRAVU53b.js} +1 -1
- package/web-app/dist/assets/{mail-j4Lo8hfB.js → mail-CHe3KyCw.js} +1 -1
- package/web-app/dist/assets/{minus-dhrzKSK5.js → minus-B8p0FoYZ.js} +1 -1
- package/web-app/dist/assets/{package-CInWQQ97.js → package-C5LDn8SI.js} +1 -1
- package/web-app/dist/assets/{plus-BpYvoLlZ.js → plus-CRd_FHOA.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-F6koqEgF.js → refresh-cw-DKoSN8s3.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CQ2W5S8f.js → rotate-ccw-Nf8zB8G0.js} +1 -1
- package/web-app/dist/assets/{save-DMCYGm06.js → save-B8ytQLgW.js} +1 -1
- package/web-app/dist/assets/{server-Bj8EX4Z1.js → server-D_VRDkaD.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CiYuulCU.js → shield-alert-PsDkSyFD.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BqEh1puP.js → thumbs-up-Da1QQhqo.js} +11 -6
- package/web-app/dist/assets/{trash-2-CCwouZJJ.js → trash-2-ChoylpGQ.js} +1 -1
- package/web-app/dist/assets/{trending-down-Cx1vhE4o.js → trending-down-CX2fUeho.js} +1 -1
- package/web-app/dist/assets/{trending-up-BUhVYJ2T.js → trending-up-CGyYgZH7.js} +1 -1
- package/web-app/dist/assets/{upload-D5IsZeFM.js → upload-KkeVyg67.js} +1 -1
- package/web-app/dist/assets/{usePolling-CJbfhFcH.js → usePolling-C5f4xrXD.js} +1 -1
- package/web-app/dist/assets/{user-BRLhezsD.js → user-WCGccIST.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
|
@@ -61,7 +61,7 @@ jobs:
|
|
|
61
61
|
# Pinned to a TAG, not @main. A moving ref would hand every user
|
|
62
62
|
# whatever main holds at the moment their issue is labeled, including a
|
|
63
63
|
# half-landed release. Bump this line deliberately.
|
|
64
|
-
uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.
|
|
64
|
+
uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.49.2
|
|
65
65
|
env:
|
|
66
66
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
67
67
|
with:
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v9.
|
|
6
|
+
# Loki Mode v9.49.2
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v9.
|
|
473
|
+
**v9.49.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.49.2
|
package/autonomy/loki
CHANGED
|
@@ -13538,10 +13538,34 @@ except Exception:
|
|
|
13538
13538
|
"$HOME/.cline/skills/loki-mode:Cline CLI"
|
|
13539
13539
|
"$HOME/.aider/skills/loki-mode:Aider CLI"
|
|
13540
13540
|
)
|
|
13541
|
+
# SEVERITY DEPENDS ON WHOSE SKILL IT IS (v9.48.x). A dangling skill symlink
|
|
13542
|
+
# used to block unconditionally, so a stale link left by an old checkout
|
|
13543
|
+
# path exited 1 on a HEALTHY machine -- while the Provider Availability
|
|
13544
|
+
# section three lines above said that same provider was "not installed".
|
|
13545
|
+
# Doctor contradicted itself on one screen and told the user to fix a tool
|
|
13546
|
+
# they do not have.
|
|
13547
|
+
#
|
|
13548
|
+
# The link only blocks when it belongs to the provider a build would
|
|
13549
|
+
# actually launch. Anything else is inert state: still reported, with the
|
|
13550
|
+
# dangling target and the fix, but not a reason to refuse to start.
|
|
13551
|
+
#
|
|
13552
|
+
# `|| true` is mandatory under `set -euo pipefail`: the accessor returns
|
|
13553
|
+
# non-zero when no provider resolves (a bare call would abort doctor), and
|
|
13554
|
+
# an empty id then makes every entry non-blocking -- the same direction the
|
|
13555
|
+
# No-AI-provider blocker above already covers.
|
|
13556
|
+
local _selected_provider=""
|
|
13557
|
+
if declare -f effective_provider_id >/dev/null 2>&1; then
|
|
13558
|
+
_selected_provider="$(effective_provider_id 2>/dev/null || true)"
|
|
13559
|
+
fi
|
|
13541
13560
|
for entry in "${skill_dirs[@]}"; do
|
|
13542
13561
|
local sdir="${entry%%:*}"
|
|
13543
13562
|
local sname="${entry#*:}"
|
|
13544
13563
|
local short_path="${sdir/$HOME/~}"
|
|
13564
|
+
# Provider id from the directory, never a parallel list: `.claude` ->
|
|
13565
|
+
# `claude`. A fifth hand-maintained list is how the lists in this repo
|
|
13566
|
+
# have drifted from auto_detect_provider before (v8.76.0, v8.82.0).
|
|
13567
|
+
local _sid="${sdir#"$HOME"/.}"
|
|
13568
|
+
_sid="${_sid%%/*}"
|
|
13545
13569
|
if [ -f "$sdir/SKILL.md" ]; then
|
|
13546
13570
|
echo -e " ${GREEN}PASS${NC} $sname ${DIM}$short_path${NC}"
|
|
13547
13571
|
pass_count=$((pass_count + 1))
|
|
@@ -13560,10 +13584,22 @@ except Exception:
|
|
|
13560
13584
|
_target=$(ls -ld "$sdir" 2>/dev/null | sed -e 's/^[^>]*-> //')
|
|
13561
13585
|
[ -n "$_target" ] || _target="unknown"
|
|
13562
13586
|
fi
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13587
|
+
# Blocking ONLY for the provider a build would actually use. The
|
|
13588
|
+
# non-blocking arm is a WARN rather than a non-counted FAIL on
|
|
13589
|
+
# purpose: a red FAIL badge that never reaches `N failed` or the
|
|
13590
|
+
# `Blocking (N)` list makes the summary disagree with the screen.
|
|
13591
|
+
# Either way the dangling target and the fix are still printed --
|
|
13592
|
+
# the stale link is reported, never silently swallowed.
|
|
13593
|
+
if [ -n "$_selected_provider" ] && [ "$_sid" = "$_selected_provider" ]; then
|
|
13594
|
+
echo -e " ${RED}FAIL${NC} $sname ${DIM}(broken symlink -> $_target)${NC}"
|
|
13595
|
+
echo -e " ${YELLOW}Fix: loki setup-skill${NC}"
|
|
13596
|
+
_doctor_block "$sname is a broken symlink. Fix: loki setup-skill"
|
|
13597
|
+
fail_count=$((fail_count + 1))
|
|
13598
|
+
else
|
|
13599
|
+
echo -e " ${YELLOW}WARN${NC} $sname ${DIM}(broken symlink -> $_target)${NC}"
|
|
13600
|
+
echo -e " ${YELLOW}Fix: loki setup-skill${NC}"
|
|
13601
|
+
warn_count=$((warn_count + 1))
|
|
13602
|
+
fi
|
|
13567
13603
|
else
|
|
13568
13604
|
echo -e " ${YELLOW}WARN${NC} $sname ${DIM}(not found - run 'loki setup-skill')${NC}"
|
|
13569
13605
|
warn_count=$((warn_count + 1))
|
|
@@ -14183,14 +14219,26 @@ memory = {
|
|
|
14183
14219
|
#
|
|
14184
14220
|
# CAUTION: this block lives inside a DOUBLE-QUOTED python3 -c program, so no
|
|
14185
14221
|
# apostrophes and no double quotes anywhere here, comments included.
|
|
14222
|
+
#
|
|
14223
|
+
# SEVERITY MIRRORS THE TEXT PATH: a dangling link is a failure only for the
|
|
14224
|
+
# provider a build would actually use. Read from the SAME payload the text
|
|
14225
|
+
# route uses (LOKI_PROVIDER_AVAILABILITY, set by cmd_doctor_json), parsed here
|
|
14226
|
+
# rather than via a second env var so the two cannot disagree. LOKI_PROVIDER
|
|
14227
|
+
# wins for the same reason effective_provider_id honors it.
|
|
14186
14228
|
_SKILL_ENTRIES = (
|
|
14187
|
-
('Claude Code', '.claude/skills/loki-mode'),
|
|
14188
|
-
('Codex CLI', '.codex/skills/loki-mode'),
|
|
14189
|
-
('Cline CLI', '.cline/skills/loki-mode'),
|
|
14190
|
-
('Aider CLI', '.aider/skills/loki-mode'),
|
|
14229
|
+
('Claude Code', '.claude/skills/loki-mode', 'claude'),
|
|
14230
|
+
('Codex CLI', '.codex/skills/loki-mode', 'codex'),
|
|
14231
|
+
('Cline CLI', '.cline/skills/loki-mode', 'cline'),
|
|
14232
|
+
('Aider CLI', '.aider/skills/loki-mode', 'aider'),
|
|
14191
14233
|
)
|
|
14234
|
+
try:
|
|
14235
|
+
_sk_pa = os.environ.get('LOKI_PROVIDER_AVAILABILITY', '').strip()
|
|
14236
|
+
_sk_selected = (json.loads(_sk_pa).get('selected') or '') if _sk_pa else ''
|
|
14237
|
+
except Exception:
|
|
14238
|
+
_sk_selected = ''
|
|
14239
|
+
_sk_selected = os.environ.get('LOKI_PROVIDER', '') or _sk_selected
|
|
14192
14240
|
skills = []
|
|
14193
|
-
for _sk_name, _sk_rel in _SKILL_ENTRIES:
|
|
14241
|
+
for _sk_name, _sk_rel, _sk_id in _SKILL_ENTRIES:
|
|
14194
14242
|
_sk_dir = os.path.join(os.path.expanduser('~'), _sk_rel)
|
|
14195
14243
|
# JSON is commonly archived or forwarded to automation. Keep the useful
|
|
14196
14244
|
# user-relative location without exposing the machine-specific home path.
|
|
@@ -14200,8 +14248,14 @@ for _sk_name, _sk_rel in _SKILL_ENTRIES:
|
|
|
14200
14248
|
'detail': None, 'required': 'required'})
|
|
14201
14249
|
elif os.path.islink(_sk_dir):
|
|
14202
14250
|
# islink is true for a DANGLING link (lstat-based), which is exactly
|
|
14203
|
-
# the broken case the text path reports
|
|
14204
|
-
|
|
14251
|
+
# the broken case the text path reports. fail only for the effective
|
|
14252
|
+
# provider; otherwise warn, matching the text path badge for badge.
|
|
14253
|
+
# `required` stays 'required' in both arms: an absent link has always
|
|
14254
|
+
# been warn+required, so the warn arm introduces no new shape, and a
|
|
14255
|
+
# consumer keys on `status`.
|
|
14256
|
+
_sk_blocking = bool(_sk_selected) and _sk_id == _sk_selected
|
|
14257
|
+
skills.append({'name': _sk_name, 'path': _sk_json_path,
|
|
14258
|
+
'status': 'fail' if _sk_blocking else 'warn',
|
|
14205
14259
|
'detail': 'broken symlink. Fix: loki setup-skill',
|
|
14206
14260
|
'required': 'required'})
|
|
14207
14261
|
else:
|
|
@@ -17358,7 +17412,78 @@ if manifests:
|
|
|
17358
17412
|
echo -e "${RED}Error: No repositories found matching: ${multi_repo}${NC}"
|
|
17359
17413
|
return 1
|
|
17360
17414
|
fi
|
|
17361
|
-
#
|
|
17415
|
+
# ORCHESTRATION (v9.49.0): --multi-repo now MIGRATES each discovered
|
|
17416
|
+
# repository, one at a time, instead of only listing them.
|
|
17417
|
+
#
|
|
17418
|
+
# WHY A WRAPPER AND NOT SURGERY INSIDE THIS FUNCTION: cmd_migrate_start
|
|
17419
|
+
# is self-contained per codebase_path (manifest, secret scan, phases)
|
|
17420
|
+
# and was measured to have 20 `return`s and ZERO `exit`s, so it is
|
|
17421
|
+
# safe to call in a loop -- a failing repo returns a code instead of
|
|
17422
|
+
# killing the run. Recursing once per repo reuses every existing gate
|
|
17423
|
+
# rather than reimplementing the pipeline.
|
|
17424
|
+
#
|
|
17425
|
+
# PARTIAL-FAILURE SEMANTICS: continue-and-report, decided on the
|
|
17426
|
+
# codebase's own precedent, not preference:
|
|
17427
|
+
# - the task schema already has `blocked` as a first-class state
|
|
17428
|
+
# alongside `completed` (mcp/server.py:888), so "some units did not
|
|
17429
|
+
# finish" is already expressible
|
|
17430
|
+
# - run.sh carries 562 best-effort sites: the house style is degrade
|
|
17431
|
+
# honestly, do not abort
|
|
17432
|
+
# - fail-closed is reserved for DEPENDENCY violations (a migrate phase
|
|
17433
|
+
# refusing to run without its predecessor's artifacts, :17575), and
|
|
17434
|
+
# independent repositories have no such dependency on each other
|
|
17435
|
+
# One repo failing therefore must not deny the other four their result.
|
|
17436
|
+
# The per-repo status table plus a non-zero aggregate exit code is what
|
|
17437
|
+
# makes that honest rather than silent.
|
|
17438
|
+
#
|
|
17439
|
+
# LOKI_MULTI_REPO_SEQUENTIAL=0 is NOT offered: these share a migrations
|
|
17440
|
+
# dir and a dashboard port, and parallel runs would interleave output
|
|
17441
|
+
# into an unreadable report.
|
|
17442
|
+
if [ "$repo_count" -gt 1 ] && [ "${_LOKI_MR_CHILD:-0}" != "1" ]; then
|
|
17443
|
+
echo -e "${BOLD}Multi-repo migration${NC} ($repo_count repositories, sequential)"
|
|
17444
|
+
echo "---"
|
|
17445
|
+
for repo in "${repos[@]}"; do echo " - $repo"; done
|
|
17446
|
+
echo ""
|
|
17447
|
+
|
|
17448
|
+
local _mr_ok=0 _mr_bad=0
|
|
17449
|
+
local _mr_rows=""
|
|
17450
|
+
local _mr_repo _mr_rc
|
|
17451
|
+
for _mr_repo in "${repos[@]}"; do
|
|
17452
|
+
echo ""
|
|
17453
|
+
echo -e "${CYAN}=== [$((_mr_ok + _mr_bad + 1))/$repo_count] $_mr_repo ===${NC}"
|
|
17454
|
+
_mr_rc=0
|
|
17455
|
+
# _LOKI_MR_CHILD guards the recursion: the child sees the same
|
|
17456
|
+
# --multi-repo argument and must NOT re-enter this block.
|
|
17457
|
+
_LOKI_MR_CHILD=1 cmd_migrate_start \
|
|
17458
|
+
"$_mr_repo" "$target" "$plan_only" "$phase" "$parallel" \
|
|
17459
|
+
"$compliance" "$dry_run" "$resume" "" "$export_report" \
|
|
17460
|
+
"$no_dashboard" "$no_docs" || _mr_rc=$?
|
|
17461
|
+
if [ "$_mr_rc" -eq 0 ]; then
|
|
17462
|
+
_mr_ok=$((_mr_ok + 1))
|
|
17463
|
+
_mr_rows="${_mr_rows} OK ${_mr_repo}"$'\n'
|
|
17464
|
+
else
|
|
17465
|
+
_mr_bad=$((_mr_bad + 1))
|
|
17466
|
+
_mr_rows="${_mr_rows} BLOCKED ${_mr_repo} (exit ${_mr_rc})"$'\n'
|
|
17467
|
+
fi
|
|
17468
|
+
done
|
|
17469
|
+
|
|
17470
|
+
echo ""
|
|
17471
|
+
echo -e "${BOLD}Multi-repo result${NC}"
|
|
17472
|
+
echo "---"
|
|
17473
|
+
printf '%s' "$_mr_rows"
|
|
17474
|
+
echo ""
|
|
17475
|
+
echo " ${_mr_ok} of ${repo_count} migrated, ${_mr_bad} blocked."
|
|
17476
|
+
if [ "$_mr_bad" -gt 0 ]; then
|
|
17477
|
+
# A non-zero exit is what keeps "continue" from becoming
|
|
17478
|
+
# "silently tolerate": CI and `loki next` both branch on it.
|
|
17479
|
+
echo -e "${YELLOW}Some repositories did not complete. Re-run just those, or inspect them with 'loki why'.${NC}"
|
|
17480
|
+
return 1
|
|
17481
|
+
fi
|
|
17482
|
+
return 0
|
|
17483
|
+
fi
|
|
17484
|
+
|
|
17485
|
+
# HONESTY (single-repo, or a --multi-repo glob that matched exactly one):
|
|
17486
|
+
# this flag DISCOVERS repositories; it does not migrate them.
|
|
17362
17487
|
# `repos[]` is populated here and never referenced again -- everything
|
|
17363
17488
|
# below operates on the single "$codebase_path". The old message said
|
|
17364
17489
|
# "Multi-repo migration (3 repositories)" and listed all three, then
|
|
@@ -464,6 +464,49 @@ render_provider_availability() {
|
|
|
464
464
|
) || return 1
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
# effective_provider_id: print the provider id a build would ACTUALLY use, or
|
|
468
|
+
# nothing when none is resolvable. Exit status is 0 only when an id was printed.
|
|
469
|
+
#
|
|
470
|
+
# WHY IT EXISTS. Doctor grades a dangling skill symlink, and the severity of
|
|
471
|
+
# that depends on WHOSE skill it is. A stale link for a provider the user does
|
|
472
|
+
# not use is inert state; the same link for the provider a build will launch is
|
|
473
|
+
# a real blocker. Both the text route and the Bun route need that one id, and
|
|
474
|
+
# both previously re-derived it inside their own subshells (the two callers
|
|
475
|
+
# below). Deriving it a third and fourth time is exactly the drift this file's
|
|
476
|
+
# header exists to prevent, so it is extracted here once.
|
|
477
|
+
#
|
|
478
|
+
# LOKI_PROVIDER WINS. render_provider_availability prints "override with
|
|
479
|
+
# LOKI_PROVIDER" two lines above, so honoring auto-detection alone would grade
|
|
480
|
+
# a skill link against a provider the very same screen says is not in use.
|
|
481
|
+
# The override is NOT validated against installed state on purpose: an explicit
|
|
482
|
+
# LOKI_PROVIDER is the user's stated intent, and a build would carry it through.
|
|
483
|
+
#
|
|
484
|
+
# DEGRADES SILENTLY, like its two siblings: a missing or unsourceable loader
|
|
485
|
+
# prints nothing and returns non-zero, and the caller falls back to treating
|
|
486
|
+
# every entry as non-blocking rather than erroring.
|
|
487
|
+
effective_provider_id() {
|
|
488
|
+
if [ -n "${LOKI_PROVIDER:-}" ]; then
|
|
489
|
+
printf '%s\n' "$LOKI_PROVIDER"
|
|
490
|
+
return 0
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
local _po_here
|
|
494
|
+
_po_here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" || return 1
|
|
495
|
+
local _loader="$_po_here/providers/loader.sh"
|
|
496
|
+
[ -f "$_loader" ] || return 1
|
|
497
|
+
|
|
498
|
+
local _sel
|
|
499
|
+
_sel="$(
|
|
500
|
+
# shellcheck source=/dev/null
|
|
501
|
+
source "$_loader" >/dev/null 2>&1 || exit 1
|
|
502
|
+
declare -f auto_detect_provider >/dev/null 2>&1 || exit 1
|
|
503
|
+
auto_detect_provider 2>/dev/null
|
|
504
|
+
)" || return 1
|
|
505
|
+
|
|
506
|
+
[ -n "$_sel" ] || return 1
|
|
507
|
+
printf '%s\n' "$_sel"
|
|
508
|
+
}
|
|
509
|
+
|
|
467
510
|
# provider_availability_json: the same data as one JSON object, for doctor --json.
|
|
468
511
|
# Separate from the renderer so neither format has to parse the other. Same
|
|
469
512
|
# degrade rule: no loader means no output and a non-zero status.
|
|
@@ -517,6 +560,10 @@ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
|
517
560
|
# routes call these so the two renderings cannot drift apart.
|
|
518
561
|
providers) render_provider_availability ;;
|
|
519
562
|
providers-json) provider_availability_json ;;
|
|
563
|
+
# The provider id a build would actually use, for the Bun doctor's
|
|
564
|
+
# skill-severity decision. Same one-implementation rule as the two
|
|
565
|
+
# above: doctor.ts reads this instead of re-deriving the priority list.
|
|
566
|
+
effective-provider) effective_provider_id ;;
|
|
520
567
|
*) offer_provider_install report ;;
|
|
521
568
|
esac
|
|
522
569
|
fi
|
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.
|