loki-mode 7.104.2 → 7.104.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/run.sh +96 -40
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +146 -3
- package/docs/INSTALLATION.md +1 -1
- package/docs/TASKLIST-ACCURACY-PLAN.md +355 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
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 v7.104.
|
|
6
|
+
# Loki Mode v7.104.3
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.104.
|
|
411
|
+
**v7.104.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.104.
|
|
1
|
+
7.104.3
|
package/autonomy/run.sh
CHANGED
|
@@ -6520,52 +6520,108 @@ EFF_EOF
|
|
|
6520
6520
|
[ ! -f "$completed_file" ] && echo "[]" > "$completed_file"
|
|
6521
6521
|
[ ! -f "$failed_file" ] && echo "[]" > "$failed_file"
|
|
6522
6522
|
|
|
6523
|
-
#
|
|
6524
|
-
|
|
6525
|
-
{
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
# Add to appropriate queue
|
|
6523
|
+
# Build the completed iteration record + move it out of in-progress, ATOMICALLY.
|
|
6524
|
+
# v7.104.3 task-list accuracy fix: the old writer emitted a THIN body
|
|
6525
|
+
# {id,type,title:"Iteration N",status,exitCode,provider} with no description
|
|
6526
|
+
# and no logs, and then DELETED the rich in-progress record -- so the done
|
|
6527
|
+
# column showed empty cards. We now LIFT the honest per-iteration parts (logs,
|
|
6528
|
+
# startedAt) from the in-progress record BEFORE removing it, and give the card
|
|
6529
|
+
# an HONEST iteration-scoped title/description derived from values in scope
|
|
6530
|
+
# ($phase/$exit_code/$duration_ms). We deliberately do NOT reuse the borrowed
|
|
6531
|
+
# PRD-story title: because pending stories never leave the queue, the
|
|
6532
|
+
# in-progress title is always pending[0] ("server.js..."), so carrying it onto
|
|
6533
|
+
# N done cards would falsely imply that story was built N times (fake-green).
|
|
6534
|
+
# We also upsert-by-id (no cross-sub-run "iteration-1 x5" accumulation) and
|
|
6535
|
+
# keep completed/failed mutually exclusive per id.
|
|
6538
6536
|
local target_file="$completed_file"
|
|
6539
|
-
|
|
6540
|
-
|
|
6537
|
+
local other_file="$failed_file"
|
|
6538
|
+
[ "$exit_code" != "0" ] && { target_file="$failed_file"; other_file="$completed_file"; }
|
|
6539
|
+
local _completed_ts; _completed_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
6540
|
+
_LOKI_TASK_ID="$task_id" \
|
|
6541
|
+
_LOKI_ITER="$iteration" \
|
|
6542
|
+
_LOKI_PHASE="$phase" \
|
|
6543
|
+
_LOKI_EXIT="$exit_code" \
|
|
6544
|
+
_LOKI_DUR="$duration_ms" \
|
|
6545
|
+
_LOKI_PROVIDER="${PROVIDER_NAME:-claude}" \
|
|
6546
|
+
_LOKI_COMPLETED_TS="$_completed_ts" \
|
|
6547
|
+
_LOKI_TARGET="$target_file" \
|
|
6548
|
+
_LOKI_OTHER="$other_file" \
|
|
6549
|
+
_LOKI_INPROG="$in_progress_file" \
|
|
6541
6550
|
python3 -c "
|
|
6542
|
-
import
|
|
6551
|
+
import json, os
|
|
6552
|
+
|
|
6553
|
+
tid = os.environ['_LOKI_TASK_ID']
|
|
6554
|
+
it = os.environ['_LOKI_ITER']
|
|
6555
|
+
phase = os.environ.get('_LOKI_PHASE') or 'unknown'
|
|
6556
|
+
exit_code = os.environ.get('_LOKI_EXIT', '1')
|
|
6557
|
+
dur = os.environ.get('_LOKI_DUR', '')
|
|
6558
|
+
provider = os.environ.get('_LOKI_PROVIDER', 'claude')
|
|
6559
|
+
completed_ts = os.environ['_LOKI_COMPLETED_TS']
|
|
6560
|
+
target = os.environ['_LOKI_TARGET']
|
|
6561
|
+
other = os.environ['_LOKI_OTHER']
|
|
6562
|
+
inprog = os.environ['_LOKI_INPROG']
|
|
6563
|
+
ok = (exit_code == '0')
|
|
6564
|
+
|
|
6565
|
+
def load(p):
|
|
6566
|
+
try:
|
|
6567
|
+
with open(p) as f:
|
|
6568
|
+
d = json.load(f)
|
|
6569
|
+
return d if isinstance(d, list) else (d.get('tasks', []) if isinstance(d, dict) else [])
|
|
6570
|
+
except Exception:
|
|
6571
|
+
return []
|
|
6572
|
+
|
|
6573
|
+
# Lift the honest body from the in-progress record before removing it.
|
|
6574
|
+
prior = next((t for t in load(inprog) if isinstance(t, dict) and t.get('id') == tid), {})
|
|
6575
|
+
logs = prior.get('logs') if isinstance(prior.get('logs'), list) else []
|
|
6576
|
+
started = prior.get('startedAt')
|
|
6577
|
+
|
|
6578
|
+
dur_s = ''
|
|
6543
6579
|
try:
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6580
|
+
if dur not in ('', None):
|
|
6581
|
+
_ms = int(float(dur))
|
|
6582
|
+
dur_s = f\", {_ms}ms\" if _ms < 1000 else f\", {round(_ms/1000)}s\"
|
|
6583
|
+
except Exception:
|
|
6584
|
+
dur_s = ''
|
|
6585
|
+
|
|
6586
|
+
if ok:
|
|
6587
|
+
title = f'Iteration {it} complete - {phase}'
|
|
6588
|
+
desc = f'Iteration {it} finished cleanly in the {phase} phase (exit 0{dur_s}).'
|
|
6589
|
+
else:
|
|
6590
|
+
title = f'Iteration {it} failed (exit {exit_code})'
|
|
6591
|
+
desc = f'Iteration {it} ended in the {phase} phase with exit code {exit_code}{dur_s}.'
|
|
6592
|
+
|
|
6593
|
+
entry = {
|
|
6594
|
+
'id': tid,
|
|
6595
|
+
'type': 'iteration',
|
|
6596
|
+
'title': title,
|
|
6597
|
+
'description': desc,
|
|
6598
|
+
'status': 'completed' if ok else 'failed',
|
|
6599
|
+
'phase': phase,
|
|
6600
|
+
'completedAt': completed_ts,
|
|
6601
|
+
'exitCode': int(exit_code) if str(exit_code).lstrip('-').isdigit() else exit_code,
|
|
6602
|
+
'provider': provider,
|
|
6603
|
+
'logs': logs,
|
|
6604
|
+
}
|
|
6605
|
+
if started:
|
|
6606
|
+
entry['startedAt'] = started
|
|
6607
|
+
|
|
6608
|
+
# Upsert-by-id into target (drop any stale same-id), cap at 50.
|
|
6609
|
+
data = [t for t in load(target) if not (isinstance(t, dict) and t.get('id') == tid)]
|
|
6610
|
+
data.append(entry)
|
|
6550
6611
|
data = data[-50:]
|
|
6551
|
-
with open(
|
|
6612
|
+
with open(target, 'w') as f:
|
|
6552
6613
|
json.dump(data, f, indent=2)
|
|
6553
|
-
" 2>/dev/null || echo "[$task_json]" > "$target_file"
|
|
6554
6614
|
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
except:
|
|
6566
|
-
pass
|
|
6567
|
-
" 2>/dev/null || true
|
|
6568
|
-
fi
|
|
6615
|
+
# Mutual exclusion: remove this id from the OTHER terminal file.
|
|
6616
|
+
odata = [t for t in load(other) if not (isinstance(t, dict) and t.get('id') == tid)]
|
|
6617
|
+
with open(other, 'w') as f:
|
|
6618
|
+
json.dump(odata, f, indent=2)
|
|
6619
|
+
|
|
6620
|
+
# Remove from in-progress.
|
|
6621
|
+
idata = [t for t in load(inprog) if not (isinstance(t, dict) and t.get('id') == tid)]
|
|
6622
|
+
with open(inprog, 'w') as f:
|
|
6623
|
+
json.dump(idata, f, indent=2)
|
|
6624
|
+
" 2>/dev/null || echo "[{\"id\":\"$task_id\",\"type\":\"iteration\",\"title\":\"Iteration $iteration\",\"status\":\"$([ "$exit_code" = "0" ] && echo completed || echo failed)\",\"completedAt\":\"$_completed_ts\",\"exitCode\":$exit_code,\"provider\":\"${PROVIDER_NAME:-claude}\"}]" > "$target_file"
|
|
6569
6625
|
|
|
6570
6626
|
# BUG-ST-014: Atomic current-task.json clear via temp file + mv
|
|
6571
6627
|
local ct_tmp=".loki/queue/current-task.json.tmp.$$"
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -1816,10 +1816,30 @@ async def list_tasks(
|
|
|
1816
1816
|
}
|
|
1817
1817
|
for _f in ("acceptance_criteria", "notes", "logs", "user_story",
|
|
1818
1818
|
"project", "source", "specification", "provider",
|
|
1819
|
-
"startedAt", "full_content"
|
|
1819
|
+
"startedAt", "full_content",
|
|
1820
|
+
# v7.104.3: iteration terminal fields so the card
|
|
1821
|
+
# can show real outcome (and the read-time honest
|
|
1822
|
+
# description synthesis for legacy thin markers can
|
|
1823
|
+
# see exitCode). exitCode may be 0, which is falsy,
|
|
1824
|
+
# so it is guarded separately below.
|
|
1825
|
+
"completedAt", "phase", "exitCode"):
|
|
1820
1826
|
_v = task.get(_f)
|
|
1821
1827
|
if _v not in (None, "", [], {}):
|
|
1822
1828
|
task_entry[_f] = _v
|
|
1829
|
+
# exitCode == 0 is a real, meaningful value but falsy; the
|
|
1830
|
+
# loop above drops it via the "not in" filter, so carry it
|
|
1831
|
+
# explicitly when the source record has the key at all.
|
|
1832
|
+
if "exitCode" in task and task.get("exitCode") == 0:
|
|
1833
|
+
task_entry["exitCode"] = 0
|
|
1834
|
+
# v7.104.3: preserve the record's OWN terminal outcome. Both
|
|
1835
|
+
# the "completed" and "failed" groups map to the "done"
|
|
1836
|
+
# column, so `status` alone can no longer tell success from
|
|
1837
|
+
# failure. The honest-description synthesis and the dedup
|
|
1838
|
+
# tie-break both need to distinguish them, and NEVER call a
|
|
1839
|
+
# failed iteration "completed" (fake-green). Derived from the
|
|
1840
|
+
# source group key, which is authoritative.
|
|
1841
|
+
if group_key in ("completed", "failed"):
|
|
1842
|
+
task_entry["_terminal_outcome"] = group_key
|
|
1823
1843
|
all_tasks.append(task_entry)
|
|
1824
1844
|
except (json.JSONDecodeError, KeyError):
|
|
1825
1845
|
pass
|
|
@@ -1846,12 +1866,32 @@ async def list_tasks(
|
|
|
1846
1866
|
items = raw_items
|
|
1847
1867
|
else:
|
|
1848
1868
|
items = []
|
|
1869
|
+
# v7.104.3: which terminal outcome (if any) does THIS queue
|
|
1870
|
+
# file represent? completed.json -> completed; failed/dead-
|
|
1871
|
+
# letter -> failed. Non-terminal files (pending/in-progress)
|
|
1872
|
+
# have none.
|
|
1873
|
+
_qf_terminal = None
|
|
1874
|
+
if queue_file == "completed.json":
|
|
1875
|
+
_qf_terminal = "completed"
|
|
1876
|
+
elif queue_file in ("failed.json", "dead-letter.json"):
|
|
1877
|
+
_qf_terminal = "failed"
|
|
1849
1878
|
if isinstance(items, list):
|
|
1850
1879
|
for i, item in enumerate(items):
|
|
1851
1880
|
if isinstance(item, dict):
|
|
1852
1881
|
tid = item.get("id", f"q-{q_status}-{i}")
|
|
1853
|
-
# Skip if already in all_tasks
|
|
1854
|
-
|
|
1882
|
+
# Skip if already in all_tasks -- EXCEPT for
|
|
1883
|
+
# terminal (completed/failed) records. v7.104.3:
|
|
1884
|
+
# the old unconditional skip dropped a failed
|
|
1885
|
+
# sibling before it could be tagged with
|
|
1886
|
+
# _terminal_outcome, so the failed-outranks-
|
|
1887
|
+
# completed dedup below never saw it and a
|
|
1888
|
+
# completed card masked a real failure (a
|
|
1889
|
+
# fake-green from the queue source). Let terminal
|
|
1890
|
+
# records always enter, tagged; the global dedup
|
|
1891
|
+
# then resolves the collision honestly. Non-
|
|
1892
|
+
# terminal records keep the skip (they cannot
|
|
1893
|
+
# override an existing terminal entry anyway).
|
|
1894
|
+
if _qf_terminal is None and any(t["id"] == tid for t in all_tasks):
|
|
1855
1895
|
continue
|
|
1856
1896
|
task_entry = {
|
|
1857
1897
|
"id": tid,
|
|
@@ -1876,6 +1916,20 @@ async def list_tasks(
|
|
|
1876
1916
|
task_entry["notes"] = item["notes"]
|
|
1877
1917
|
if isinstance(item.get("logs"), list):
|
|
1878
1918
|
task_entry["logs"] = item["logs"]
|
|
1919
|
+
# v7.104.3: iteration terminal fields + the
|
|
1920
|
+
# source-file terminal outcome, so the honest
|
|
1921
|
+
# description synthesis can distinguish a clean
|
|
1922
|
+
# exit from a failure and never mislabels a
|
|
1923
|
+
# failed iteration "completed".
|
|
1924
|
+
for _qf in ("provider", "startedAt", "completedAt", "phase"):
|
|
1925
|
+
if item.get(_qf) not in (None, "", [], {}):
|
|
1926
|
+
task_entry[_qf] = item[_qf]
|
|
1927
|
+
if "exitCode" in item and isinstance(item.get("exitCode"), int):
|
|
1928
|
+
task_entry["exitCode"] = item["exitCode"]
|
|
1929
|
+
if queue_file == "completed.json":
|
|
1930
|
+
task_entry["_terminal_outcome"] = "completed"
|
|
1931
|
+
elif queue_file in ("failed.json", "dead-letter.json"):
|
|
1932
|
+
task_entry["_terminal_outcome"] = "failed"
|
|
1879
1933
|
all_tasks.append(task_entry)
|
|
1880
1934
|
except (json.JSONDecodeError, KeyError):
|
|
1881
1935
|
pass
|
|
@@ -1903,6 +1957,95 @@ async def list_tasks(
|
|
|
1903
1957
|
except Exception:
|
|
1904
1958
|
pass
|
|
1905
1959
|
|
|
1960
|
+
# v7.104.3 task-list accuracy: global dedup-by-id with a terminal-wins rule.
|
|
1961
|
+
# The dashboard-state.json groups (read first, above) can carry the same id in
|
|
1962
|
+
# more than one column when the underlying queue files hold stale entries -
|
|
1963
|
+
# e.g. real anonima data showed iteration-13 in BOTH inProgress and completed,
|
|
1964
|
+
# and completed listing iteration-1 five times (a pre-fix blind-append run).
|
|
1965
|
+
# The run.sh write-side fix (upsert-by-id + terminal mutual-exclusion) stops
|
|
1966
|
+
# NEW collisions, but the dashboard must render correctly against state that
|
|
1967
|
+
# was written before the fix, or during a partial write. We keep, per id, the
|
|
1968
|
+
# single most-authoritative entry.
|
|
1969
|
+
#
|
|
1970
|
+
# Priority is TERMINAL-WINS, deliberately NOT the naive
|
|
1971
|
+
# in_progress > pending > done: the reported symptom is a completed iteration
|
|
1972
|
+
# still showing in in-progress/todo, so letting in_progress outrank done would
|
|
1973
|
+
# perpetuate exactly that bug. A record that reached a terminal state (has a
|
|
1974
|
+
# completedAt, i.e. status == done) is the truth for that id; among non-terminal
|
|
1975
|
+
# states we fall back to in_progress > review > pending. This is safe globally
|
|
1976
|
+
# here because pending PRD-story ids (prd-NNN) and iteration ids (iteration-N)
|
|
1977
|
+
# never share an id, so no task is legitimately both pending and done.
|
|
1978
|
+
#
|
|
1979
|
+
# Within the terminal (done) column, a same-id collision between a completed
|
|
1980
|
+
# and a failed record must resolve to FAILED, never completed: masking a real
|
|
1981
|
+
# failure behind a success is a fake-green the trust model forbids. So the
|
|
1982
|
+
# authority key is a 2-tuple (column-rank, terminal-outcome-rank) where a
|
|
1983
|
+
# failed terminal record outranks a completed one; non-terminal states carry a
|
|
1984
|
+
# neutral 0 in the second slot.
|
|
1985
|
+
_rank = {"done": 3, "in_progress": 2, "review": 1, "pending": 0}
|
|
1986
|
+
_term = {"failed": 1, "completed": 0}
|
|
1987
|
+
|
|
1988
|
+
def _authority(_task):
|
|
1989
|
+
return (_rank.get(_task.get("status"), -1),
|
|
1990
|
+
_term.get(_task.get("_terminal_outcome"), 0))
|
|
1991
|
+
|
|
1992
|
+
_by_id: dict = {}
|
|
1993
|
+
_order: list = []
|
|
1994
|
+
for _t in all_tasks:
|
|
1995
|
+
_id = _t.get("id")
|
|
1996
|
+
if _id is None:
|
|
1997
|
+
_order.append(_t) # cannot dedup an id-less entry; keep as-is
|
|
1998
|
+
continue
|
|
1999
|
+
_prev = _by_id.get(_id)
|
|
2000
|
+
if _prev is None:
|
|
2001
|
+
_by_id[_id] = _t
|
|
2002
|
+
_order.append(_id)
|
|
2003
|
+
elif _authority(_t) > _authority(_prev):
|
|
2004
|
+
_by_id[_id] = _t # higher-authority state replaces in place (order kept)
|
|
2005
|
+
all_tasks = [t if not isinstance(t, str) else _by_id[t] for t in _order]
|
|
2006
|
+
|
|
2007
|
+
# v7.104.3 task-list accuracy: HONEST render-time description for legacy
|
|
2008
|
+
# iteration cards. Iterations completed BEFORE the run.sh write-side fix were
|
|
2009
|
+
# persisted as thin markers - they carry real captured fields (status,
|
|
2010
|
+
# exitCode, provider, completedAt) but no description or logs, so they render
|
|
2011
|
+
# as empty "Iteration N" cards with nothing inside (the exact symptom the
|
|
2012
|
+
# dashboard showed). These iterations will never re-run, so the write-side
|
|
2013
|
+
# fix cannot backfill them. We synthesize a one-line description at READ time
|
|
2014
|
+
# from the fields that ARE present - never mutating the stored state (no
|
|
2015
|
+
# fabrication, fully reversible) and never inventing an outcome the record
|
|
2016
|
+
# does not attest. New (post-fix) cards already carry a real description and
|
|
2017
|
+
# are left untouched.
|
|
2018
|
+
for _t in all_tasks:
|
|
2019
|
+
if _t.get("type") == "iteration" and not _t.get("description"):
|
|
2020
|
+
_ec = _t.get("exitCode")
|
|
2021
|
+
_prov = _t.get("provider")
|
|
2022
|
+
# The record's OWN terminal outcome (completed vs failed) is
|
|
2023
|
+
# authoritative. Both map to the "done" column, so status alone can
|
|
2024
|
+
# not tell them apart - a failed iteration must NEVER be described as
|
|
2025
|
+
# "completed" (fake-green). Prefer the exit code when it is a real
|
|
2026
|
+
# integer; otherwise fall back to the terminal outcome; and when even
|
|
2027
|
+
# that is absent, use a neutral verb that asserts no success.
|
|
2028
|
+
_failed = (_t.get("_terminal_outcome") == "failed")
|
|
2029
|
+
_outcome = None
|
|
2030
|
+
if isinstance(_ec, int) and _ec == 0 and not _failed:
|
|
2031
|
+
_outcome = "completed cleanly (exit 0)"
|
|
2032
|
+
elif isinstance(_ec, int) and _ec != 0:
|
|
2033
|
+
_outcome = f"failed (exit {_ec})"
|
|
2034
|
+
elif _failed:
|
|
2035
|
+
# failed record without a usable exit code: honest, no exit claim
|
|
2036
|
+
_outcome = "failed"
|
|
2037
|
+
elif _t.get("_terminal_outcome") == "completed":
|
|
2038
|
+
_outcome = "completed"
|
|
2039
|
+
elif _t.get("status") == "done":
|
|
2040
|
+
# terminal but neither outcome nor exit code is known: neutral
|
|
2041
|
+
# verb that does not assert success or failure
|
|
2042
|
+
_outcome = "finished"
|
|
2043
|
+
if _outcome:
|
|
2044
|
+
_desc = f"Iteration {_outcome}"
|
|
2045
|
+
if _prov:
|
|
2046
|
+
_desc += f", built by {_prov}"
|
|
2047
|
+
_t["description"] = _desc + "."
|
|
2048
|
+
|
|
1906
2049
|
# Apply project_id filter if provided
|
|
1907
2050
|
if project_id is not None:
|
|
1908
2051
|
all_tasks = [t for t in all_tasks if t.get("project_id") == project_id]
|
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:** v7.104.
|
|
5
|
+
**Version:** v7.104.3
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Dashboard Task-List Accuracy Fix Plan
|
|
2
|
+
|
|
3
|
+
Branch: fix/tasklist-accuracy
|
|
4
|
+
Worktree: /Users/lokesh/git/loki-tasklist-wt
|
|
5
|
+
Type: PATCH release (bug fix, no new features)
|
|
6
|
+
Status: Plan only. No implementation in this document.
|
|
7
|
+
|
|
8
|
+
## 1. Summary
|
|
9
|
+
|
|
10
|
+
On a live run (~/git/anonima, loki start reusing a generated PRD) the dashboard
|
|
11
|
+
board at :57375/#section=overview shows:
|
|
12
|
+
|
|
13
|
+
- DONE column: 30 bare "Iteration N" cards with empty body (click -> empty modal).
|
|
14
|
+
- PENDING column: 12 rich PRD stories (prd-001..prd-012) that never move.
|
|
15
|
+
- IN-PROGRESS: iteration-13, which correctly borrows the active PRD story
|
|
16
|
+
title/description (intentional).
|
|
17
|
+
|
|
18
|
+
This plan verifies the mechanism against source, recommends an honesty-first
|
|
19
|
+
board model, lists the exact per-file change set with line references, gives a
|
|
20
|
+
test plan, and notes the patch-release steps.
|
|
21
|
+
|
|
22
|
+
## 2. Verified root cause (against source + real data)
|
|
23
|
+
|
|
24
|
+
Reproduced by replaying the /api/tasks merge logic over the real
|
|
25
|
+
~/git/anonima/.loki files. Rendered output shape:
|
|
26
|
+
|
|
27
|
+
- TOTAL 43 tasks: done=30, pending=12, in_progress=1.
|
|
28
|
+
- EMPTY-description done cards: 30 of 30.
|
|
29
|
+
- Duplicate ids in the output: iteration-1 x5, iteration-2 x3,
|
|
30
|
+
iteration-3..12 x2 each, iteration-13 x2 (appears in BOTH in_progress and
|
|
31
|
+
done). This is a distinct, second defect (see 2C).
|
|
32
|
+
|
|
33
|
+
There are four concrete defects.
|
|
34
|
+
|
|
35
|
+
### 2A. Thin completed markers (primary defect -> empty done cards)
|
|
36
|
+
|
|
37
|
+
`track_iteration_start` (autonomy/run.sh:5370-5569) writes a RICH in-progress
|
|
38
|
+
record: it reads pending[0] for context (5395-5416) and emits a task with
|
|
39
|
+
id `iteration-N`, type `iteration`, a real title/description, acceptance_criteria,
|
|
40
|
+
notes, and a `logs` array (5462-5488, with fallbacks at 5499-5532 and
|
|
41
|
+
5545-5568). `append_iteration_log` (5013-5072) appends further per-phase log
|
|
42
|
+
entries to that same in-progress record.
|
|
43
|
+
|
|
44
|
+
`track_iteration_complete` (autonomy/run.sh:6276-6455) writes the COMPLETED
|
|
45
|
+
marker with a hardcoded THIN body (6400-6411):
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
"id": "iteration-N",
|
|
49
|
+
"type": "iteration",
|
|
50
|
+
"title": "Iteration N",
|
|
51
|
+
"status": "completed"|"failed",
|
|
52
|
+
"completedAt": "...",
|
|
53
|
+
"exitCode": N,
|
|
54
|
+
"provider": "..."
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
No title-from-context, no description, no logs. It appends this to
|
|
58
|
+
completed.json/failed.json (6414-6429), then DELETES the rich in-progress
|
|
59
|
+
record (6431-6443). So the rich in-progress card becomes an empty done card,
|
|
60
|
+
and the logs that lived only in the in-progress record are discarded.
|
|
61
|
+
|
|
62
|
+
Confirmed data: ~/git/anonima/.loki/queue/completed.json items are all
|
|
63
|
+
`{"id":"iteration-N","title":"Iteration N","description":""...}`.
|
|
64
|
+
|
|
65
|
+
Key consequence for the fix: the body (logs/phase) exists ONLY in the
|
|
66
|
+
in-progress record at completion time. The API cannot recover it after run.sh
|
|
67
|
+
deletes it. run.sh MUST lift it before removal. This is why run.sh is the
|
|
68
|
+
required fix site for the card body, not the API.
|
|
69
|
+
|
|
70
|
+
### 2B. PRD stories never reconcile pending -> done
|
|
71
|
+
|
|
72
|
+
PRD tasks are populated once into pending.json by the PRD parser
|
|
73
|
+
(autonomy/run.sh:15167-15213, ids `prd-001`..). Nothing ever pops or moves a
|
|
74
|
+
prd task out of pending: grep of every pending.json write site
|
|
75
|
+
(2625-2720 dedup, 2918-2938 GitHub sync read-only, 14663 openspec purge,
|
|
76
|
+
14679 populate) shows no path that transitions a prd story to completed. The
|
|
77
|
+
RARV loop only READS pending[0] for iteration context (5403-5413); it never
|
|
78
|
+
mutates prd status. So the 12 stories sit in pending forever regardless of how
|
|
79
|
+
many iterations run.
|
|
80
|
+
|
|
81
|
+
Honesty check (decisive): ~/git/anonima/.loki/checklist/checklist.json has 19
|
|
82
|
+
items, ALL status `pending`; verification-results.json summary is
|
|
83
|
+
verified=0/failing=0/pending=19. Nothing in this run is verified-complete.
|
|
84
|
+
Auto-moving any prd story to done would be fake-green.
|
|
85
|
+
|
|
86
|
+
### 2C. No intra-source dedup in /api/tasks (duplicate cards, broken keys)
|
|
87
|
+
|
|
88
|
+
dashboard/server.py /api/tasks (1772-1914) merges two sources:
|
|
89
|
+
1. dashboard-state.json `.tasks` groups (1783-1825) mapped
|
|
90
|
+
pending->pending, inProgress->in_progress, completed->done, failed->done.
|
|
91
|
+
2. queue/*.json files (1827-1881).
|
|
92
|
+
|
|
93
|
+
The queue merge skips ids already present (1854 `any(t["id"] == tid ...)`), but
|
|
94
|
+
there is NO dedup WITHIN the dashboard-state groups themselves, and no
|
|
95
|
+
cross-column dedup. The real dashboard-state completed group already contains
|
|
96
|
+
iteration-1 x5 and iteration-13 (which is ALSO in the inProgress group). Result:
|
|
97
|
+
the same id renders as multiple cards, and iteration-13 appears in both the
|
|
98
|
+
in-progress and done columns simultaneously. In the React board these become
|
|
99
|
+
duplicate `key={task.id}` / dnd-kit sortable ids (TaskCard.tsx:24), which is a
|
|
100
|
+
correctness bug (duplicate keys, unstable drag).
|
|
101
|
+
|
|
102
|
+
Root of the id collision: iteration ids are `iteration-N` and N resets/repeats
|
|
103
|
+
across sub-runs, and completed.json is capped with `data[-50:]` (run.sh:6426)
|
|
104
|
+
which retains stale duplicate-id entries rather than replacing by id.
|
|
105
|
+
|
|
106
|
+
### 2D. iteration-13 leaks into completed while still in-progress
|
|
107
|
+
|
|
108
|
+
The completed group contains iteration-13 even though it is the active
|
|
109
|
+
in-progress card. Combined with 2C this is what puts one id in two columns.
|
|
110
|
+
The dedup fix (3C) resolves the display; the run.sh completed-writer dedup (3B)
|
|
111
|
+
resolves the source accumulation.
|
|
112
|
+
|
|
113
|
+
## 3. Recommended board model (honesty-first)
|
|
114
|
+
|
|
115
|
+
Recommendation: Option A now; defer Option B as a scoped follow-up. Do NOT ship
|
|
116
|
+
Option B (or C) in this patch.
|
|
117
|
+
|
|
118
|
+
Rationale (honesty + accuracy):
|
|
119
|
+
|
|
120
|
+
- Iterations that ran are real events. Making their done cards honest and
|
|
121
|
+
informative is truthful and low-risk.
|
|
122
|
+
- LITERAL Option A is a trap and must be avoided: `track_iteration_start`
|
|
123
|
+
borrows pending[0] for the title, and because prd stories never leave pending
|
|
124
|
+
(2B), pending[0] is ALWAYS prd-001. Carrying that borrowed PRD title forward
|
|
125
|
+
onto every done card would render 13+ done cards all titled
|
|
126
|
+
"server.js (single backend module)", implying server.js was built and
|
|
127
|
+
completed 13 times, when the checklist says it was never verified. That is the
|
|
128
|
+
same fake-green we reject for Option B, spread across the done column. So the
|
|
129
|
+
corrected Option A carries forward the REAL, iteration-scoped parts (logs,
|
|
130
|
+
phase, exit code, duration) and keeps an iteration-scoped title
|
|
131
|
+
("Iteration N complete - <phase>, exit 0"), NOT the borrowed PRD story title.
|
|
132
|
+
- Option B (reconcile prd pending -> done) requires a trustworthy per-story
|
|
133
|
+
completion signal. The only per-item truth signal is the checklist, keyed by
|
|
134
|
+
`be-001`/`api-001` item ids with NO stored `prd-NNN` link
|
|
135
|
+
(verified in checklist.json/verification-results.json). Mapping checklist ->
|
|
136
|
+
prd would require fuzzy title matching, which introduces inaccuracy
|
|
137
|
+
(mismatch -> fake-green or fake-red). Shipping a fuzzy reconciler in a patch
|
|
138
|
+
contradicts the high-accuracy mandate. On this run the correct verified signal
|
|
139
|
+
would move ZERO stories anyway (verified=0), so B delivers no value now and
|
|
140
|
+
carries real regression risk. Defer to a follow-up that either (a) stamps a
|
|
141
|
+
`prd-NNN` source id onto checklist items at generation time so the join is
|
|
142
|
+
exact, or (b) surfaces the checklist as its own honest progress panel instead
|
|
143
|
+
of mutating prd story status.
|
|
144
|
+
|
|
145
|
+
Net board after this patch: DONE shows honest iteration cards (informative
|
|
146
|
+
title + real logs, no empty modals); PENDING keeps the 12 prd stories
|
|
147
|
+
(truthful - none are verified-done); IN-PROGRESS shows the single active
|
|
148
|
+
iteration; no duplicate cards; no card in two columns.
|
|
149
|
+
|
|
150
|
+
## 4. Exact change set (per file, with line refs)
|
|
151
|
+
|
|
152
|
+
All engine (run.sh) edits are additive/corrective to the task-record shape only.
|
|
153
|
+
No trust-gate, verdict, or completion-council logic is touched.
|
|
154
|
+
|
|
155
|
+
### 4A. autonomy/run.sh - track_iteration_complete (6390-6444)
|
|
156
|
+
|
|
157
|
+
Change the completed-marker construction so it lifts the honest body from the
|
|
158
|
+
in-progress record BEFORE that record is removed.
|
|
159
|
+
|
|
160
|
+
- Before building task_json (currently 6400-6411), read the matching
|
|
161
|
+
`iteration-N` entry from in-progress.json (the file already read at 6432-6443
|
|
162
|
+
for removal). Capture its `logs`, `title`, `description`, `acceptance_criteria`,
|
|
163
|
+
`notes`, `startedAt`, `user_story`, `source`, `project` if present.
|
|
164
|
+
- Build the completed entry with:
|
|
165
|
+
- `title`: iteration-scoped and honest, e.g.
|
|
166
|
+
"Iteration N complete - <phase>" (exit 0) or
|
|
167
|
+
"Iteration N failed (exit <code>)" - derived from `$phase` (already computed
|
|
168
|
+
at 6355-6356) and `$exit_code`. Do NOT reuse the borrowed PRD story title
|
|
169
|
+
(see 3 rationale). If a truthful iteration description is wanted, synthesize
|
|
170
|
+
"Iteration N: <phase>, exit <code>, <duration>ms" from values already in
|
|
171
|
+
scope (`$phase`, `$exit_code`, `$duration_ms` at 6285).
|
|
172
|
+
- `logs`: the lifted logs array from the in-progress record (preserves the
|
|
173
|
+
real per-phase entries so the modal is populated).
|
|
174
|
+
- keep `type`, `status`, `completedAt`, `exitCode`, `provider` as today; add
|
|
175
|
+
`startedAt` (from in-progress) so the card can show duration.
|
|
176
|
+
- Do this with the same python-embedded approach already used at 6417-6429;
|
|
177
|
+
read in-progress inside that python block so it is atomic with the append.
|
|
178
|
+
- Dedup by id on write (see 4B) instead of blind append.
|
|
179
|
+
|
|
180
|
+
Keep the existing lock/atomic-write and the last-resort fallback shape so a
|
|
181
|
+
python-unavailable environment still writes a valid (if minimal) card.
|
|
182
|
+
|
|
183
|
+
### 4B. autonomy/run.sh - completed.json/failed.json dedup on write (6417-6429)
|
|
184
|
+
|
|
185
|
+
Replace the blind `data.append(...)` + `data[-50:]` (6424-6426) with a
|
|
186
|
+
replace-by-id upsert: drop any existing entry with the same `id`, append the
|
|
187
|
+
new one, then cap at 50. This stops the cross-sub-run iteration-N accumulation
|
|
188
|
+
(iteration-1 x5) at the source. Failed.json (same writer, target chosen at
|
|
189
|
+
6414-6415) gets the same upsert.
|
|
190
|
+
|
|
191
|
+
Note: also prevents an id being simultaneously in completed and failed (real
|
|
192
|
+
data has iteration-1 in both). Upsert-by-id in the chosen target plus removal
|
|
193
|
+
from the other target (add a small removal of `id` from the non-target file)
|
|
194
|
+
makes completed/failed mutually exclusive per id.
|
|
195
|
+
|
|
196
|
+
### 4C. dashboard/server.py - /api/tasks global dedup (1780-1914)
|
|
197
|
+
|
|
198
|
+
Add a single global dedup pass keyed by id with column priority so one id yields
|
|
199
|
+
exactly one card, and it lands in the most-active column:
|
|
200
|
+
|
|
201
|
+
- Priority order: in_progress > review > pending > done. (in_progress beats done
|
|
202
|
+
so iteration-13-in-both resolves to the in-progress card; pending beats done
|
|
203
|
+
is irrelevant here but keeps a story that is both queued and archived in the
|
|
204
|
+
live column.)
|
|
205
|
+
- Implementation: keep a dict keyed by id while appending; when a duplicate id
|
|
206
|
+
arrives, keep the entry whose status has higher priority, and if equal
|
|
207
|
+
priority prefer the one with a non-empty description/logs (richer wins). Apply
|
|
208
|
+
this across BOTH the dashboard-state pass (1796-1823) and the queue pass
|
|
209
|
+
(1850-1879), replacing the current one-directional skip at 1854.
|
|
210
|
+
- Return `list(dict.values())` (or rebuild all_tasks from the dict) at 1906
|
|
211
|
+
before the project_id/status filters, preserving current ordering by
|
|
212
|
+
first-seen where possible.
|
|
213
|
+
|
|
214
|
+
Backward compatibility: the response is still a JSON array of the same
|
|
215
|
+
task-entry shape (same keys as today at 1808-1822 and 1856-1878). No field is
|
|
216
|
+
removed or renamed. Consumers see fewer/deduped items and populated
|
|
217
|
+
description/logs on done cards. This is strictly a correctness improvement to an
|
|
218
|
+
existing shape - compatible.
|
|
219
|
+
|
|
220
|
+
### 4D. dashboard/frontend - defense-in-depth (optional, low risk)
|
|
221
|
+
|
|
222
|
+
The React TaskCard already guards empty description (TaskCard.tsx:129-133) and
|
|
223
|
+
renders title from `task.title` (124-126); the empty cards are a DATA problem
|
|
224
|
+
fixed by 4A/4C, so no functional SPA change is required. Optional hardening:
|
|
225
|
+
|
|
226
|
+
- TaskModal.tsx: when a task has neither description nor logs nor
|
|
227
|
+
acceptance_criteria, render an explicit honest placeholder
|
|
228
|
+
("No details recorded for this iteration.") instead of an empty modal body.
|
|
229
|
+
This is a safety net for any legacy/thin records still on disk from before the
|
|
230
|
+
patch.
|
|
231
|
+
- No change to api.ts transformTask (api.ts:41-56) or types.ts is needed; the
|
|
232
|
+
status enum already covers done/in_progress/pending.
|
|
233
|
+
|
|
234
|
+
Prefer to include 4D TaskModal placeholder (small, defensive) but keep it out of
|
|
235
|
+
scope if the patch must be minimal - 4A/4C alone fix the reported bug.
|
|
236
|
+
|
|
237
|
+
## 5. Dedup correctness argument
|
|
238
|
+
|
|
239
|
+
- Source (run.sh): 4B upsert-by-id makes completed.json / failed.json contain at
|
|
240
|
+
most one entry per iteration id, and mutual exclusion across the two files.
|
|
241
|
+
- API (server.py): 4C global id-dedup with column priority guarantees each id
|
|
242
|
+
appears in exactly one column. iteration-13 (currently in inProgress state
|
|
243
|
+
group AND completed queue) collapses to the in_progress card because
|
|
244
|
+
in_progress outranks done. The 5x iteration-1 collapses to one done card.
|
|
245
|
+
- Result on the anonima fixture: 43 -> ~26 unique cards (12 pending + 1
|
|
246
|
+
in_progress + ~13 unique iteration done cards), zero duplicate ids, zero
|
|
247
|
+
cross-column duplication, zero empty-body done cards.
|
|
248
|
+
|
|
249
|
+
## 6. Test plan
|
|
250
|
+
|
|
251
|
+
### 6A. pytest for /api/tasks (primary)
|
|
252
|
+
|
|
253
|
+
Add a test module under the dashboard test suite (co-locate with existing
|
|
254
|
+
server/API tests; confirm the exact dir during implementation, e.g.
|
|
255
|
+
dashboard/tests/ or tests/). Build a fixture .loki dir mirroring anonima:
|
|
256
|
+
|
|
257
|
+
- dashboard-state.json .tasks with: pending = 12 prd-NNN rich stories;
|
|
258
|
+
inProgress = [iteration-13 rich, borrowed prd title]; completed = duplicate
|
|
259
|
+
iteration-1..13 thin markers INCLUDING iteration-13 (to reproduce cross-column
|
|
260
|
+
leak); failed = [iteration-14 thin, iteration-1 thin].
|
|
261
|
+
- queue/completed.json + queue/pending.json mirroring the same.
|
|
262
|
+
|
|
263
|
+
Assertions:
|
|
264
|
+
1. No done-column card has an empty title AND empty description AND empty logs
|
|
265
|
+
(no empty modal payloads). After the fix, done iteration cards carry logs.
|
|
266
|
+
2. Every returned id is unique (len(ids) == len(set(ids))).
|
|
267
|
+
3. iteration-13 appears exactly once, with status in_progress (column-priority
|
|
268
|
+
rule).
|
|
269
|
+
4. All 12 prd-NNN remain in pending (no fake-green: none promoted to done).
|
|
270
|
+
5. Counts: done has one card per unique completed/failed iteration id; no
|
|
271
|
+
duplicates.
|
|
272
|
+
6. Backward-compat: response is a list; each entry still has id/title/
|
|
273
|
+
description/status/priority/type keys.
|
|
274
|
+
|
|
275
|
+
Add a focused unit-style test for the dedup helper (column-priority + richer-
|
|
276
|
+
wins) if it is factored into a function.
|
|
277
|
+
|
|
278
|
+
### 6B. run.sh completed-writer test (shell/behavioral)
|
|
279
|
+
|
|
280
|
+
If the shell test harness supports it (see scripts/local-ci.sh 14/14 dual-route
|
|
281
|
+
suite), add a case that: seeds an in-progress.json with a rich iteration-N record
|
|
282
|
+
(title/desc/logs), calls track_iteration_complete N 0, then asserts the resulting
|
|
283
|
+
completed.json entry (a) has a non-empty logs array carried from in-progress,
|
|
284
|
+
(b) has an honest iteration-scoped title (not "Iteration N" empty-body, not the
|
|
285
|
+
borrowed PRD story title), and (c) contains no duplicate id after two calls with
|
|
286
|
+
the same N (upsert). Keep it provider-agnostic and offline.
|
|
287
|
+
|
|
288
|
+
### 6C. Playwright screenshot of the board
|
|
289
|
+
|
|
290
|
+
Capture :57375/#section=overview against the fixture-backed dashboard (or the
|
|
291
|
+
anonima state) after the fix: assert the DONE column cards show titles + preview
|
|
292
|
+
text (not bare "Iteration N"), clicking a done card opens a populated modal, and
|
|
293
|
+
no card ID is visually duplicated across columns. Store under
|
|
294
|
+
artifacts/<release>-screens/ per CLAUDE.md:227.
|
|
295
|
+
|
|
296
|
+
## 7. Disjoint build lanes
|
|
297
|
+
|
|
298
|
+
Two independent lanes (can be done in parallel, no shared edit region):
|
|
299
|
+
|
|
300
|
+
- Lane 1 (engine): autonomy/run.sh 6390-6444 (completed writer + upsert). Owner
|
|
301
|
+
must respect: additive/corrective to record shape only; no trust/verdict/
|
|
302
|
+
council logic; no version bump/commit; no emoji/em-dash.
|
|
303
|
+
- Lane 2 (dashboard): dashboard/server.py /api/tasks 1780-1914 (global dedup) +
|
|
304
|
+
optional dashboard/frontend TaskModal.tsx placeholder.
|
|
305
|
+
|
|
306
|
+
Lane 3 (tests, after 1+2): pytest fixture (6A), optional shell test (6B),
|
|
307
|
+
Playwright shot (6C).
|
|
308
|
+
|
|
309
|
+
Sequencing: Lane 1 and Lane 2 are independent and each independently improves
|
|
310
|
+
the board; the API dedup (Lane 2) also protects against any not-yet-fixed thin
|
|
311
|
+
records, so it can ship even if Lane 1 slips. Tests depend on both.
|
|
312
|
+
|
|
313
|
+
## 8. Patch release steps (per CLAUDE.md)
|
|
314
|
+
|
|
315
|
+
- Version: current 7.104.1 (VERSION, package.json, CLAUDE.md:306, SKILL.md
|
|
316
|
+
header+footer, plugins/loki-mode/.claude-plugin/plugin.json, wiki/Home.md,
|
|
317
|
+
wiki/_Sidebar.md, docs/INSTALLATION.md - grep "7.104.1" for the full set).
|
|
318
|
+
Bump PATCH -> 7.104.2 in ALL of these (CLAUDE.md:301 mandates header AND footer
|
|
319
|
+
for SKILL.md).
|
|
320
|
+
- CHANGELOG.md: add a new "## [7.104.2] - <date>" section above 7.104.1 with a
|
|
321
|
+
fix entry describing honest iteration done cards + /api/tasks dedup + no
|
|
322
|
+
fake-green prd promotion. (Currently "## [Unreleased]" is "(none)".)
|
|
323
|
+
- Pre-push gate (MANDATORY, CLAUDE.md:318-330): run
|
|
324
|
+
`bash scripts/local-ci.sh` on this Mac; do not push if it says DO NOT PUSH.
|
|
325
|
+
- Commit protocol (CLAUDE.md:290-296): show git diff --stat, stage files
|
|
326
|
+
individually by name (never git add -A), STOP and wait for user approval
|
|
327
|
+
before commit. This plan does not commit.
|
|
328
|
+
- Post-release distribution validation (CLAUDE.md:332-334): npm pack + Docker
|
|
329
|
+
pull smoke of the shipped version.
|
|
330
|
+
|
|
331
|
+
## 9. Open questions
|
|
332
|
+
|
|
333
|
+
1. Done-card title wording: "Iteration N complete - <phase>" vs
|
|
334
|
+
"Iteration N: <phase>, exit 0, <duration>ms". Pick the clearest; both avoid
|
|
335
|
+
the borrowed PRD title. Founder preference?
|
|
336
|
+
2. Should failed iterations render in the done column (current mapping
|
|
337
|
+
failed->done) or a distinct failed column? Out of scope for this patch;
|
|
338
|
+
current behavior preserved. Note for follow-up.
|
|
339
|
+
3. Follow-up (Option B) design: stamp `prd-NNN` source id onto checklist items
|
|
340
|
+
at generation so checklist->story is an exact join, versus surfacing the
|
|
341
|
+
checklist as a separate honest progress panel. Decide before building B.
|
|
342
|
+
4. Exact dashboard test directory + whether a shell harness case for
|
|
343
|
+
track_iteration_complete is supported by scripts/local-ci.sh - confirm at
|
|
344
|
+
implementation time.
|
|
345
|
+
5. Should the API dedup also collapse the stale duplicate ids already persisted
|
|
346
|
+
in old on-disk state (one-time migration), or is display-time dedup
|
|
347
|
+
sufficient? Recommend display-time only (no destructive migration in a patch).
|
|
348
|
+
|
|
349
|
+
## 10. Critical files for implementation
|
|
350
|
+
|
|
351
|
+
- /Users/lokesh/git/loki-tasklist-wt/autonomy/run.sh (track_iteration_complete 6276-6455; track_iteration_start 5370-5569; append_iteration_log 5013-5072; write_dashboard_state 5078-5118)
|
|
352
|
+
- /Users/lokesh/git/loki-tasklist-wt/dashboard/server.py (/api/tasks 1772-1914)
|
|
353
|
+
- /Users/lokesh/git/loki-tasklist-wt/dashboard/frontend/src/components/TaskModal.tsx (optional empty-body placeholder)
|
|
354
|
+
- /Users/lokesh/git/loki-tasklist-wt/CHANGELOG.md (patch entry)
|
|
355
|
+
- /Users/lokesh/git/loki-tasklist-wt/VERSION and package.json (patch bump; plus SKILL.md/CLAUDE.md/plugin.json version strings)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.104.
|
|
2
|
+
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.104.3";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=G8(B8(import.meta.url)),Z=e$(Q);Q$=U8(H8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var c1={};b(c1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>u1,commandVersion:()=>S8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>f1});async function u1($,Q=f1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([u1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=N8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function N8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function S8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var f1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function t($){return D8?"":$}var D8,T,S,_,nZ,I,k,y,J;var c=L(()=>{D8=(process.env.NO_COLOR??"").length>0;T=t("\x1B[0;31m"),S=t("\x1B[0;32m"),_=t("\x1B[1;33m"),nZ=t("\x1B[0;34m"),I=t("\x1B[0;36m"),k=t("\x1B[1m"),y=t("\x1B[2m"),J=t("\x1B[0m")});import{existsSync as c8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(c8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var J0={};b(J0,{runStatus:()=>U3});import{existsSync as v,readFileSync as U$,readdirSync as e1,statSync as $0}from"fs";import{resolve as D,basename as Q3}from"path";import{homedir as Z3}from"os";function Q0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Q0($),U=Q0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function X3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -805,4 +805,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
805
805
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
806
806
|
`),process.stderr.write(z8),2}}i1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var EZ=await RZ(Bun.argv.slice(2));process.exit(EZ);
|
|
807
807
|
|
|
808
|
-
//# debugId=
|
|
808
|
+
//# debugId=7CE53CC97EC3260A64756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.104.
|
|
4
|
+
"version": "7.104.3",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.104.
|
|
5
|
+
"version": "7.104.3",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|