@delorenj/pjangler 1.3.7 → 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 +20 -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 +14320 -7847
- package/dist/mcp-server.js +13482 -8146
- package/package.json +4 -3
- package/templates/commonproject/copier.yml +14 -5
- package/templates/commonproject/template/.mise/scripts/link-agentfiles.sh +38 -5
- package/templates/commonproject/template/.mise/scripts/provision-packs.py +60 -2
- package/templates/commonproject/template/.mise/scripts/sync-skills.py +479 -24
- package/templates/commonproject/template/mise.toml.jinja +12 -6
- package/templates/hermes-agent/template/.gitignore.jinja +1 -0
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +11 -7
- package/templates/hermes-agent/template/.scripts/_lib.sh +54 -10
- package/templates/hermes-agent/template/.scripts/lib/parse-fleet-env.py +29 -2
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +18 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delorenj/pjangler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Project subsystem bootstrapper CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "esbuild src/index.ts src/mcp-server.ts src/prompt.ts --bundle --packages=external --platform=node --format=esm --outdir=dist",
|
|
26
|
+
"build": "esbuild src/index.ts src/mcp-server.ts src/prompt.ts --bundle --packages=external --platform=node --format=esm --outdir=dist && node scripts/export-project-notebook-skill.mjs",
|
|
27
27
|
"check:audit:prod": "npm audit --omit=dev",
|
|
28
28
|
"check:lock": "node scripts/check-package-lock-parity.mjs",
|
|
29
29
|
"check:submodules": "node scripts/check-submodule-contract.mjs",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"mcp": "node dist/mcp-server.js",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"test:bmad-installer-contract": "node tests/bmad-installer-contract-regressions.mjs",
|
|
35
|
-
"test": "
|
|
35
|
+
"test:pjan-77": "node tests/pjan-77-notebook-domain-regressions.mjs && node tests/pjan-77-notebook-adapter-contract.mjs && node tests/pjan-77-notebook-lifecycle-regressions.mjs && node tests/pjan-77-notebook-cli-contract.mjs && node tests/pjan-77-notebook-hooks-capture.mjs && node tests/pjan-77-notebook-security-isolation.mjs && node tests/pjan-77-notebook-release-gates.mjs",
|
|
36
|
+
"test": "npm run check:lock && node tests/package-lock-parity-regressions.mjs && npm run check:submodules && npm run check:tracked-secrets && node tests/portable-test-paths-regressions.mjs && node tests/release-regressions.mjs && node tests/submodule-contract-regressions.mjs && node tests/secret-publication-gate-regressions.mjs && node tests/bmad-version-surface-regressions.mjs && node tests/bmad-transaction-regressions.mjs && node tests/parity-migrate-regressions.mjs && node tests/hermes-profile-inheritance-regressions.mjs && node tests/pjan-57-lifecycle-recipes-regressions.mjs && node tests/pjan-57-dogfood-regressions.mjs && node tests/generated-project-lifecycle-regressions.mjs && node tests/pack-flatten-regressions.mjs && node tests/pack-flatten-cross-engine-regressions.mjs && node tests/registry-cache-parity-regressions.mjs && node tests/registry-root-ladder-regressions.mjs && node tests/pjan-23-regressions.mjs && node tests/pjan-24-regressions.mjs && node tests/pjan-28-regressions.mjs && node tests/pjan-30-regressions.mjs && node tests/pjan-31a-regressions.mjs && node tests/pjan-36-regressions.mjs && node tests/pjan-43-regressions.mjs && node tests/pjan-48-regressions.mjs && node tests/pjan-49-regressions.mjs && node tests/pjan-50-regressions.mjs && node tests/pjan-65-regressions.mjs && node tests/pjan-67-lifecycle-preflight-regressions.mjs && node tests/pjan-67-regressions.mjs && node tests/pjan-67-trusted-lifecycle-regressions.mjs && node tests/pjan-71-regressions.mjs && node tests/pjan-72-regressions.mjs && node tests/pjan-75-regressions.mjs && node tests/pjan-76-regressions.mjs && node tests/skillex-init-regressions.mjs && node tests/pjan-84-global-scope-regressions.mjs && node tests/pjan-84-registry-flag-regressions.mjs && node tests/pjan-84-finding-scope-regressions.mjs && node tests/pjan-84-orphan-adoption-regressions.mjs && node tests/fleet-shared-bloodbank-regressions.mjs && node tests/mcp-catalog-regressions.mjs && node tests/mcp-server-regressions.mjs && node tests/project-registry-regressions.mjs && node tests/pg-registry-regressions.mjs && node tests/momo-lifecycle-plane-regressions.mjs && npm run test:pjan-77",
|
|
36
37
|
"migrate:up": "node-pg-migrate --migrations-dir migrations up",
|
|
37
38
|
"migrate:down": "node-pg-migrate --migrations-dir migrations down",
|
|
38
39
|
"migrate:create": "node-pg-migrate --migrations-dir migrations create",
|
|
@@ -114,18 +114,27 @@ _exclude:
|
|
|
114
114
|
# into the caller's shared per-user CLI configs. Scaffolding must never mutate
|
|
115
115
|
# global state as a side effect — the operator trusts/enters the repo themselves.
|
|
116
116
|
_tasks:
|
|
117
|
-
-
|
|
117
|
+
# PJAN-82: seed the base ignore only when there is nothing to lose. `cp` over
|
|
118
|
+
# an existing .gitignore silently discarded whatever the repo already had,
|
|
119
|
+
# every time the template was re-rendered into a live project.
|
|
120
|
+
- "[ -s .gitignore ] || cp ~/.config/git/ignore .gitignore 2>/dev/null || [ -s .gitignore ] || echo '# Add ignores here' > .gitignore"
|
|
118
121
|
# Ensure secrets + per-dev agent-hook/skill overrides are ignored (appended after the base ignore).
|
|
119
122
|
# Each CLI root's hand-owned config is durable project state; the skill
|
|
120
123
|
# projections inside it are not — bmad-method and skills:sync rewrite them
|
|
121
124
|
# on every run, so they are re-ignored after the un-ignore lines (last
|
|
122
125
|
# match wins). _bmad/_config/manifest.yaml pins the version that
|
|
123
126
|
# reproduces them exactly.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
# PJAN-82: appended once (a re-render used to duplicate the whole block), and
|
|
128
|
+
# the skill patterns carry no trailing slash so a SYMLINKED projection is
|
|
129
|
+
# ignored too — .gemini/.copilot/.kimi-code get a symlink, not a directory.
|
|
130
|
+
- "grep -q '^# Generated CLI configurations are durable project state' .gitignore 2>/dev/null || printf '\\n# Secrets + per-dev agent-hook/skill overrides (see .agents/local.example.json)\\n.env\\n.env.*\\n!.env.op\\n.agents/local.json\\n.lastagent\\n**/.claude/settings.local.json\\n\\n# Generated CLI configurations are durable project state...\\n!.claude/\\n!.claude/**\\n!.codex/\\n!.codex/**\\n!.gemini/\\n!.gemini/**\\n!.copilot/\\n!.copilot/**\\n!.opencode/\\n!.opencode/**\\n!.kimi-code/\\n!.kimi-code/**\\n# ...but their skill projections are regenerated, so they stay out of the tree.\\n# No trailing slash: some CLIs get a real directory here and some a symlink.\\n/.agents/skills\\n.claude/skills\\n.codex/skills\\n.gemini/skills\\n.copilot/skills\\n.opencode/skills\\n.kimi-code/skills\\n' >> .gitignore"
|
|
131
|
+
# PJAN-82: use the managed script, which refuses to replace a REAL CLAUDE.md.
|
|
132
|
+
# `ln -sf` cannot distinguish a stale link it owns from a hand-written file it
|
|
133
|
+
# must not touch, so a re-render into a live project destroyed the file and
|
|
134
|
+
# reported success.
|
|
135
|
+
- "./.mise/scripts/link-agentfiles.sh \"$(pwd)\""
|
|
127
136
|
# Materialize the declared Skillex packs' manifest + symlink topology
|
|
128
137
|
# without copying pack content or disturbing project-specific skills.
|
|
129
|
-
- "python3 .mise/scripts/provision-packs.py"
|
|
138
|
+
- "python3 .mise/scripts/provision-packs.py --root \"$(pwd)\""
|
|
130
139
|
# Stamp the repo's absolute path into .project.json (the SOT) post-render.
|
|
131
140
|
- "python3 -c \"import json,os,pathlib; p=pathlib.Path('.project.json'); d=json.loads(p.read_text()); d['repo_path']=os.getcwd(); p.write_text(json.dumps(d,indent=2)+chr(10))\""
|
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
+
set -eu
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
# Symlink the per-CLI agent instruction files at AGENTS.md.
|
|
5
|
+
#
|
|
6
|
+
# A mise ENTER hook runs with cwd set to the directory the user cd'd into, NOT
|
|
7
|
+
# to config_root — measured on mise 2026.8.10, and true even for a parent config
|
|
8
|
+
# when the entered directory is a nested child. `mise run <task>` does run at
|
|
9
|
+
# config_root, which is why the old cwd-relative version looked correct for
|
|
10
|
+
# years: only the enter-hook path was wrong. So take the root explicitly, and
|
|
11
|
+
# fall back to this script's own location — never to cwd.
|
|
12
|
+
root="${1:-${MISE_CONFIG_ROOT:-}}"
|
|
13
|
+
own_root=$(CDPATH= cd "$(dirname "$0")/../.." && pwd -P)
|
|
14
|
+
if [ -z "$root" ]; then
|
|
15
|
+
root="$own_root"
|
|
7
16
|
else
|
|
8
|
-
|
|
17
|
+
root=$(CDPATH= cd "$root" && pwd -P)
|
|
9
18
|
fi
|
|
19
|
+
if [ "$root" != "$own_root" ]; then
|
|
20
|
+
echo "link-agentfiles: refusing to act on $root; this script belongs to $own_root" >&2
|
|
21
|
+
echo "link-agentfiles: a nested repo must ship its own .mise/scripts copy" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
CDPATH= cd "$root"
|
|
26
|
+
if [ ! -f AGENTS.md ]; then
|
|
27
|
+
echo "No AGENTS.md in $root. Nothing to symlink."
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# `ln -sf` cannot tell a stale link it owns from a real file it must not touch:
|
|
32
|
+
# -f means "unlink whatever is there". A hand-written CLAUDE.md was silently
|
|
33
|
+
# replaced by a symlink, and the script printed a green checkmark while doing it.
|
|
34
|
+
for name in CLAUDE.md GEMINI.md; do
|
|
35
|
+
if [ -e "$name" ] && [ ! -L "$name" ]; then
|
|
36
|
+
echo "link-agentfiles: refusing to replace the real file $root/$name with a symlink" >&2
|
|
37
|
+
echo "link-agentfiles: move its content into AGENTS.md, then delete $name" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
ln -sfn AGENTS.md "$name"
|
|
41
|
+
done
|
|
42
|
+
echo "✅ AGENTS.md links verified in $root"
|
|
@@ -29,6 +29,7 @@ from __future__ import annotations
|
|
|
29
29
|
|
|
30
30
|
import importlib.util
|
|
31
31
|
import json
|
|
32
|
+
import argparse
|
|
32
33
|
import os
|
|
33
34
|
import re
|
|
34
35
|
import shutil
|
|
@@ -286,13 +287,46 @@ def atomic_write(path: Path, content: bytes, mode: int) -> None:
|
|
|
286
287
|
# --------------------------------------------------------------------------- #
|
|
287
288
|
|
|
288
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
|
+
|
|
289
322
|
def provision(
|
|
290
323
|
*,
|
|
324
|
+
root: str | None = None,
|
|
291
325
|
after_preflight: Callable[[], None] | None = None,
|
|
292
326
|
create_link: Callable[[Path, Path, int], None] | None = None,
|
|
293
327
|
after_apply: Callable[[Path, Path], None] | None = None,
|
|
294
328
|
) -> int:
|
|
295
|
-
project_root =
|
|
329
|
+
project_root = resolved_project_root(root)
|
|
296
330
|
agents_path = project_root / ".agents"
|
|
297
331
|
skills_path = agents_path / "skills"
|
|
298
332
|
agents_existed = agents_path.exists() or agents_path.is_symlink()
|
|
@@ -387,10 +421,24 @@ def provision(
|
|
|
387
421
|
)
|
|
388
422
|
except OSError:
|
|
389
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()
|
|
390
437
|
if (
|
|
391
438
|
entry.name not in expected
|
|
392
439
|
and entry.name not in managed_manifest_names
|
|
393
440
|
and not link_targets_pack
|
|
441
|
+
and not dangling_link
|
|
394
442
|
):
|
|
395
443
|
continue
|
|
396
444
|
target = expected.get(entry.name)
|
|
@@ -514,8 +562,18 @@ def provision(
|
|
|
514
562
|
|
|
515
563
|
|
|
516
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()
|
|
517
575
|
try:
|
|
518
|
-
changed = provision()
|
|
576
|
+
changed = provision(root=args.root)
|
|
519
577
|
except (FileNotFoundError, OSError, ValueError, RuntimeError, engine.PackUnavailable) as error:
|
|
520
578
|
raise SystemExit(
|
|
521
579
|
f"Skillex pack provisioning failed: {error}; "
|