@delorenj/pjangler 1.3.0 → 1.4.2
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/.mise/scripts/link-agentfiles.sh +38 -5
- package/README.md +92 -0
- package/dist/assets/project-notebook-skill/SHA256SUMS +10 -0
- package/dist/assets/project-notebook-skill/SKILL.md +64 -0
- package/dist/assets/project-notebook-skill/agents/openai.yaml +6 -0
- package/dist/assets/project-notebook-skill/export-manifest.json +56 -0
- package/dist/assets/project-notebook-skill/hooks/claude.settings.json +26 -0
- package/dist/assets/project-notebook-skill/hooks/hooks.master.json +26 -0
- package/dist/assets/project-notebook-skill/hooks/session-end.sh +228 -0
- package/dist/assets/project-notebook-skill/hooks/session-start.sh +228 -0
- package/dist/assets/project-notebook-skill/references/configuration.md +93 -0
- package/dist/assets/project-notebook-skill/references/recovery.md +54 -0
- package/dist/assets/project-notebook-skill/scripts/project-hooks.py +865 -0
- package/dist/assets/project-notebook-skill/tests/test_project_hooks.py +848 -0
- package/dist/index.js +11307 -2809
- package/dist/mcp-server.js +9637 -2094
- package/dist/prompt.js +404 -0
- package/package.json +8 -5
- package/templates/commonproject/copier.yml +19 -5
- package/templates/commonproject/template/.mise/scripts/link-agentfiles.sh +38 -5
- package/templates/commonproject/template/.mise/scripts/provision-packs.py +74 -52
- package/templates/commonproject/template/.mise/scripts/sync-skills.py +479 -24
- package/templates/commonproject/template/mise.toml.jinja +12 -6
- package/templates/hermes-agent/copier.yml +8 -11
- package/templates/hermes-agent/template/.gitignore.jinja +1 -0
- package/templates/hermes-agent/template/.runtime-scaffold/.gitignore.jinja +44 -0
- package/templates/hermes-agent/template/.scripts/01-config.sh +9 -0
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +18 -28
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +68 -4
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +18 -1
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +77 -43
- package/templates/hermes-agent/template/.scripts/80-registry.sh +6 -0
- package/templates/hermes-agent/template/.scripts/_lib.sh +116 -16
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +29 -1
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +13 -1
- package/templates/hermes-agent/template/.scripts/lib/fleet-env.sh +202 -0
- package/templates/hermes-agent/template/.scripts/lib/parse-fleet-env.py +761 -0
- package/templates/hermes-agent/template/.scripts/lifecycle.sh +126 -0
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +19 -3
- package/templates/hermes-agent/template/SOUL.md.jinja +44 -8
- package/templates/hermes-agent/template/hermes.jinja +20 -8
- package/templates/hermes-agent/template/momo.jinja +177 -0
- package/templates/hermes-agent/template/role.yaml.jinja +19 -19
|
@@ -19,15 +19,17 @@ What this script owns is the transactional projection into the project:
|
|
|
19
19
|
one unsafe or tampered pack produces ZERO mutation
|
|
20
20
|
* `.agents/skills.json` is rewritten atomically, preserving its mode
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
Only packs a project DECLARES in `packs[]` are provisioned. Nothing is pinned
|
|
23
|
+
implicitly -- in particular BMAD is not a pack: `bmad-method install` writes
|
|
24
|
+
bmad-* skills into `.agents/skills` itself, versioned per project by
|
|
25
|
+
`_bmad/_config/manifest.yaml`.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
28
|
from __future__ import annotations
|
|
28
29
|
|
|
29
30
|
import importlib.util
|
|
30
31
|
import json
|
|
32
|
+
import argparse
|
|
31
33
|
import os
|
|
32
34
|
import re
|
|
33
35
|
import shutil
|
|
@@ -40,11 +42,12 @@ from typing import Callable
|
|
|
40
42
|
SKILLS_SCHEMA = "https://raw.githubusercontent.com/delorenj/skillex/main/skills.schema.json"
|
|
41
43
|
SKILLS_REGISTRY = "https://github.com/delorenj/skillex.git"
|
|
42
44
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
# BMAD is NOT a pack. `bmad-method install` writes bmad-* skills into
|
|
46
|
+
# .agents/skills itself, per project, versioned by _bmad/_config/manifest.yaml.
|
|
47
|
+
# This script used to pin a frozen `packs/bmad/<version>` in the registry and
|
|
48
|
+
# project a second copy of the same skills; when the registry dropped that pack
|
|
49
|
+
# the pin took every `pjangler project create` down with it on any machine
|
|
50
|
+
# without a warm cache. Only packs a project DECLARES are provisioned now.
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
def load_engine():
|
|
@@ -68,38 +71,13 @@ engine = load_engine()
|
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
def pack_root_override(name: str) -> Path | None:
|
|
71
|
-
"""Developer/test pin for one pack root, e.g. `PJ_PACK_ROOT_HERMES_BASE`.
|
|
72
|
-
|
|
73
|
-
`PJ_BMAD_PACK_ROOT` predates the generic form and stays a first-class alias
|
|
74
|
-
for the `bmad` pack.
|
|
75
|
-
"""
|
|
74
|
+
"""Developer/test pin for one pack root, e.g. `PJ_PACK_ROOT_HERMES_BASE`."""
|
|
76
75
|
generic = os.environ.get(f"PJ_PACK_ROOT_{re.sub(r'[^A-Z0-9]', '_', name.upper())}", "").strip()
|
|
77
76
|
if generic:
|
|
78
77
|
return Path(generic).expanduser().absolute()
|
|
79
|
-
if name == BMAD_PACK_NAME:
|
|
80
|
-
legacy = os.environ.get("PJ_BMAD_PACK_ROOT", "").strip()
|
|
81
|
-
if legacy:
|
|
82
|
-
return Path(legacy).expanduser().absolute()
|
|
83
78
|
return None
|
|
84
79
|
|
|
85
80
|
|
|
86
|
-
def implicit_bmad_entry() -> dict:
|
|
87
|
-
"""The implicit BMAD pin, expressed as an ordinary `packs[]` entry.
|
|
88
|
-
|
|
89
|
-
It deliberately carries NO `source`: like every declared pack it must walk
|
|
90
|
-
the one resolution ladder in `resolve_pack_root()` -- env override first,
|
|
91
|
-
then the contract-ordered registry checkouts. Pinning a hardcoded root here
|
|
92
|
-
would give the same pack name two resolutions in one process, so adding
|
|
93
|
-
`packs:[{"name":"bmad",...}]` to a manifest would silently MOVE the pack.
|
|
94
|
-
"""
|
|
95
|
-
return {
|
|
96
|
-
"name": BMAD_PACK_NAME,
|
|
97
|
-
"version": BMAD_PACK_VERSION,
|
|
98
|
-
"sealed": True,
|
|
99
|
-
"optional": False,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
81
|
def apply_root_override(entry: dict) -> dict:
|
|
104
82
|
"""Pin a declared pack at an overridden root without changing its identity."""
|
|
105
83
|
if entry.get("source") or entry.get("registry_path"):
|
|
@@ -150,24 +128,11 @@ def resolve_declared_packs(manifest: dict, base_dir: Path):
|
|
|
150
128
|
# Later packs override earlier ones (contract section 5).
|
|
151
129
|
declared_members[name] = path
|
|
152
130
|
|
|
131
|
+
# Nothing is pinned implicitly any more. The empty pair is kept so every
|
|
132
|
+
# caller keeps one shape and the manifest writer still evicts leftovers
|
|
133
|
+
# from when something WAS pinned here.
|
|
153
134
|
implicit_members: dict[str, Path] = {}
|
|
154
135
|
implicit_packs: list[dict] = []
|
|
155
|
-
if not any(entry["name"] == BMAD_PACK_NAME for entry in entries):
|
|
156
|
-
# Same call shape as a declared pack above, so the pin cannot resolve
|
|
157
|
-
# anywhere a declared `bmad` entry would not.
|
|
158
|
-
pinned = engine.normalize_pack_entry(apply_root_override(implicit_bmad_entry()))
|
|
159
|
-
pinned["sealed"] = True
|
|
160
|
-
for name, path in engine.resolve_pack(
|
|
161
|
-
pinned,
|
|
162
|
-
cache_dir,
|
|
163
|
-
base_dir,
|
|
164
|
-
default_registry,
|
|
165
|
-
registry_roots,
|
|
166
|
-
managed_roots,
|
|
167
|
-
on_resolved=implicit_packs.append,
|
|
168
|
-
):
|
|
169
|
-
implicit_members[name] = path
|
|
170
|
-
|
|
171
136
|
return declared_members, implicit_members, declared_packs, implicit_packs
|
|
172
137
|
|
|
173
138
|
|
|
@@ -322,13 +287,46 @@ def atomic_write(path: Path, content: bytes, mode: int) -> None:
|
|
|
322
287
|
# --------------------------------------------------------------------------- #
|
|
323
288
|
|
|
324
289
|
|
|
290
|
+
def own_root() -> Path:
|
|
291
|
+
"""The repo this script file belongs to: <root>/.mise/scripts/<this>."""
|
|
292
|
+
return Path(__file__).resolve().parents[2]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def resolved_project_root(requested: str | None) -> Path:
|
|
296
|
+
"""Resolve the repo to provision, refusing to infer it from cwd.
|
|
297
|
+
|
|
298
|
+
A mise ENTER hook runs with cwd set to the directory the user cd'd into, not
|
|
299
|
+
config_root -- including a PARENT config's hook. That is how 33GOD's copy of
|
|
300
|
+
this script came to force-rewrite `pjangler/.agents/skills.json` and plant 75
|
|
301
|
+
dangling pack links in `pjangler/.agents/skills`. `mise run <task>` does run
|
|
302
|
+
at config_root, so the cwd assumption was only ever wrong on the hook path.
|
|
303
|
+
"""
|
|
304
|
+
mine = own_root()
|
|
305
|
+
if requested is None:
|
|
306
|
+
requested = os.environ.get("MISE_CONFIG_ROOT") or None
|
|
307
|
+
if requested is None:
|
|
308
|
+
raise SystemExit(
|
|
309
|
+
"provision-packs: --root (or $MISE_CONFIG_ROOT) is required; "
|
|
310
|
+
"refusing to infer the subject repo from cwd"
|
|
311
|
+
)
|
|
312
|
+
resolved = Path(requested).resolve(strict=True)
|
|
313
|
+
if resolved != mine:
|
|
314
|
+
raise SystemExit(
|
|
315
|
+
f"provision-packs: refusing to act on {resolved}; this script "
|
|
316
|
+
f"belongs to {mine}. A nested repo must ship its own "
|
|
317
|
+
".mise/scripts copy."
|
|
318
|
+
)
|
|
319
|
+
return resolved
|
|
320
|
+
|
|
321
|
+
|
|
325
322
|
def provision(
|
|
326
323
|
*,
|
|
324
|
+
root: str | None = None,
|
|
327
325
|
after_preflight: Callable[[], None] | None = None,
|
|
328
326
|
create_link: Callable[[Path, Path, int], None] | None = None,
|
|
329
327
|
after_apply: Callable[[Path, Path], None] | None = None,
|
|
330
328
|
) -> int:
|
|
331
|
-
project_root =
|
|
329
|
+
project_root = resolved_project_root(root)
|
|
332
330
|
agents_path = project_root / ".agents"
|
|
333
331
|
skills_path = agents_path / "skills"
|
|
334
332
|
agents_existed = agents_path.exists() or agents_path.is_symlink()
|
|
@@ -423,10 +421,24 @@ def provision(
|
|
|
423
421
|
)
|
|
424
422
|
except OSError:
|
|
425
423
|
link_targets_pack = False
|
|
424
|
+
# PJAN-82: a DANGLING symlink here is stale by definition.
|
|
425
|
+
#
|
|
426
|
+
# An entry stopped being recognizable as pack-managed the moment its pack
|
|
427
|
+
# declaration went away: `ownership_roots` no longer contains the pack, so
|
|
428
|
+
# `link_targets_pack` is false, and the normalized manifest no longer names
|
|
429
|
+
# it either — so the checks below skipped it and it stayed forever. That is
|
|
430
|
+
# how momo, bloodbank and candystore each ended up with 76 links into
|
|
431
|
+
# `~/.agents/.cache/registries/.../packs/bmad/6.10.1-next.31/`, a cache
|
|
432
|
+
# directory that no longer exists. A link that resolves to nothing cannot be
|
|
433
|
+
# a hand-authored skill and cannot be serving anyone, so it is reclaimable
|
|
434
|
+
# regardless of where it points. A link that still RESOLVES is left alone
|
|
435
|
+
# unless it is provably pack-managed.
|
|
436
|
+
dangling_link = link_target is not None and not entry.exists()
|
|
426
437
|
if (
|
|
427
438
|
entry.name not in expected
|
|
428
439
|
and entry.name not in managed_manifest_names
|
|
429
440
|
and not link_targets_pack
|
|
441
|
+
and not dangling_link
|
|
430
442
|
):
|
|
431
443
|
continue
|
|
432
444
|
target = expected.get(entry.name)
|
|
@@ -550,8 +562,18 @@ def provision(
|
|
|
550
562
|
|
|
551
563
|
|
|
552
564
|
def main() -> None:
|
|
565
|
+
parser = argparse.ArgumentParser(description="Provision declared Skillex packs.")
|
|
566
|
+
parser.add_argument(
|
|
567
|
+
"--root",
|
|
568
|
+
default=None,
|
|
569
|
+
help=(
|
|
570
|
+
"Project root to provision; defaults to $MISE_CONFIG_ROOT. Never "
|
|
571
|
+
"cwd -- see resolved_project_root()."
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
args = parser.parse_args()
|
|
553
575
|
try:
|
|
554
|
-
changed = provision()
|
|
576
|
+
changed = provision(root=args.root)
|
|
555
577
|
except (FileNotFoundError, OSError, ValueError, RuntimeError, engine.PackUnavailable) as error:
|
|
556
578
|
raise SystemExit(
|
|
557
579
|
f"Skillex pack provisioning failed: {error}; "
|