@appchy/jarvis 0.1.80 → 0.1.81

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 CHANGED
@@ -10110,7 +10110,7 @@ import { createRequire as createRequire2 } from "module";
10110
10110
  var _require = createRequire2(import.meta.url);
10111
10111
  var VERSION2 = _require("../package.json").version ?? "0.0.0";
10112
10112
  var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
10113
- var SHA = "959c0ae";
10113
+ var SHA = "fa00fb0";
10114
10114
  var BUILT = "2026-09-11";
10115
10115
  var BUILD = SHA ?? "source";
10116
10116
  var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
@@ -624,7 +624,12 @@ def gate(root, task, accept: str = "", owner: str = "") -> list:
624
624
  # The harness names the files and stops there: committing somebody's source
625
625
  # is a far larger claim on their repo than committing the board, and a board
626
626
  # write deliberately leaves a session's own code alone (founder, 2026-09-10).
627
- loose = git.uncommitted_code(root.parent) if git.enabled() else []
627
+ #
628
+ # Scoped to what this task is answerable for, because a checkout is shared
629
+ # and the unscoped version means nobody completes anything until everybody
630
+ # commits. Unclaimed paths, and a task that declares no regions, stay strict.
631
+ loose = (git.uncommitted_mine(root.parent, root, list(task.code or []))
632
+ if git.enabled() else [])
628
633
  if loose:
629
634
  shown = ", ".join(loose[:5])
630
635
  more = f" and {len(loose) - 5} more" if len(loose) > 5 else ""
@@ -136,6 +136,44 @@ def uncommitted_code(repo) -> list:
136
136
  return sorted(found)
137
137
 
138
138
 
139
+ def uncommitted_mine(repo, root, regions: list) -> list:
140
+ """Of the uncommitted code, what THIS task is answerable for.
141
+
142
+ **`moved_only_elsewhere` already decided this question for commits**, and the
143
+ two have to agree. That one says a commit outside your `code:` regions does not
144
+ invalidate your evidence, because a suite failure over there only warns — and
145
+ holding that while letting a merely DIRTY file over there refuse you is the same
146
+ inconsistency, read one step earlier.
147
+
148
+ It is not hypothetical. A session finishing harness work was blocked by a peer's
149
+ 19 uncommitted UI files in a shared checkout, none of them in the region its
150
+ task declared. In a checkout two people share, the unscoped rule means nobody
151
+ can complete anything until everybody commits.
152
+
153
+ **Unclaimed is always yours, and that is the whole safety of it.**
154
+ `system_for_path` answers `None` both for a file nothing declares and for a repo
155
+ whose systems declare nothing, and from here the two are indistinguishable.
156
+ Reading either as *not my business* would make the check vacuous in exactly the
157
+ repos that never filled in `paths:`. A task that declares no `code:` of its own
158
+ is answerable for all of it, for the same reason: it has not said what it
159
+ touches, so it cannot say a file is somebody else's.
160
+ """
161
+ loose = uncommitted_code(repo)
162
+ if not loose or not regions:
163
+ return loose
164
+ from .registry import scan_systems, system_for_path
165
+ systems = scan_systems(root)
166
+ if not systems:
167
+ return loose
168
+ mine = {r for r in regions if r}
169
+ kept = []
170
+ for name in loose:
171
+ owner = system_for_path(root, name, systems)
172
+ if owner is None or (mine & set(owner.code)):
173
+ kept.append(name)
174
+ return kept
175
+
176
+
139
177
  def machine() -> str:
140
178
  """Which machine acted. `WORK_MACHINE` where something knows a better name for
141
179
  this box than its hostname — a daemon that already has an identity for it —
@@ -3634,6 +3634,95 @@ def test_partly_committed_code_is_still_uncommitted_code():
3634
3634
  config.apply(config.DEFAULTS)
3635
3635
 
3636
3636
 
3637
+ def test_a_peers_uncommitted_work_in_another_system_does_not_block_you():
3638
+ # The mirror of `moved_only_elsewhere`, and it has to agree with it: if a
3639
+ # COMMIT outside your regions cannot invalidate your evidence, a merely DIRTY
3640
+ # file outside them cannot refuse you either. Holding both at once is the
3641
+ # inconsistency, read one step earlier.
3642
+ #
3643
+ # Measured rather than imagined: the session that built the unscoped version
3644
+ # was blocked by a peer's 19 uncommitted UI files, none in the region its task
3645
+ # declared. In a shared checkout that rule means nobody completes anything
3646
+ # until everybody commits.
3647
+ with tempfile.TemporaryDirectory() as tmp:
3648
+ try:
3649
+ repo = _git_repo(tmp, push=False)
3650
+ arch = repo / "work" / "architecture"
3651
+ arch.mkdir(parents=True, exist_ok=True)
3652
+ (arch / "board.md").write_text(
3653
+ "---\ntype: system\ncode: [data]\npaths: [packages/board]\n"
3654
+ "depends_on: []\n---\n\n# Board\n")
3655
+ (arch / "screen.md").write_text(
3656
+ "---\ntype: system\ncode: [web]\npaths: [apps/web]\n"
3657
+ "depends_on: []\n---\n\n# Screen\n")
3658
+ for f in ("packages/board/store.ts", "apps/web/page.tsx", "scripts/loose.py"):
3659
+ (repo / f).parent.mkdir(parents=True, exist_ok=True)
3660
+ (repo / f).write_text("first\n")
3661
+ _provable_task(repo) # commits the board AND the code above
3662
+
3663
+ root = repo / "work"
3664
+ # The task says what it touches, which is what earns it the narrowing.
3665
+ brief = root / "versions" / "01-a-cut" / "an-epic" / "in-progress" / "alpha" / "task.md"
3666
+ brief.write_text(brief.read_text().replace(
3667
+ "priority: P0", "priority: P0\ncode: [data]"))
3668
+ _git(repo, "add", "-A")
3669
+ _git(repo, "commit", "-qm", "alpha declares its region")
3670
+
3671
+ with _work_dir(str(root)) as work_root:
3672
+ old, gate.VERIFY = gate.VERIFY, {"tests": "true"}
3673
+ try:
3674
+ assert gate.cmd_verify({"task": "alpha"}) == 0
3675
+
3676
+ # A PEER IS MID-EDIT IN ANOTHER SYSTEM. You can still finish.
3677
+ (repo / "apps/web/page.tsx").write_text("a peer, mid-thought\n")
3678
+ assert gate.gate(work_root, model.locate(work_root, "alpha")) == []
3679
+
3680
+ # YOUR OWN REGION, UNCOMMITTED. That is the thing this catches.
3681
+ (repo / "packages/board/store.ts").write_text("the fix, uncommitted\n")
3682
+ reasons = gate.gate(work_root, model.locate(work_root, "alpha"))
3683
+ assert any("packages/board/store.ts" in r for r in reasons), reasons
3684
+ assert not any("apps/web/page.tsx" in r for r in reasons), reasons
3685
+
3686
+ # A PATH NOTHING CLAIMS IS ALWAYS YOURS. The safety property: a
3687
+ # repo that never filled in `paths:` must not get a gate that
3688
+ # passes everything, which is worse than the noise it replaces.
3689
+ _git(repo, "checkout", "--", "packages/board/store.ts")
3690
+ (repo / "scripts/loose.py").write_text("claimed by no system\n")
3691
+ reasons = gate.gate(work_root, model.locate(work_root, "alpha"))
3692
+ assert any("scripts/loose.py" in r for r in reasons), reasons
3693
+ finally:
3694
+ gate.VERIFY = old
3695
+ finally:
3696
+ config.apply(config.DEFAULTS)
3697
+
3698
+
3699
+ def test_a_task_that_declares_no_region_is_answerable_for_all_of_it():
3700
+ # It has not said what it touches, so it cannot say a file is somebody else's.
3701
+ # The same reading `moved_only_elsewhere` gives an empty `regions`.
3702
+ with tempfile.TemporaryDirectory() as tmp:
3703
+ try:
3704
+ repo = _git_repo(tmp, push=False)
3705
+ arch = repo / "work" / "architecture"
3706
+ arch.mkdir(parents=True, exist_ok=True)
3707
+ (arch / "screen.md").write_text(
3708
+ "---\ntype: system\ncode: [web]\npaths: [apps/web]\n"
3709
+ "depends_on: []\n---\n\n# Screen\n")
3710
+ (repo / "apps/web").mkdir(parents=True)
3711
+ (repo / "apps/web/page.tsx").write_text("first\n")
3712
+ _provable_task(repo)
3713
+ with _work_dir(str(repo / "work")) as root:
3714
+ old, gate.VERIFY = gate.VERIFY, {"tests": "true"}
3715
+ try:
3716
+ assert gate.cmd_verify({"task": "alpha"}) == 0
3717
+ (repo / "apps/web/page.tsx").write_text("changed\n")
3718
+ reasons = gate.gate(root, model.locate(root, "alpha"))
3719
+ assert any("apps/web/page.tsx" in r for r in reasons), reasons
3720
+ finally:
3721
+ gate.VERIFY = old
3722
+ finally:
3723
+ config.apply(config.DEFAULTS)
3724
+
3725
+
3637
3726
  def test_a_repo_that_never_asked_for_git_completes_exactly_as_before():
3638
3727
  # The harness is shared with repos that opted out of all of this. Refusing on
3639
3728
  # their working tree would be a new claim on somebody who asked for none.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appchy/jarvis",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
4
4
  "description": "Jarvis — local AI coding assistant CLI",
5
5
  "private": false,
6
6
  "type": "module",
@@ -57,15 +57,15 @@
57
57
  "typescript": "^5.7.0",
58
58
  "vitest": "^2.1.0",
59
59
  "@jarvis/agents": "1.0.0",
60
- "@jarvis/board": "0.1.0",
61
60
  "@jarvis/anthropic": "1.0.0",
61
+ "@jarvis/board": "0.1.0",
62
62
  "@jarvis/data": "0.1.0",
63
63
  "@jarvis/errors": "1.0.0",
64
64
  "@jarvis/logger": "1.0.0",
65
65
  "@jarvis/rpc": "1.0.0",
66
66
  "@jarvis/types": "1.0.0",
67
- "@jarvis/ui": "0.1.0",
68
67
  "@jarvis/typescript-config": "1.0.0",
68
+ "@jarvis/ui": "0.1.0",
69
69
  "@jarvis/vitest-config": "1.0.0"
70
70
  },
71
71
  "scripts": {