@appchy/jarvis 0.1.132 → 0.1.133

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
@@ -10402,7 +10402,7 @@ import { createRequire as createRequire2 } from "module";
10402
10402
  var _require = createRequire2(import.meta.url);
10403
10403
  var VERSION2 = _require("../package.json").version ?? "0.0.0";
10404
10404
  var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
10405
- var SHA = "361e631";
10405
+ var SHA = "9b15888";
10406
10406
  var BUILT = "2026-09-12";
10407
10407
  var BUILD = SHA ?? "source";
10408
10408
  var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
@@ -167,6 +167,16 @@ DEFAULTS = {
167
167
  # interpolated. A repo may run anything it likes; what it may not do is get a
168
168
  # `;` to mean `;`.
169
169
  "verify": {},
170
+ # How much open work an epic and a cut may hold before the board says so. Counted in
171
+ # OPEN tasks, because what is hard to plan together or to close is what is still owed.
172
+ # Neither is a gate: an epic past its ceiling warns and a written `one_goal:` answers
173
+ # it; a cut past its ceiling is flagged as owing a reshape and nothing is refused or
174
+ # routed. The numbers are a repo's own — a harness serving repos of very different
175
+ # sizes cannot know them — and these are only what a repo that sets nothing gets.
176
+ "ceilings": {
177
+ "epic": 8,
178
+ "cut": 15,
179
+ },
170
180
  "autonomy": {
171
181
  # The highest `tier:` an unattended run acts on ALONE. Default 2 — a
172
182
  # scheduled shift does ordinary work and stops at the irreversible.
@@ -458,6 +468,12 @@ def _validate(cfg: dict) -> None:
458
468
  f"without a shell (shell=False), so shell operators would be "
459
469
  f"passed as literal arguments. Put the composition in a script "
460
470
  f"and call that.")
471
+ for key in ("epic", "cut"):
472
+ value = cfg["ceilings"][key]
473
+ # `bool` is an `int` in Python, and `true` is not a number of tasks.
474
+ if not isinstance(value, int) or isinstance(value, bool) or value < 1:
475
+ raise ConfigError(f"ceilings.{key} must be a whole number of open tasks, 1 or more "
476
+ f"(got {value!r}) — 0 would flag every {key} that holds any open work")
461
477
  ceiling = cfg["autonomy"]["ceiling"]
462
478
  if not isinstance(ceiling, int) or isinstance(ceiling, bool) or not 0 <= ceiling <= 3:
463
479
  raise ConfigError("autonomy.ceiling must be an integer 0–3 (the highest "
@@ -650,6 +666,10 @@ def apply(cfg: dict) -> None:
650
666
  gate.VERIFY = dict(cfg["verify"])
651
667
  task.PLANS_DIR = cfg["plans"]["dir"]
652
668
  autonomy.CEILING = cfg["autonomy"]["ceiling"]
669
+ # Read through `tree` by the lint and the model, never imported by name, so binding it
670
+ # here is what makes a repo's own number the one in force.
671
+ tree.EPIC_TASK_CEILING = cfg["ceilings"]["epic"]
672
+ tree.CUT_TASK_CEILING = cfg["ceilings"]["cut"]
653
673
  # `shift` reads the ceiling it was imported with, so bind it there too — a
654
674
  # module-level `from … import CEILING` captures the value, not the name.
655
675
  shift.CEILING = cfg["autonomy"]["ceiling"]
@@ -6,6 +6,7 @@ from .frontmatter import rewrite_file
6
6
  from .model import epic_home, locate_epic, locate_version
7
7
  from .scaffold import _check_covers_ref, _check_kebab, _check_unused, _scaffold_epic
8
8
  from .generate import _sync
9
+ from .lint import say_ceiling
9
10
  from . import events, links
10
11
 
11
12
 
@@ -210,6 +211,9 @@ def cmd_epic_move(args) -> int:
210
211
  )
211
212
  print(f"pulled epic '{name}' ({was}) -> {rel(dest, root)} "
212
213
  f"with {len(moved)} task(s)")
214
+ # A whole epic arriving is the likeliest way a cut goes past its ceiling in one
215
+ # move, so it is told exactly as filing or placing a single task is.
216
+ say_ceiling(root, version.name)
213
217
  _sync(root)
214
218
  return 0
215
219
  #: Headings whose content is a STATEMENT ABOUT THE WORLD rather than a plan for one
@@ -13,7 +13,7 @@ from .tree import (
13
13
  )
14
14
  from .frontmatter import as_list, parse_frontmatter, split_frontmatter
15
15
  from . import tree as _tree
16
- from .model import locate_feature, scan, scan_features, scan_filed
16
+ from .model import locate_feature, locate_version, scan, scan_features, scan_filed
17
17
  from .registry import code_vocabulary, locate_domain
18
18
 
19
19
  #: The MCP server this repo names as its graph engine, bound by `config.apply`.
@@ -591,6 +591,17 @@ def ceiling_note(version) -> str:
591
591
  f"its version.md")
592
592
 
593
593
 
594
+ def say_ceiling(root, version_name: str) -> None:
595
+ """Tell whoever just added work to a cut that it is past its ceiling, if it is.
596
+
597
+ One home for every command that lands work in a cut — filing a task, placing one,
598
+ moving a whole epic — so a new way in cannot forget to say it."""
599
+ landed = locate_version(root, version_name)
600
+ note = ceiling_note(landed) if landed else ""
601
+ if note:
602
+ print(f"note: {note}")
603
+
604
+
594
605
  def print_lint(root: Path):
595
606
  for w in lint_warnings(root):
596
607
  print(f" WARN {w}", file=sys.stderr)
@@ -27,6 +27,7 @@ from .tree import BLOCKED, BUCKETS, die, find_work_root, rel
27
27
  from .frontmatter import rewrite_file
28
28
  from .model import locate, record_session, scan
29
29
  from .generate import _sync
30
+ from .lint import ceiling_note
30
31
  from .epic import plans_held
31
32
  from . import autonomy, events, holders, links, peers
32
33
  # The ceiling is read through the MODULE, never bound in with `from … import`.
@@ -251,7 +252,14 @@ def cmd_status(args) -> int:
251
252
  # every epic plan and cannot be undone — so this announces and never acts, and it
252
253
  # quotes the price so the decision is made with it visible rather than after.
253
254
  ready = [v for v in s["versions"] if v.finishable()]
254
- print(f"WAITING ON YOU ({len(waiting) + len(ready)})")
255
+ # A cut past its ceiling owes a reshape, and where its work goes is a person's call —
256
+ # so it waits on them HERE, beside a finished cut nobody has closed, rather than only
257
+ # scrolling past as a warning after some unrelated write. Same sentence as the
258
+ # warning, from the one function that words it.
259
+ full = [v for v in live if v.over_ceiling()]
260
+ print(f"WAITING ON YOU ({len(waiting) + len(ready) + len(full)})")
261
+ for v in full:
262
+ print(f" {ceiling_note(v)}")
255
263
  for v in ready:
256
264
  plans, size, held = plans_held(v)
257
265
  cost = ""
@@ -264,7 +272,7 @@ def cmd_status(args) -> int:
264
272
  for t, q in waiting[:8]:
265
273
  parts = q.split(" ")
266
274
  print(f" {t.name}: {' '.join(parts[2:]) if len(parts) > 2 else q}")
267
- if not waiting and not ready:
275
+ if not waiting and not ready and not full:
268
276
  print(" nothing — the shift is not blocked on you.")
269
277
 
270
278
  # One read of the record, shared by every section below. It is a `git log`
@@ -15,6 +15,7 @@ from .registry import code_vocabulary
15
15
  from .scaffold import _check_kebab, _check_owner_ref, _check_priority, _check_unused, _scaffold_handoff, _scaffold_task
16
16
  from .epic import _bucketed, cmd_epic_move, epic_for_task
17
17
  from .generate import _sync
18
+ from .lint import say_ceiling
18
19
  from .gate import delivery_gate, report_gate
19
20
  from .autonomy import _open_questions, derive_tier
20
21
  from . import events
@@ -122,6 +123,12 @@ def cmd_new(args) -> int:
122
123
  epic=epic.name)
123
124
  print(f"created {rel(folder / 'task.md', root)} ({home})")
124
125
  print(f" tier {tier} — {tier_why}")
126
+ # Filing into a full cut is never refused and never re-routed: where this task
127
+ # belongs is a planning call. What the caller is owed is the fact, addressed to
128
+ # them as a `note:` — which a door relaying the harness's words hands to an agent,
129
+ # rather than leaving it in the board-wide warnings nobody filing over a tool sees.
130
+ if version_name:
131
+ say_ceiling(root, version_name)
125
132
  _sync(root)
126
133
  return 0
127
134
  def cmd_place(args) -> int:
@@ -198,6 +205,9 @@ def cmd_place(args) -> int:
198
205
  if restored:
199
206
  events.append(root, "restored", name, to=f"{version.name}/{epic.name}")
200
207
  print(f"{'restored' if restored else 'placed'} '{name}' -> {rel(dest, root)}")
208
+ # Placing is a person arranging a cut on purpose, and a pre-planned cut is legal —
209
+ # so it is told, never stopped, exactly as filing is.
210
+ say_ceiling(root, version.name)
201
211
  _sync(root)
202
212
  return 0
203
213
 
@@ -329,7 +329,16 @@ design is done ONCE for a whole goal; at one or two tasks there is no "whole goa
329
329
  - **Under the floor with real depth behind it → cut the tasks, don't merge.** An epic whose Plan lists
330
330
  five slices and has one folder is *under-cut*. An epic with **zero** tasks is the worst case: a goal
331
331
  nobody has cut work for has not been planned, only wished.
332
- - **The floor is a floor, not a target.** A long-running epic at nine tasks is fine.
332
+ - **The floor is a floor, and there is a ceiling too.** Past **8 open tasks** an epic warns: it is no
333
+ longer a goal anybody plans together. Split it along its goals, or say why its size is deliberate in
334
+ `one_goal:`. Open tasks only — a long-running epic whose work has mostly shipped is fine at any size.
335
+ (This bullet used to say "a long-running epic at nine tasks is fine", which the ceiling made false.)
336
+ - **A cut has a ceiling of 15 open tasks, summed across its epics, and a full cut is FLAGGED — never
337
+ refused, never routed.** Filing into one still lands where you filed it; the board then says a
338
+ reshape is owed until somebody resolves it — a later cut, or a reshaped epic — so the cut that closes
339
+ is not a half-built one. **That call is a person's: an agent parks it rather than moving work.** A cut
340
+ planned big on purpose says why in `one_goal:` in its `version.md`. The number of epics is not
341
+ bounded, only the sum. Both numbers are a repo's to set, under `ceilings` in its config.
333
342
 
334
343
  **Merging epics is prose work, not folder work.** Moving the folders takes one `mv`; what matters is
335
344
  that the surviving `epic.md` absorbs every settled call from the ones being dissolved *before* their
@@ -185,6 +185,25 @@
185
185
  "default": {},
186
186
  "description": "Commands run to verify work, by name — e.g. {\"test\": \"<this repo's test command>\"}. Free-form because toolchains differ, and naming one here would ship a bias. EXECUTED by `work verify` and required by the completion gate: a task cannot reach complete/ without a passing record. Each is split with shlex and run with shell=False, so shell operators (&&, ||, ;, |, >, $(), backticks) are REFUSED at load rather than passed to the first binary as literal arguments — put the composition in a script and call that."
187
187
  },
188
+ "ceilings": {
189
+ "type": "object",
190
+ "additionalProperties": false,
191
+ "description": "How much OPEN work an epic and a cut may hold before the board says so. Neither is a gate: an epic past its ceiling warns and a written `one_goal:` answers it; a cut past its ceiling is flagged as owing a reshape — move work to a later cut, or reshape an epic — and nothing is refused or routed, because where a task belongs is a person's call.",
192
+ "properties": {
193
+ "epic": {
194
+ "type": "integer",
195
+ "minimum": 1,
196
+ "default": 8,
197
+ "description": "Open tasks an epic may hold before it warns that it is no longer a goal anybody plans together."
198
+ },
199
+ "cut": {
200
+ "type": "integer",
201
+ "minimum": 1,
202
+ "default": 15,
203
+ "description": "Open tasks a cut may hold, summed across its epics, before it is flagged as owing a reshape. The number of epics is not bounded, only this sum."
204
+ }
205
+ }
206
+ },
188
207
  "autonomy": {
189
208
  "type": "object",
190
209
  "additionalProperties": false,
@@ -6645,6 +6645,10 @@ def test_every_shipped_default_is_mechanics_or_nothing():
6645
6645
  # On by default like its two siblings, and for their reason: a hook the
6646
6646
  # harness ships is its own mechanics, not a fact about somebody's setup.
6647
6647
  "hooks.pre_tool_use.enabled",
6648
+ # How big the harness's own tiers may get before it says so — its model, like
6649
+ # the autonomy ceiling beside it. The numbers were chosen against live counts
6650
+ # across four boards, and a repo that disagrees sets its own.
6651
+ "ceilings.epic", "ceilings.cut",
6648
6652
  }
6649
6653
  shipped = set()
6650
6654
 
@@ -9005,6 +9009,197 @@ def test_the_readme_never_shows_an_archived_item():
9005
9009
  finally:
9006
9010
  os.environ.pop("WORK_DIR")
9007
9011
 
9012
+
9013
+ # ---------------------------------------------------------------------------
9014
+ # A cut has a ceiling — and an epic does too.
9015
+ #
9016
+ # Nothing bounded how much a board may describe. mixbrix filed 73 items in one day
9017
+ # and reached 690; this repo's own cut 02 reached 24 open. Founder's calls,
9018
+ # 2026-09-12: count OPEN tasks, 8 per epic and 15 per cut by default, per repo. An
9019
+ # epic past its ceiling warns and a written `one_goal:` answers it. A cut past its
9020
+ # ceiling is FLAGGED as owing a reshape — never refused and never routed, because
9021
+ # where a task belongs is a planning call: "it should be resolved and either be in a
9022
+ # new cut or the epic, depends on the task … to make sure we dont land a half baked
9023
+ # cut".
9024
+ # ---------------------------------------------------------------------------
9025
+
9026
+
9027
+ def _open_epic(v: Path, name: str, queued: int = 0, done: int = 0, one_goal: str = ""):
9028
+ e = _epic(v, name, owner="quality", one_goal=one_goal)
9029
+ for bucket, n in (("queue", queued), ("complete", done)):
9030
+ for i in range(n):
9031
+ _task(e / bucket, f"{name}-{bucket}-{i}")
9032
+ return e
9033
+
9034
+
9035
+ def _ceiling_warnings(root: Path, needle: str) -> list:
9036
+ return [w for w in lint.lint_warnings(root) if needle in w]
9037
+
9038
+
9039
+ def test_an_epic_warns_only_once_it_is_past_its_ceiling_of_open_tasks():
9040
+ # AT the ceiling is legal; one past it is not. Asserted side by side, because an
9041
+ # off-by-one here is the difference between a guard and a nag.
9042
+ with tempfile.TemporaryDirectory() as tmp:
9043
+ v = _tree(tmp, "26-cut")
9044
+ _open_epic(v, "at-the-ceiling", queued=8)
9045
+ _open_epic(v, "one-past-it", queued=9)
9046
+ warned = _ceiling_warnings(Path(tmp), "open task(s)")
9047
+ assert not any("at-the-ceiling" in w for w in warned), warned
9048
+ assert any("one-past-it" in w for w in warned), warned
9049
+
9050
+
9051
+ def test_finished_work_does_not_count_against_an_epic():
9052
+ # An epic whose work has mostly shipped is not hard to plan together. Counting
9053
+ # finished tasks would warn on every long-lived goal about work nobody is still
9054
+ # doing — which is why this bound counts OPEN tasks while the floor counts all.
9055
+ with tempfile.TemporaryDirectory() as tmp:
9056
+ v = _tree(tmp, "26-cut")
9057
+ _open_epic(v, "mostly-shipped", queued=3, done=20)
9058
+ assert not _ceiling_warnings(Path(tmp), "open task(s)")
9059
+
9060
+
9061
+ def test_an_epic_that_wrote_why_its_size_is_deliberate_is_not_warned():
9062
+ # The same written escape the floor and the region cap already honour — not a
9063
+ # fourth mechanism. An empty one would exempt nothing, as it does for those.
9064
+ with tempfile.TemporaryDirectory() as tmp:
9065
+ v = _tree(tmp, "26-cut")
9066
+ _open_epic(v, "big-on-purpose", queued=12, one_goal="one migration, cut in steps")
9067
+ assert not _ceiling_warnings(Path(tmp), "open task(s)")
9068
+
9069
+
9070
+ def test_a_cut_is_flagged_when_its_SUMMED_open_tasks_pass_its_ceiling():
9071
+ # Two epics of eight and seven: neither warns on its own, and the cut is exactly
9072
+ # full. One more open task anywhere and the cut owes a reshape. Epic COUNT is not
9073
+ # bounded — only the sum — so splitting an epic can never make this worse.
9074
+ with tempfile.TemporaryDirectory() as tmp:
9075
+ root = Path(tmp)
9076
+ v = _tree(tmp, "26-cut")
9077
+ _open_epic(v, "half-a", queued=8)
9078
+ _open_epic(v, "half-b", queued=7)
9079
+ assert model.locate_version(root, "26-cut").over_ceiling() == 0
9080
+ _open_epic(v, "one-more", queued=1, done=5)
9081
+ assert model.locate_version(root, "26-cut").over_ceiling() == 1
9082
+ assert len(_ceiling_warnings(root, "past its ceiling")) == 1
9083
+ assert not _ceiling_warnings(root, "open task(s)"), "no single epic is past 8"
9084
+
9085
+
9086
+ def _full_cut(v: Path) -> Path:
9087
+ """Two epics at exactly their ceiling of eight: neither warns on its own, and the
9088
+ cut is one past its ceiling of fifteen. Returns the first, to file into."""
9089
+ first = _open_epic(v, "a", queued=8)
9090
+ _open_epic(v, "b", queued=8)
9091
+ return first
9092
+
9093
+
9094
+ def test_the_cut_flag_names_both_ways_out_and_whose_call_it_is():
9095
+ # A flag that only says "too many" leaves the reader to invent the remedy, and the
9096
+ # remedy an agent invents is moving work around — exactly the call that is not
9097
+ # its to make.
9098
+ with tempfile.TemporaryDirectory() as tmp:
9099
+ _full_cut(_tree(tmp, "26-cut"))
9100
+ [flag] = _ceiling_warnings(Path(tmp), "past its ceiling")
9101
+ assert "later cut" in flag and "reshape an epic" in flag, flag
9102
+ assert "person's call" in flag, flag
9103
+
9104
+
9105
+ def test_filing_into_a_full_cut_is_never_refused_and_never_rerouted():
9106
+ # The founder's call in one assertion: the item lands exactly where it was filed.
9107
+ # A refusal mid-sweep is what sessions route around, and a silent reroute decides
9108
+ # which half of a goal ships.
9109
+ with tempfile.TemporaryDirectory() as tmp:
9110
+ full = _full_cut(_tree(tmp, "26-cut"))
9111
+ with _work_dir(tmp) as root:
9112
+ assert model.locate_version(root, "26-cut").over_ceiling() == 1
9113
+ assert task.cmd_new({"name": "one-more-thing", "epic": "a"}) == 0
9114
+ assert (full / "queue" / "one-more-thing" / "task.md").is_file()
9115
+ assert model.locate_version(root, "26-cut").over_ceiling() == 2
9116
+
9117
+
9118
+ def test_placing_a_whole_epic_into_a_full_cut_is_told_too():
9119
+ # A whole epic arriving is the likeliest way a cut goes past its ceiling in one
9120
+ # move, and it runs through a different command from filing a single task.
9121
+ import io, contextlib
9122
+ with tempfile.TemporaryDirectory() as tmp:
9123
+ _full_cut(_tree(tmp, "26-cut"))
9124
+ later = Path(tmp) / "versions" / "27-next"
9125
+ later.mkdir(parents=True)
9126
+ (later / "version.md").write_text(
9127
+ "---\ncreated: 2026-08-01\norder: 27\noutcome: y\n---\n\n# Next\n")
9128
+ _open_epic(later, "arriving", queued=2)
9129
+ out = io.StringIO()
9130
+ with _work_dir(tmp), contextlib.redirect_stdout(out):
9131
+ assert task.cmd_place({"name": "arriving", "version": "26-cut"}) == 0
9132
+ assert "note: cut 26-cut: 18 open tasks, 3 past its ceiling" in out.getvalue(), \
9133
+ out.getvalue()
9134
+
9135
+
9136
+ def test_a_cut_that_wrote_why_its_size_is_deliberate_is_not_flagged():
9137
+ # Planning a big release in advance stays legal. What the flag exists for is
9138
+ # accretion, and the difference is recorded rather than guessed.
9139
+ with tempfile.TemporaryDirectory() as tmp:
9140
+ root = Path(tmp)
9141
+ v = _tree(tmp, "26-cut")
9142
+ md = v / "version.md"
9143
+ md.write_text(md.read_text().replace(
9144
+ "---\n\n# Cut", 'one_goal: "planned as one release on purpose"\n---\n\n# Cut'))
9145
+ _full_cut(v)
9146
+ assert model.locate_version(root, "26-cut").over_ceiling() == 0
9147
+ assert not _ceiling_warnings(root, "past its ceiling")
9148
+
9149
+
9150
+ def test_a_released_cut_owes_no_reshape():
9151
+ with tempfile.TemporaryDirectory() as tmp:
9152
+ _full_cut(_tree(tmp, "26-cut", released="2026-09-01"))
9153
+ assert model.locate_version(Path(tmp), "26-cut").over_ceiling() == 0
9154
+
9155
+
9156
+ def test_list_and_the_readme_show_a_full_cut_beside_its_status():
9157
+ # Being full is not a status — a cut can be planned or current and still owe a
9158
+ # reshape — so it is printed beside the badge rather than replacing it.
9159
+ import io, contextlib
9160
+ with tempfile.TemporaryDirectory() as tmp:
9161
+ _full_cut(_tree(tmp, "26-cut"))
9162
+ out = io.StringIO()
9163
+ with _work_dir(tmp) as root:
9164
+ with contextlib.redirect_stdout(out):
9165
+ report.cmd_list({})
9166
+ generate._sync(root)
9167
+ assert "over its ceiling by 1" in out.getvalue(), out.getvalue()
9168
+ assert "Over its ceiling by 1" in (root / "README.md").read_text()
9169
+
9170
+
9171
+ def test_a_repo_sets_its_own_ceilings_and_every_reader_honours_them():
9172
+ # The harness serves repos of very different sizes, so the numbers are config.
9173
+ # Read THROUGH THE MODULE: a `from .tree import` would capture the shipped default
9174
+ # and quietly ignore the repo's own, which is the drift the harness keeps finding.
9175
+ with tempfile.TemporaryDirectory() as tmp:
9176
+ root = Path(tmp)
9177
+ _full_cut(_tree(tmp, "26-cut"))
9178
+ assert model.locate_version(root, "26-cut").over_ceiling() == 1
9179
+ cfg = copy.deepcopy(config.DEFAULTS)
9180
+ cfg["ceilings"] = {"epic": 5, "cut": 20}
9181
+ config.apply(cfg)
9182
+ try:
9183
+ assert model.locate_version(root, "26-cut").over_ceiling() == 0
9184
+ assert len(_ceiling_warnings(root, "open task(s)")) == 2, "8 open is past a ceiling of 5"
9185
+ finally:
9186
+ config.apply(config.DEFAULTS)
9187
+
9188
+
9189
+ def test_a_ceiling_must_be_a_positive_whole_number():
9190
+ # A ceiling of 0 flags every cut with any work in it, and a string never compares
9191
+ # — so it is refused at load, naming the key, rather than discovered later as a
9192
+ # board that warns about everything or about nothing.
9193
+ for key in ("epic", "cut"):
9194
+ for bad in (0, -1, "15", 1.5, True):
9195
+ with tempfile.TemporaryDirectory() as tmp:
9196
+ repo = _repo_with_config(tmp, {"ceilings": {key: bad}})
9197
+ try:
9198
+ config.load(repo)
9199
+ assert False, f"accepted ceilings.{key} = {bad!r}"
9200
+ except config.ConfigError as e:
9201
+ assert f"ceilings.{key}" in str(e) and f"every {key} " in str(e), e
9202
+
9008
9203
  if __name__ == "__main__":
9009
9204
  tests = [v for k, v in sorted(globals().items())
9010
9205
  if k.startswith("test_") and callable(v)]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appchy/jarvis",
3
- "version": "0.1.132",
3
+ "version": "0.1.133",
4
4
  "description": "Jarvis — local AI coding assistant CLI",
5
5
  "private": false,
6
6
  "type": "module",