Skip to content

OH MY PM v0.5.4

Choose a tag to compare

@github-actions github-actions released this 02 Aug 13:05
288337a

OH MY PM v0.5.4

Contract and repository consistency release. It adds shared contracts at the
application boundary and makes the package dependency model explicit and
mechanically enforced.

No public behaviour changes.

Release lineage

Latest published stable, and immutable base v0.5.3
v0.5.0 superseded, unpublished source candidate — no tag or release
v0.5.4 prepared; not yet published

No existing release tag is moved, replaced, recreated, or deleted. Merging the
v0.5.4 pull request publishes nothing.

What the audit found first

This release began with an audit of the actual tree, and the result changed its
shape. Several problems the plan anticipated did not exist:

Property Audit result
Production dependency cycles None. The graph was already acyclic.
Cross-package deep src/ imports None anywhere in the workspace.
@oh-my-pm/contracts purity Already declared zero workspace dependencies.
CLI exit-code consistency Already consistent with the documented policy in cli/src/help.ts.
CLI/MCP shared use cases Already shared for the four project workflows and the GitHub workflows.

So this release repairs none of those. It is important to state that plainly:
the guards below make existing correct structure permanent, they do not fix
violations. Where something genuinely did not exist, it is listed as newly
implemented.

Newly implemented

The authoritative package catalog

packages.json gives every one of the thirteen workspace packages exactly one
role plus an explicit contract: allowed dependencies, forbidden inversions,
responsibilities and non-responsibilities, public entry points, permitted process
side effects, data ownership, release-bundle status, compatibility surface, and
test ownership.

The layer order is derived from the real graph rather than an imported
convention:

contract < capability < composition < orchestration < application < presentation < packaging < development

A package may depend on its own layer or any layer to its left, never to its
right. The names describe this codebase: providers owns both the network
boundary and the ProviderRegistry abstraction that planner is written against,
so a generic domain/infrastructure split does not fit — ranking by actual longest
dependency path does.

Catalog-driven boundary validation

pnpm validate:packages derives every check from the catalog, so a package added
later inherits them instead of needing hand-written pairwise rules:

  • no production dependency cycle (dev-only edges are correctly not treated as
    cycles);
  • dependencies point down the layer order and stay inside the declared allowance;
  • no package reaches inside another's src/, dist/, or test/;
  • no package imports a workspace package it does not declare;
  • no package below presentation writes to stdout/stderr or calls
    process.exit;
  • declared exports match the catalog's entry points;
  • the catalog's inBundle claims match the real release dependency closure;
  • every package has a README.

This extends rather than duplicates tools/validate-boundaries.mjs, which
keeps its specific high-value pairwise rules and its release/workflow policy.

The shared application result contract

interface ApplicationResult<TData> {
  schemaVersion: string;
  operation: string;
  generatedAt: string;
  source: SourceDescriptor;
  data: TData;
  diagnostics: Diagnostic[];
  provenance: ProvenanceRecord[];
}

ApplicationResult<T> is additive. ProjectWorkflowResult and
GitHubWorkflowResult keep their exact shapes and public behaviour; the envelope
gives them a shared identity so a consumer can ask "where did this come from?"
without knowing which use case it called. Nothing is forced into it.

Serialization is deterministic: applicationResultToJson emits canonical key
order and sorts selection and details keys, so byte-identical inputs give
byte-identical output however the object was assembled. generatedAt comes from
the caller's injected clock, keeping results reproducible.

Normalized source descriptors

A closed set of eight source kinds — local project, four GitHub shapes, GitHub
search, Project Memory snapshot, Project Timeline — each carrying identity and
bounded selection metadata only. Never a token, an authorization header, a raw
transport object, a resolved absolute path, or document content.
assertSafeSourceDescriptor is the single shared enforcement.

On paths specifically: a failure message echoes the root the caller supplied,
verbatim. If a caller passes an absolute path, an absolute path comes back — that
is their own string, not a disclosure. The guarantee is that nothing resolves
the root before reporting it.

Provenance contracts

ProvenanceRecord is optional by design — attaching it to every trivial field
would bloat simple outputs. It supports the source, a repository-relative document
path, a line or range, a GitHub item number, a snapshot id, the deriving rule, and
a truncated flag so a bounded read is not mistaken for a complete one.

Unified diagnostics and a repository-wide error taxonomy

One Diagnostic shape with a stable machine-readable code, a three-level
severity, optional remediation and retryability, an optional narrower source, and
JSON-safe details only — never a cause chain or a stack trace.

Eleven error categories, each with a complete behavioural contract: retryability,
CLI exit code, severity, and whether MCP marks the result an error.

Every existing public failure code is classified; none is renamed or removed.
Those code strings appear in CLI JSON and MCP results, so they are public. Two
tests keep the table honest in both directions: every declared code must be
classified, and every classified code must still be declared.

CLI exit-code and MCP error mappings

The exit-code policy is the one already documented in cli/src/help.ts and
already implemented — 0 success, 1 runtime execution failed, 2 invalid
invocation or a controlled precondition failure. This release makes it explicit
and testable; it changes no exit code.

MCP preserves the machine-readable code and sanitized message, and keeps an
expected validation failure a structured result rather than an unstructured
crash, so an agent can read the code and correct its own call.

Direct CLI/MCP semantic parity assertions

tests/e2e/semantic-parity.test.ts compares the two surfaces against each
other
. The pre-existing extraction-parity.test.ts runs each surface against
its own expected shape and re-runs it for determinism — both files could pass
while CLI and MCP quietly diverged, because nothing compared one to the other.

Covered: the four shared local project workflows, the four shared GitHub
workflows, the document set behind both surfaces, failure-code agreement,
exit-code agreement with the taxonomy, sanitized-message agreement, and the
absence of any token or authorization header in either surface's output.

Consolidation, and its deliberate limit

application/src/errors.ts and application/src/provider-diagnostics.ts were the
migration inputs. The outcome differs between them, for a reason worth recording:

  • errors.ts keeps its two sanitization helpers (sanitizedErrorCode,
    looksLikeAbsolutePath). They are used, correct, and orthogonal to the
    taxonomy.
  • provider-diagnostics.ts keeps its report types unchanged, and gains an
    adapter instead. buildProviderStatusReport(...) is returned directly as
    the result of the provider_status and github_provider_diagnostics MCP tools
    and printed by the CLI under --json, so its schemaVersion: 1 and its
    ok | info | warning | fail vocabulary are a client-facing contract.
    Rewriting those types onto Diagnostic would change an MCP tool's output
    schema, which this release must not do.

application/src/diagnostics-adapter.ts projects those reports into the unified
model for consumers that want one vocabulary. The report types are documented in
docs/v0.5/contracts.md as deliberately retained public
shapes, not leftovers.

The intentional CLI asymmetry, documented not "fixed"

@oh-my-pm/cli declares runtime, providers, skills, and kernel while
@oh-my-pm/mcp-server reaches the shared project workflows through
@oh-my-pm/application. This is intentional.

cli/src/local-process.ts composes a local Runtime for status, doctor, and
plan: runtime-identity and free-form planning commands that the application
boundary does not expose and that no second surface consumes. The four shared
project workflows and the GitHub workflows go through the application boundary on
both surfaces.

Extending the boundary to absorb those three commands purely for symmetry was
considered and rejected: it would restructure three packages to tidy a diagram
without a second consumer to justify it. If a future surface ever needs runtime
identity, that is the point at which the boundary should grow — driven by a real
consumer. The asymmetry is recorded in packages.json and
docs/v0.5/contracts.md.

Separately, @oh-my-pm/project-memory is a production dependency of the CLI
so it ships in the bundle, while being reached only through a lazy dynamic import.
cli/test/memory-boundary.test.ts enforces both halves. It is not an unused
dependency, and it was not "cleaned up".

Compatibility policy

docs/v0.5/contracts.md defines the full table.
APPLICATION_RESULT_SCHEMA_VERSION increments only for a breaking change to the
envelope; adding an optional field does not. Renaming or removing a public failure
code, changing an existing exit code, and changing MCP tool order or an input
schema incompatibly are all breaking. A Project Memory store written by an older
release must remain readable, which is why the store format is versioned
separately.

Cleanup performed

Deliberately minimal, and none of it speculative. The audit looked for unused
scripts, dead or duplicate validators, unused dependencies, and stale package
READMEs, and found nothing safe to remove that was not also evidence or
compatibility surface. Per the repository's own decision rule, uncertain cleanups
were kept and the uncertainty documented rather than deleted on assumption.

One additive change: @oh-my-pm/application becomes a root devDependency, because
tests/e2e/semantic-parity.test.ts imports the taxonomy to assert exit-code
agreement.

User-visible behaviour statement

None. No command, flag, output format, exit code, MCP tool, MCP schema, tool
order, annotation, failure code, Project Brain schema, Project Memory store
format, or installed layout changes. Twelve read-only MCP tools in the same order,
zero write tools, seven memory subcommands, schema 1, store format 2.

Migration statement

No migration. Installing v0.5.4 over v0.5.3 requires no action, and a v0.5.3
Project Memory store is read and written identically.

Compatibility statement

  • Canonical commands ohmypm, ohmypm-mcp, ohmypm-install — unchanged.
  • Deprecated aliases — retained, still warn on stderr only, no removal scheduled.
  • MCP tool inventory, order, schemas, annotations — unchanged.
  • Public failure codes and CLI exit codes — unchanged.
  • Generated TypeScript and Rust contracts — unchanged and byte-reproducible.
  • Release archive names, bundle profile, installed layout — unchanged.

Validation evidence

pnpm build                          OK
pnpm quality                        OK (lint, format, rustfmt, clippy)
pnpm test                           OK (unit, release, rust)
pnpm validate                       OK (public, structure, boundaries, packages,
                                        contracts, version, commands, references,
                                        docs, docs inventory)
pnpm mcp:smoke                      OK
release bundle + archives + repro   OK
installed qualification             OK (432/432)

New tests: 21 result-contract tests, 20 package-catalog mutation tests, 15
CLI/MCP semantic parity tests.

Every one of the 20 mutation tests introduces a single violation into a
disposable git fixture and asserts the specific guard fires. That discipline
caught a real defect during development: the relative-import guard resolved
specifiers against the package root instead of the importing file's directory, so
it matched nothing and was vacuously true. A guard that has never been
observed to fail is not evidence of anything.

Known limitations

  • Layer names are repository-specific. capability/composition/
    orchestration describe this graph. A reader expecting a conventional
    domain/infrastructure split must read the rationale in packages.json.
  • ApplicationResult<T> is available but not yet the return type of the
    existing use cases.
    Retrofitting runLocalProjectWorkflow and
    runGitHubProjectWorkflow to return it would change what CLI and MCP receive,
    which is a v0.6 concern. Today it is the contract for new boundary surfaces and
    the shared vocabulary for diagnostics and provenance.
  • Provider report types remain a second result shape. Retained deliberately,
    because they are public MCP output. Unifying them requires an MCP schema change.
  • The side-effect guard is textual. It matches console.*,
    process.stdout/stderr, and process.exit in comment-stripped source; an
    effect reached through an indirect alias would not be caught.
  • concern is declared, not inferred (inherited from v0.5.3): duplicate
    authority is detected only when two documents claim the same string.

Readiness for v0.6.0

What these two releases make ready, stated narrowly:

  • documentation authority is machine-readable and enforced (v0.5.3);
  • every package has one authoritative role and a mechanically enforced dependency
    contract;
  • a shared, deterministic, secret-free result/diagnostic/provenance vocabulary
    exists at the application boundary for a future surface to consume;
  • error semantics are classified once and mapped to both existing surfaces;
  • CLI and MCP are proven to agree semantically on the shared workflows.

No Dashboard is implemented, designed, or made ready beyond the boundary a
third surface would consume. v0.6 remains uncommitted; ohmypm is still the
canonical command and the omp migration has not begun.