@appchy/jarvis 0.1.73 → 0.1.74
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/dist/bin.js +1 -1
- package/harness/harness/migrate.py +56 -6
- package/harness/test_work.py +26 -0
- package/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -10109,7 +10109,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
10109
10109
|
var _require = createRequire2(import.meta.url);
|
|
10110
10110
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
10111
10111
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
10112
|
-
var SHA = "
|
|
10112
|
+
var SHA = "9566241";
|
|
10113
10113
|
var BUILT = "2026-09-10";
|
|
10114
10114
|
var BUILD = SHA ?? "source";
|
|
10115
10115
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
@@ -51,11 +51,26 @@ def _uncommitted(repo: Path, work: Path) -> list:
|
|
|
51
51
|
return sorted(dirty)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _archived_cuts(root: Path) -> list:
|
|
55
|
+
"""Cuts sitting in the archive's former home, `work/archive/versions/<cut>/`.
|
|
56
|
+
|
|
57
|
+
Every repo that ever archived a cut has these, and the layout change made them
|
|
58
|
+
UNREACHABLE: `locate_version` looks in `versions/` and `versions/archive/`, so
|
|
59
|
+
`path` and `where` answer "no version named that" for a record the repo still
|
|
60
|
+
holds. jarvis had archived nothing, which is why the first migration never met
|
|
61
|
+
this and three siblings each have seven to eleven of them.
|
|
62
|
+
"""
|
|
63
|
+
old = root / "archive" / "versions"
|
|
64
|
+
if not old.is_dir():
|
|
65
|
+
return []
|
|
66
|
+
return sorted(p for p in old.iterdir() if (p / "version.md").is_file())
|
|
67
|
+
|
|
68
|
+
|
|
54
69
|
def cmd_migrate(args) -> int:
|
|
55
|
-
"""`work/backlog/`
|
|
70
|
+
"""`work/backlog/` and the archive both move inside `versions/`.
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
One layout: backlog and archive are states a cut's work is in, so they belong
|
|
73
|
+
beside the cuts rather than in directories of their own.
|
|
59
74
|
"""
|
|
60
75
|
root = find_work_root()
|
|
61
76
|
repo = root.parent
|
|
@@ -64,18 +79,26 @@ def cmd_migrate(args) -> int:
|
|
|
64
79
|
old = root / "backlog"
|
|
65
80
|
new = backlog_dir(root)
|
|
66
81
|
archive = archive_dir(root)
|
|
82
|
+
cuts = _archived_cuts(root)
|
|
67
83
|
|
|
68
|
-
# The
|
|
69
|
-
# or a repo has grown a second
|
|
70
|
-
# about which copy
|
|
84
|
+
# The hard refusals. Two of anything means somebody has already started this,
|
|
85
|
+
# or a repo has grown a second copy — and merging two trees is a judgement
|
|
86
|
+
# about which copy is real, which is not a call a migration makes.
|
|
71
87
|
if old.is_dir() and new.is_dir():
|
|
72
88
|
die(f"both {rel(old, root)} and {rel(new, root)} exist — this repo has two "
|
|
73
89
|
f"backlogs and only a person can say which task is the real one. "
|
|
74
90
|
f"Merge them by hand, then run this again.")
|
|
91
|
+
clashes = [c.name for c in cuts if (archive / c.name).exists()]
|
|
92
|
+
if clashes:
|
|
93
|
+
die(f"{len(clashes)} cut(s) are archived in both places: "
|
|
94
|
+
f"{', '.join(clashes)}. Which record is the real one is yours to say — "
|
|
95
|
+
f"merge them by hand, then run this again.")
|
|
75
96
|
|
|
76
97
|
todo = []
|
|
77
98
|
if old.is_dir():
|
|
78
99
|
todo.append("the backlog moves inside versions/")
|
|
100
|
+
if cuts:
|
|
101
|
+
todo.append(f"{len(cuts)} archived cut(s) move inside versions/")
|
|
79
102
|
if not archive.is_dir():
|
|
80
103
|
todo.append("versions/archive/ is created")
|
|
81
104
|
if not todo:
|
|
@@ -88,6 +111,8 @@ def cmd_migrate(args) -> int:
|
|
|
88
111
|
print(f" {line}")
|
|
89
112
|
if old.is_dir():
|
|
90
113
|
print(f" {sum(1 for _ in old.rglob('task.md'))} task(s) would move")
|
|
114
|
+
for c in cuts:
|
|
115
|
+
print(f" archived: {c.name}")
|
|
91
116
|
return 0
|
|
92
117
|
|
|
93
118
|
dirty = _uncommitted(repo, root)
|
|
@@ -124,6 +149,31 @@ def cmd_migrate(args) -> int:
|
|
|
124
149
|
(archive / ".gitkeep").write_text("")
|
|
125
150
|
print(f"created {rel(archive, root)}")
|
|
126
151
|
|
|
152
|
+
if cuts:
|
|
153
|
+
# One block for every cut. Each carries a whole released version's worth of
|
|
154
|
+
# documents, and the links between them are the reason this is worth doing
|
|
155
|
+
# properly rather than with a `mv`.
|
|
156
|
+
with links.repairing(repo) as moved:
|
|
157
|
+
for c in cuts:
|
|
158
|
+
moved[c.resolve()] = (archive / c.name).resolve()
|
|
159
|
+
shutil.move(str(c), str(archive / c.name))
|
|
160
|
+
print(f"moved {len(cuts)} archived cut(s) -> {rel(archive, root)}")
|
|
161
|
+
|
|
162
|
+
# What is left under the old archive is NOT the harness's. One repo keeps
|
|
163
|
+
# an `archive/backlog/` of its own there; moving it would be a guess about
|
|
164
|
+
# somebody else's records, and deleting the directory would take it with.
|
|
165
|
+
stale_home = root / "archive"
|
|
166
|
+
leftover = sorted(p.name for p in stale_home.iterdir()) if stale_home.is_dir() else []
|
|
167
|
+
leftover = [n for n in leftover if n != "versions"]
|
|
168
|
+
versions_dir = stale_home / "versions"
|
|
169
|
+
if versions_dir.is_dir() and not any(versions_dir.iterdir()):
|
|
170
|
+
versions_dir.rmdir()
|
|
171
|
+
if leftover:
|
|
172
|
+
print(f" {rel(stale_home, root)} still holds {', '.join(leftover)} — "
|
|
173
|
+
f"the harness does not own those, so they were left alone")
|
|
174
|
+
elif stale_home.is_dir() and not any(stale_home.iterdir()):
|
|
175
|
+
stale_home.rmdir()
|
|
176
|
+
|
|
127
177
|
print("done — commit this, then every machine working this repo needs a CLI "
|
|
128
178
|
"new enough to read it")
|
|
129
179
|
return 0
|
package/harness/test_work.py
CHANGED
|
@@ -5520,6 +5520,32 @@ def test_migrate_moves_the_backlog_inside_versions_and_repairs_what_pointed_at_i
|
|
|
5520
5520
|
f"a link outside work/ was left pointing at {href}"
|
|
5521
5521
|
|
|
5522
5522
|
|
|
5523
|
+
def test_migrate_brings_already_archived_cuts_to_where_lookups_look():
|
|
5524
|
+
# The layout change moved where `archive` WRITES without moving what it had
|
|
5525
|
+
# already written, and `locate_version` only reads the new place — so every
|
|
5526
|
+
# cut a repo had archived answered "no version named that". jarvis had
|
|
5527
|
+
# archived none, which is why this survived the first migration.
|
|
5528
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
5529
|
+
_tree(tmp)
|
|
5530
|
+
root = Path(tmp)
|
|
5531
|
+
(root / "versions" / "backlog").rmdir()
|
|
5532
|
+
(root / "backlog").mkdir()
|
|
5533
|
+
old = root / "archive" / "versions" / "09-shipped"
|
|
5534
|
+
old.mkdir(parents=True)
|
|
5535
|
+
(old / "version.md").write_text(
|
|
5536
|
+
"---\ncreated: 2026-01-01\norder: 9\noutcome: x\nreleased: 2026-02-01\n"
|
|
5537
|
+
"---\n\n# Shipped\n")
|
|
5538
|
+
# Something the harness does not own, in the same directory.
|
|
5539
|
+
(root / "archive" / "backlog" / "an-old-idea").mkdir(parents=True)
|
|
5540
|
+
|
|
5541
|
+
assert _migrate(tmp) == 0
|
|
5542
|
+
assert (root / "versions" / "archive" / "09-shipped" / "version.md").is_file()
|
|
5543
|
+
assert model.locate_version(root, "09-shipped") is not None, \
|
|
5544
|
+
"an archived cut is still not lookupable after migrating"
|
|
5545
|
+
assert (root / "archive" / "backlog" / "an-old-idea").is_dir(), \
|
|
5546
|
+
"the migration moved records the harness does not own"
|
|
5547
|
+
|
|
5548
|
+
|
|
5523
5549
|
def test_migrate_says_nothing_to_do_on_a_repo_already_migrated():
|
|
5524
5550
|
# Nobody remembers which repos have been through it, so the safe answer to
|
|
5525
5551
|
# running it twice has to be "nothing", not a second move or an error.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appchy/jarvis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "Jarvis — local AI coding assistant CLI",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"tsup": "^8.5.1",
|
|
57
57
|
"typescript": "^5.7.0",
|
|
58
58
|
"vitest": "^2.1.0",
|
|
59
|
-
"@jarvis/
|
|
59
|
+
"@jarvis/anthropic": "1.0.0",
|
|
60
60
|
"@jarvis/board": "0.1.0",
|
|
61
61
|
"@jarvis/data": "0.1.0",
|
|
62
62
|
"@jarvis/errors": "1.0.0",
|
|
63
|
+
"@jarvis/logger": "1.0.0",
|
|
63
64
|
"@jarvis/rpc": "1.0.0",
|
|
64
65
|
"@jarvis/types": "1.0.0",
|
|
65
66
|
"@jarvis/typescript-config": "1.0.0",
|
|
66
67
|
"@jarvis/ui": "0.1.0",
|
|
67
68
|
"@jarvis/vitest-config": "1.0.0",
|
|
68
|
-
"@jarvis/
|
|
69
|
-
"@jarvis/logger": "1.0.0"
|
|
69
|
+
"@jarvis/agents": "1.0.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"dev": "tsx watch src/bin.ts start --foreground",
|