@delorenj/pjangler
v1.5.18
Published
Project subsystem bootstrapper CLI
Maintainers
Readme
pjangler
Project definitions now live in repository .project.json files with one case-insensitive project_id. Use pj info, pj list, and pj doctor; the singleton PostgreSQL registry indexes those manifests. See project registry for installation, migration, and compatibility details.
Project subsystem bootstrapper CLI + MCP server.
Install
Requires Node 24 or newer. Project skills use @delorenj/skillex 0.1.2; see
the integration contract.
npm install
npm run buildCLI usage
npm run build
node dist/index.js --help
# or if installed globally
pjangler --helpRepository ignore contract
CommonProject preserves an existing .gitignore and adds only portable
project rules for secrets and local .agents projections. It never copies the
operator's core.excludesFile. .agents/ is the sole canonical agent-config
tree; .claude/, .codex/, and the other supported client roots are generated
local projections covered by machine-wide ignore policy. bmad.cli-roots
removes only exact obsolete lines previously generated by pjangler and never
changes Git's index. Use the gitignore-maintenance skill to audit effective
rule provenance and reconcile already-tracked ignored paths.
Project notebook
pjangler notebook manages one companion Open Notebook for a registered
repository. The binding and stable Overview note IDs live in .project.json
and the project registry; endpoint, authentication mode, defaults, and finite
capture limits live in the registry's global notebook configuration.
pjangler notebook status . --json
pjangler notebook create . --live --json
pjangler notebook list notes . --json
pjangler notebook search notes "release evidence" . --json
pjangler notebook audit . --json
pjangler notebook migrate . --live --apply --jsonTrue SessionStart and SessionEnd hooks are projected separately from the
project-scoped hook masters. Their per-repository policy stays fail-open and
disabled until explicitly enabled in .project.json.
Parity audit
BMAD recipes support an optional owner-provided g33 companion for registered
33GOD projects, with explicit opt-in/out, no-write previews, and structured
optional outcomes. See g33 companion for the callable
configuration and the actual pj recipe run bmad --dry-run --json commands.
pjangler audit runs the deterministic 33god parity rules against a repository
and exits nonzero when any of them fails. pjangler migrate applies the
idempotent fix the owning rule declares.
pjangler audit # every rule, human report
pjangler audit --json # machine-parseable
pjangler audit --rules mise.config-root,sot.project-json
pjangler migrate sot.project-json --dry-run
pjangler migrate --all--rules reports only the rules you name. An unknown id is an error, never an
empty pass — a caller probing for one rule learns that the rule is gone
instead of reading a clean report over nothing.
pjangler audit --json and the pjangler_list_parity_rules MCP tool report the
live catalog. The rules are owned by the recipes in src/recipes/, not by a
central table, so that catalog is the only authority on which ones exist.
Obsidian plugins
pjangler init <name> --obsidian-plugin does everything a normal init does
(registry, .project.json, board, BMAD, skills, hooks, mise, the init commit).
The difference is the CommonProject render: it is the variant built on the
canonical obsidian-sample-plugin.
That means manifest.json, versions.json, npm + esbuild + TypeScript in src/,
eslint-plugin-obsidianmd, and build/release workflows on the self-hosted runners.
pjangler init tag-wrangler --obsidian-plugin --description "Wrangle tags across a vault"
cd tag-wrangler && npm install && npm run dev
mise run plugin:link ~/Vaults/Main # symlink into <vault>/.obsidian/plugins/<id>- The type is recorded as
template.commonproject.project_typeand in.copier-answers.yml. It is fixed at creation, and re-runninginitkeeps it. - On an existing repo, the flag only records the type, and only when the repo
already has an Obsidian
manifest.json. mise run version:bumpkeepspackage.json,manifest.jsonandversions.jsonin step. It never tags. Release withnpm version patch|minor|major, which commits and then tags a bareX.Y.Z(Obsidian matches that), and push the tag.- MCP takes the same choice as
projectType: "obsidian-plugin"onpjangler_project_initandpjangler_bootstrap_33god_project.
Agents
pjangler does not hire, provision, or inspect agents. That is Flume's surface —
flume hire, flume roster, flume record, flume review — and the org chart
is Flume's record.
pjangler describe still reports a repository's employees, and pjangler init
carries existing .project.json agents entries forward. Neither writes a new
one: that block is a one-way projection of the org chart, not a place a project
declares intent.
Orienting in a repo
describe reads a repo and reports what it actually is — detected type,
installed subsystems, config files present, and what to do next. It is the
first call for an agent (or a human) landing somewhere unfamiliar, and it works
in any repo, 33GOD or not.
pjangler describe # current directory
pjangler describe ../some-repo
pjangler describe --json # machine-parseable, for agent context
pjangler describe --interactive # tick off fixable findings, press A to applySubsystem presence and subsystem correctness are reported separately: presence
comes from marker files on disk, parity from the recipe's own audit rules. A
subsystem that was never installed reads absent, not broken.
Last activity, not lifecycle status
Projects report when work last happened, not a lifecycle word. The old
status field said planned for 22 of 27 registered projects — including ones
with commits the same day — so it never varied and never informed.
Activity is computed from git at read time, so it cannot go stale and no cron has to walk the registry. It spans more than the checked-out branch:
| source | covers | | --- | --- | | refs | every local branch, remote-tracking branch, and tag | | worktrees | every linked worktree's HEAD, including detached ones whose commits are on no ref | | uncommitted | working-tree edits that are not committed yet |
The newest of those wins, and the winning source is always reported. Remote state reflects your last fetch — nothing here touches the network.
pj list orders by that signal, newest work first.
Shell prompt
pjangler-prompt prints one compact line when the working directory is inside a
pjangler project, and nothing at all when it is not:
pjangler (PJAN) · 3mIt is a separate binary from the main CLI on purpose — the CLI loads the whole parity rule set and takes ~52ms to boot, while this runs in ~30ms, well inside starship's 500ms budget.
Add it to ~/.config/starship.toml as a second, project-scoped prompt line:
format = "…$all${custom.pjangler}$line_break$character"
[custom.pjangler]
description = "pjangler project context"
when = true
shell = ["sh"]
symbol = "◆"
style = "bold purple"
# The newline lives INSIDE the conditional group, so nothing is emitted —
# not even a blank line — outside a pjangler project.
format = "(\n[$symbol $output]($style))"
command = 'exec pjangler-prompt 2>/dev/null'$line_break must be named explicitly: it is otherwise absorbed by $all and
the extra line lands in the wrong place.
MCP server usage
Run over stdio:
npm run mcp
# or
pjangler-mcpExposed tools:
pjangler_list_capabilitiespjangler_list_parity_rulespjangler_audit_projectpjangler_migrate_projectpjangler_bootstrap_33god_projectpjangler_project_initpjangler_project_listpjangler_project_showpjangler_infopjangler_describe_projectpjangler_describe_recipepjangler_run_recipe
