@appchy/jarvis 0.1.90 → 0.1.91
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/assets/epic-template.md +8 -0
- package/harness/harness/lint.py +10 -2
- package/harness/harness/model.py +7 -0
- package/harness/test_work.py +29 -2
- package/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -10145,7 +10145,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
10145
10145
|
var _require = createRequire2(import.meta.url);
|
|
10146
10146
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
10147
10147
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
10148
|
-
var SHA = "
|
|
10148
|
+
var SHA = "eaa6a77";
|
|
10149
10149
|
var BUILT = "2026-09-11";
|
|
10150
10150
|
var BUILD = SHA ?? "source";
|
|
10151
10151
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
@@ -27,6 +27,14 @@ continues: {{CONTINUES}}
|
|
|
27
27
|
epic with no `covers:` is a lint: work that moves no stated criterion is
|
|
28
28
|
work nobody can tell is finished.
|
|
29
29
|
|
|
30
|
+
`one_goal:` is the epic's answer to the THIN-epic check, and it is a
|
|
31
|
+
sentence rather than a flag. An epic under the task floor is usually a topic
|
|
32
|
+
wearing an epic costume — but not always, and where the goal genuinely needs
|
|
33
|
+
only these slices, saying so in writing is the exemption. An empty one
|
|
34
|
+
exempts nothing, the same way `owner:` does: a field that can be silenced by
|
|
35
|
+
writing anything in it is not a check. The task tier carries the same field
|
|
36
|
+
for the same reason, about its WIDTH rather than its size.
|
|
37
|
+
|
|
30
38
|
`owner:` is the ONE exemption from that, and it is not an escape hatch.
|
|
31
39
|
Some epics genuinely move no product criterion — a trailer, a test harness,
|
|
32
40
|
a migration — because nothing a user can do changes. Those answer to an
|
package/harness/harness/lint.py
CHANGED
|
@@ -174,16 +174,24 @@ def _shape_lint(root: Path, s: dict) -> list:
|
|
|
174
174
|
# epic for the same reason rule 3 does: a want nobody has planned has no
|
|
175
175
|
# slices yet, and "fold it into an epic that fits" is advice about work
|
|
176
176
|
# being cut, not about a note somebody parked.
|
|
177
|
+
#
|
|
178
|
+
# The exemption is DECLARED, the same way a task declares its width is
|
|
179
|
+
# deliberate. This message used to end "unless §Plan names the slices not
|
|
180
|
+
# yet cut" — an escape nothing implemented, so the only way to satisfy it
|
|
181
|
+
# was to stop reading the warning. A tool that promises an exemption it
|
|
182
|
+
# does not honour teaches people to ignore every warning beside it, which
|
|
183
|
+
# costs more than the thin epic ever did.
|
|
177
184
|
for e in [e for v in s["versions"] for e in v.epics]:
|
|
178
185
|
if not e.planned or e.done_tier:
|
|
179
186
|
continue
|
|
180
187
|
n = len(e.all_tasks())
|
|
181
|
-
if n < EPIC_TASK_FLOOR:
|
|
188
|
+
if n < EPIC_TASK_FLOOR and not e.one_goal:
|
|
182
189
|
warns.append(
|
|
183
190
|
f"epic {e.name}: {n} task(s) — an epic is the goal you plan "
|
|
184
191
|
f"TOGETHER, and under {EPIC_TASK_FLOOR} there is no together. "
|
|
185
192
|
f"Fold {'it' if n == 1 else 'them'} into an existing epic that "
|
|
186
|
-
f"fits,
|
|
193
|
+
f"fits — or, if this goal genuinely needs only these, say why in "
|
|
194
|
+
f"`one_goal:`")
|
|
187
195
|
|
|
188
196
|
# 3b — a task inside an epic carrying its own plan.md. The design is settled
|
|
189
197
|
# ONCE in `epic.md` §Plan; a second design doc per task is the
|
package/harness/harness/model.py
CHANGED
|
@@ -148,6 +148,13 @@ class Epic:
|
|
|
148
148
|
# the tree disagrees with — the folder's home is the only statement of it.
|
|
149
149
|
self.done_tier = folder.parent.name == DONE_TIER
|
|
150
150
|
self.covers = as_list(self.fm.get("covers"))
|
|
151
|
+
# The same declared exemption a task carries for its WIDTH, at the tier
|
|
152
|
+
# above and for its SIZE. One field name across both rather than a second
|
|
153
|
+
# vocabulary: in each case the claim is that the shape was chosen and the
|
|
154
|
+
# sentence is what makes it reviewable. Empty exempts nothing, exactly as
|
|
155
|
+
# `owner:` and the task's own do — a field that can be silenced by writing
|
|
156
|
+
# anything in it is not a check.
|
|
157
|
+
self.one_goal = str(self.fm.get("one_goal", "")).strip()
|
|
151
158
|
# Same vocabulary as a task's `owner:` — a bare `<feature>` or a bare
|
|
152
159
|
# org-domain. It exists on an epic for one reason: an epic owned by a
|
|
153
160
|
# DOMAIN has no `AC-nn` layer to qualify against, because a domain hosts
|
package/harness/test_work.py
CHANGED
|
@@ -453,12 +453,13 @@ def _task(folder: Path, name: str, body: str = "# T\n", fm: str = "priority: P1\
|
|
|
453
453
|
|
|
454
454
|
|
|
455
455
|
def _epic(folder: Path, name: str, covers: str = "[lesson/AC-01]",
|
|
456
|
-
owner: str = ""):
|
|
456
|
+
owner: str = "", one_goal: str = ""):
|
|
457
457
|
d = folder / name
|
|
458
458
|
d.mkdir(parents=True)
|
|
459
459
|
own = f"owner: {owner}\n" if owner else ""
|
|
460
|
+
goal = f"one_goal: {one_goal}\n" if one_goal else ""
|
|
460
461
|
(d / "epic.md").write_text(
|
|
461
|
-
f"---\ntype: epic\ncreated: 2026-08-01\ncovers: {covers}\n{own}---\n\n# {name}\n")
|
|
462
|
+
f"---\ntype: epic\ncreated: 2026-08-01\ncovers: {covers}\n{own}{goal}---\n\n# {name}\n")
|
|
462
463
|
return d
|
|
463
464
|
|
|
464
465
|
|
|
@@ -857,6 +858,32 @@ def test_an_empty_one_goal_exempts_nothing():
|
|
|
857
858
|
assert any("wide: spans 3 code regions" in x for x in _warns(Path(tmp)))
|
|
858
859
|
|
|
859
860
|
|
|
861
|
+
def test_a_thin_epic_that_says_its_size_is_deliberate_is_not_flagged():
|
|
862
|
+
"""The message used to end "unless §Plan names the slices not yet cut" — an
|
|
863
|
+
escape nothing implemented, so the only way to satisfy it was to stop reading
|
|
864
|
+
the warning. The exemption is a SENTENCE now, the same shape a task uses for
|
|
865
|
+
its width, so one vocabulary covers both tiers."""
|
|
866
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
867
|
+
v = _tree(tmp)
|
|
868
|
+
e = _epic(v, "thin", one_goal="the two slices are the whole of it; there "
|
|
869
|
+
"is no third thing this goal needs")
|
|
870
|
+
_task(e / "queue", "one")
|
|
871
|
+
# Specific to the FLOOR message: the same epic trips other lints in a bare
|
|
872
|
+
# fixture tree, and a substring match on its name would pass or fail on one
|
|
873
|
+
# of those instead of on the thing under test.
|
|
874
|
+
assert not any("epic thin: 1 task(s)" in x for x in _warns(Path(tmp)))
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def test_an_empty_one_goal_exempts_no_thin_epic_either():
|
|
878
|
+
"""Checked the way the task's own is, or the field becomes a way to silence
|
|
879
|
+
the lint by writing nothing at all in it."""
|
|
880
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
881
|
+
v = _tree(tmp)
|
|
882
|
+
e = _epic(v, "thin", one_goal=" ")
|
|
883
|
+
_task(e / "queue", "one")
|
|
884
|
+
assert any("epic thin: 1 task(s)" in x for x in _warns(Path(tmp)))
|
|
885
|
+
|
|
886
|
+
|
|
860
887
|
def test_a_backlog_epic_is_not_asked_what_it_covers():
|
|
861
888
|
"""A captured want has no criteria yet — they are chosen when it is PLANNED,
|
|
862
889
|
and asking first only produces a guess the planning session argues with."""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appchy/jarvis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.91",
|
|
4
4
|
"description": "Jarvis — local AI coding assistant CLI",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"@jarvis/agents": "1.0.0",
|
|
60
60
|
"@jarvis/anthropic": "1.0.0",
|
|
61
61
|
"@jarvis/board": "0.1.0",
|
|
62
|
-
"@jarvis/data": "0.1.0",
|
|
63
62
|
"@jarvis/errors": "1.0.0",
|
|
64
63
|
"@jarvis/logger": "1.0.0",
|
|
65
64
|
"@jarvis/rpc": "1.0.0",
|
|
65
|
+
"@jarvis/types": "1.0.0",
|
|
66
66
|
"@jarvis/typescript-config": "1.0.0",
|
|
67
|
-
"@jarvis/vitest-config": "1.0.0",
|
|
68
67
|
"@jarvis/ui": "0.1.0",
|
|
69
|
-
"@jarvis/
|
|
68
|
+
"@jarvis/vitest-config": "1.0.0",
|
|
69
|
+
"@jarvis/data": "0.1.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"dev": "tsx watch src/bin.ts start --foreground",
|