Skip to content

Releases: ai-plugin-marketplace/tools

@ai-plugin-marketplace/core@0.10.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 18:28
a63e5b1

Minor Changes

  • #99 7efeba9 Thanks @mike-north! - Make aipm add-target preserve-or-warn instead of refusing an already-materialized target and
    generating a schema-invalid skeleton on retry.

    Previously, add-target threw Refusing to overwrite when any file the target would write
    already existed — including the common case where the target is already fully scaffolded — and
    the printed remedy ("remove the file, then re-run") regenerated a placeholder with a blanked
    description, which is schema-invalid for targets whose manifest requires a non-empty
    description (e.g. Vercel's SKILL.md), immediately failing aipm build.

    • An already-materialized target (every file it would write already exists) is now a friendly
      no-op: addTarget() resolves with { status: 'already-present', written: [], preserved: [...] }
      and the CLI prints '<target>' is already present in <plugin>; nothing to do. instead of
      throwing.
    • Existing files are never overwritten. For a multi-file target with a partial conflict, the
      existing file(s) are preserved untouched and only the missing file(s) are written
      (status: 'partially-added'); the CLI reports which files were preserved so the author can
      review them by hand.
    • addTarget()'s public return type changes from Promise<void> to
      Promise<AddTargetOutcome> (new exported type: { target, status, written, preserved }).
    • Placeholder fields a schema requires to be non-empty (Vercel's SKILL.md description) are now
      emitted as non-empty placeholder prose instead of a blank string, so add-target's own output
      always passes aipm build/aipm validate.
  • #113 5e45ad5 Thanks @mike-north! - Reject a Claude manifest hooks reference to the auto-loaded hooks/hooks.json

    Claude Code auto-loads <pluginDir>/hooks/hooks.json and refuses to load a plugin whose
    .claude-plugin/plugin.json hooks field names that same file ("Duplicate hooks file detected …
    The standard hooks/hooks.json is loaded automatically, so manifest.hooks should only reference
    additional hook files"). aipm validate now emits a hard schema-invalid finding when the hooks
    field — a string, or any string entry when it is an array — normalizes to hooks/hooks.json, with
    a hint to drop the reference. Claude's generated hooks artifact remains hooks/claude.json.

  • #106 45d20f7 Thanks @mike-north! - Warn aipm init about an ancestor pnpm workspace instead of leaving newcomers to silently
    corrupt it, and sync the CLI's README.md/docs quick start with aipm --help.

    A directory with no local package.json that sits under an ancestor pnpm-workspace.yaml has
    pnpm add/pnpm install silently target the ANCESTOR's manifest and lockfile instead of a new
    repo's own — a first-time user following the old quick start verbatim could corrupt an unrelated
    parent project with no warning.

    • init()'s public return type changes from Promise<void> to Promise<InitOutcome> (new
      exported type: { ancestorWorkspace?: string }). ancestorWorkspace is the absolute path to an
      ancestor pnpm-workspace.yaml, when one exists above the newly scaffolded directory.
    • aipm init prints a warning to stderr when ancestorWorkspace is set, before telling the user
      to run pnpm install.
    • packages/cli/README.md ("GUIDE.md" in the published package) now: requires npm init -y
      before the install command, with an explicit workspace-hazard note; documents the lint
      command and every subcommand's flags (previously undocumented, though implemented); includes
      the quick start's install step; and replaces the dead ../../docs/specs/architecture.md
      relative link (only resolvable inside the monorepo checkout, not the published npm package)
      with an absolute GitHub URL.
  • #107 9467da0 Thanks @mike-north! - Remove vercel from the default set of targets a fresh aipm scaffold/core.scaffold() declares.

    vercel's only build artifact is an author-authored skills/<name>/SKILL.md, which the scaffold
    never seeds — so a plugin created with the default target set declared vercel but emitted zero
    artifacts for it anywhere on aipm build, while aipm list-targets/aipm check-support reported
    it as fully supported. A fresh scaffold now declares every known target except vercel; vercel
    remains fully supported and can still be requested explicitly via core.scaffold(name, { targets: [...] }) or added to an existing plugin with aipm add-target <plugin> vercel.

Patch Changes

  • #98 8a72630 Thanks @mike-north! - Fix aipm validate/aipm build silently ignoring a plugin-shaped plugins/* directory (has a
    target manifest and/or a skill) that is missing aipm.config.ts. Discovery previously filtered
    repo-root candidates on config presence alone, so such a directory never reached the plugin list —
    validate reported green and build reported "Built 0 plugin(s)" with exit 0 even though the
    plugin was broken and unbuildable. Discovery now also includes plugin-shaped-but-configless
    directories; downstream handling is unchanged (hard envelope-invalid from validate, a thrown
    error naming the missing aipm.config.ts from build) — the same diagnostic a single-plugin
    target missing its config already produced. A directory with neither a config nor any plugin-shape
    marker is still correctly excluded from discovery.

  • #110 4e5f352 Thanks @mike-north! - Fix three CLI output-wording/ordering defects that misled users during a normal build/validate
    run (no behavior change — the same conditions are still detected, only how/when they are
    reported changes):

    • Pre-build freshness on a dist bundle file that was never built now reports missing with a
      "run aipm build" hint, instead of stale.
    • aipm build's Built N plugin(s), M artifact(s). success line no longer prints when the
      post-build validate step has a hard finding that fails the run.
    • The version-consistency finding now hints that aipm.config.ts is the source of truth for
      the version, so the manifest — not the config — is the one to bump.
  • #104 fa00aae Thanks @mike-north! - Fix aipm init's README.md and aipm scaffold's Kiro POWER.md emitting literal
    backslash-backtick sequences (\`, byte pair 5c 60) instead of real backtick characters
    (0x60), which rendered the generated Markdown visibly broken.

    Root cause: these templates are tagged with String.raw (the project convention for multi-line
    embedded Markdown, which disables escape-sequence interpretation), but their source still wrote an
    escaped backtick (\`) to embed a literal backtick character — the escape is only interpreted
    in an ordinary template literal, so under String.raw it stayed as the two literal characters
    backslash + backtick. Both templates now interpolate a bt = '`' constant instead of escaping
    the backtick in the template source.

    Also fixes Kiro's POWER.md "Related Files" bullet, which presented steering/ as an existing
    sibling file even though a freshly-scaffolded plugin has no steering/ directory (Kiro's scaffold
    contributes only POWER.md) — it now reads `steering/` (optional, hand-authored) — add Kiro steering files here if needed.

  • #100 dbc0e68 Thanks @mike-north! - Fix aipm validate and aipm lint silently accepting a Claude plugin.json that is valid JSON but omits a schema-required field (e.g. name) — this now emits a hard schema-invalid finding / schema/target-conformance diagnostic, matching the other targets' behavior. aipm lint --format json's summary.fileCount now reflects the files a run actually scanned (LintResult.scannedFiles) instead of the number of distinct files a diagnostic happened to be attached to, which previously stayed pinned regardless of manifest changes.

@ai-plugin-marketplace/cli@0.7.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 18:28
a63e5b1

Minor Changes

  • #99 7efeba9 Thanks @mike-north! - Make aipm add-target preserve-or-warn instead of refusing an already-materialized target and
    generating a schema-invalid skeleton on retry.

    Previously, add-target threw Refusing to overwrite when any file the target would write
    already existed — including the common case where the target is already fully scaffolded — and
    the printed remedy ("remove the file, then re-run") regenerated a placeholder with a blanked
    description, which is schema-invalid for targets whose manifest requires a non-empty
    description (e.g. Vercel's SKILL.md), immediately failing aipm build.

    • An already-materialized target (every file it would write already exists) is now a friendly
      no-op: addTarget() resolves with { status: 'already-present', written: [], preserved: [...] }
      and the CLI prints '<target>' is already present in <plugin>; nothing to do. instead of
      throwing.
    • Existing files are never overwritten. For a multi-file target with a partial conflict, the
      existing file(s) are preserved untouched and only the missing file(s) are written
      (status: 'partially-added'); the CLI reports which files were preserved so the author can
      review them by hand.
    • addTarget()'s public return type changes from Promise<void> to
      Promise<AddTargetOutcome> (new exported type: { target, status, written, preserved }).
    • Placeholder fields a schema requires to be non-empty (Vercel's SKILL.md description) are now
      emitted as non-empty placeholder prose instead of a blank string, so add-target's own output
      always passes aipm build/aipm validate.
  • #106 45d20f7 Thanks @mike-north! - Warn aipm init about an ancestor pnpm workspace instead of leaving newcomers to silently
    corrupt it, and sync the CLI's README.md/docs quick start with aipm --help.

    A directory with no local package.json that sits under an ancestor pnpm-workspace.yaml has
    pnpm add/pnpm install silently target the ANCESTOR's manifest and lockfile instead of a new
    repo's own — a first-time user following the old quick start verbatim could corrupt an unrelated
    parent project with no warning.

    • init()'s public return type changes from Promise<void> to Promise<InitOutcome> (new
      exported type: { ancestorWorkspace?: string }). ancestorWorkspace is the absolute path to an
      ancestor pnpm-workspace.yaml, when one exists above the newly scaffolded directory.
    • aipm init prints a warning to stderr when ancestorWorkspace is set, before telling the user
      to run pnpm install.
    • packages/cli/README.md ("GUIDE.md" in the published package) now: requires npm init -y
      before the install command, with an explicit workspace-hazard note; documents the lint
      command and every subcommand's flags (previously undocumented, though implemented); includes
      the quick start's install step; and replaces the dead ../../docs/specs/architecture.md
      relative link (only resolvable inside the monorepo checkout, not the published npm package)
      with an absolute GitHub URL.
  • #107 9467da0 Thanks @mike-north! - Remove vercel from the default set of targets a fresh aipm scaffold/core.scaffold() declares.

    vercel's only build artifact is an author-authored skills/<name>/SKILL.md, which the scaffold
    never seeds — so a plugin created with the default target set declared vercel but emitted zero
    artifacts for it anywhere on aipm build, while aipm list-targets/aipm check-support reported
    it as fully supported. A fresh scaffold now declares every known target except vercel; vercel
    remains fully supported and can still be requested explicitly via core.scaffold(name, { targets: [...] }) or added to an existing plugin with aipm add-target <plugin> vercel.

Patch Changes

  • #98 8a72630 Thanks @mike-north! - Fix aipm validate/aipm build silently ignoring a plugin-shaped plugins/* directory (has a
    target manifest and/or a skill) that is missing aipm.config.ts. Discovery previously filtered
    repo-root candidates on config presence alone, so such a directory never reached the plugin list —
    validate reported green and build reported "Built 0 plugin(s)" with exit 0 even though the
    plugin was broken and unbuildable. Discovery now also includes plugin-shaped-but-configless
    directories; downstream handling is unchanged (hard envelope-invalid from validate, a thrown
    error naming the missing aipm.config.ts from build) — the same diagnostic a single-plugin
    target missing its config already produced. A directory with neither a config nor any plugin-shape
    marker is still correctly excluded from discovery.

  • #110 4e5f352 Thanks @mike-north! - Fix three CLI output-wording/ordering defects that misled users during a normal build/validate
    run (no behavior change — the same conditions are still detected, only how/when they are
    reported changes):

    • Pre-build freshness on a dist bundle file that was never built now reports missing with a
      "run aipm build" hint, instead of stale.
    • aipm build's Built N plugin(s), M artifact(s). success line no longer prints when the
      post-build validate step has a hard finding that fails the run.
    • The version-consistency finding now hints that aipm.config.ts is the source of truth for
      the version, so the manifest — not the config — is the one to bump.
  • #104 fa00aae Thanks @mike-north! - Fix aipm init's README.md and aipm scaffold's Kiro POWER.md emitting literal
    backslash-backtick sequences (\`, byte pair 5c 60) instead of real backtick characters
    (0x60), which rendered the generated Markdown visibly broken.

    Root cause: these templates are tagged with String.raw (the project convention for multi-line
    embedded Markdown, which disables escape-sequence interpretation), but their source still wrote an
    escaped backtick (\`) to embed a literal backtick character — the escape is only interpreted
    in an ordinary template literal, so under String.raw it stayed as the two literal characters
    backslash + backtick. Both templates now interpolate a bt = '`' constant instead of escaping
    the backtick in the template source.

    Also fixes Kiro's POWER.md "Related Files" bullet, which presented steering/ as an existing
    sibling file even though a freshly-scaffolded plugin has no steering/ directory (Kiro's scaffold
    contributes only POWER.md) — it now reads `steering/` (optional, hand-authored) — add Kiro steering files here if needed.

  • #100 dbc0e68 Thanks @mike-north! - Fix aipm validate and aipm lint silently accepting a Claude plugin.json that is valid JSON but omits a schema-required field (e.g. name) — this now emits a hard schema-invalid finding / schema/target-conformance diagnostic, matching the other targets' behavior. aipm lint --format json's summary.fileCount now reflects the files a run actually scanned (LintResult.scannedFiles) instead of the number of distinct files a diagnostic happened to be attached to, which previously stayed pinned regardless of manifest changes.

  • #103 43db451 Thanks @mike-north! - Fix -h/--help on subcommands (build, validate, lint, scaffold, init, migrate, check-support, add-target, list-targets) executing the subcommand instead of printing usage — aipm build --help ran a real build, and aipm validate --help misparsed --help as the target path. --help/-h now short-circuits to usage and exits 0 before any argument parsing or side effect, for every subcommand.

  • Updated dependencies [7efeba9, 5e45ad5, 8a72630, 4e5f352, fa00aae, dbc0e68, 45d20f7, [9467da0](https://gi...

Read more

@ai-plugin-marketplace/core@0.9.0

Choose a tag to compare

@github-actions github-actions released this 19 Jul 14:52
ab2f711

Minor Changes

  • #81 cb16c38 Thanks @mike-north! - Guard aipm build against a stale installed toolkit silently reverting generated artifacts.

    Every sentinel-carrying generated artifact is now stamped with the @ai-plugin-marketplace/core
    version that produced it (_generated.version in JSON outputs, a # version: line in
    inline/sidecar outputs). Before writing anything, aipm build compares the installed core version
    against the version stamped into existing committed artifacts: if the installed toolkit is older
    (by semver precedence), the build refuses with a non-zero exit and a message naming both versions
    and suggesting pnpm install. This closes the failure mode where a checkout with a stale
    node_modules regenerates committed outputs with an older generator and silently reverts a shipped
    fix. Equal-or-newer installs, first-time/unstamped trees, and same-version rebuilds proceed as
    before and (re)stamp with the installed version.

    • New BuildOptions.forceDowngrade and the aipm build --force-downgrade flag override the guard.
    • The freshness check ignores the version stamp, so a version bump alone no longer marks committed
      artifacts stale.
  • #84 6d2ee20 Thanks @mike-north! - aipm validate (and lint()'s correctness/version-consistency rule) now fails when a declared
    target's manifest version field (.claude-plugin/plugin.json, .codex-plugin/plugin.json,
    .cursor-plugin/plugin.json, gemini-extension.json, POWER.md frontmatter,
    .plugin/plugin.json) does not match aipm.config.ts's version. Installs are keyed by manifest
    version, so a stale author-maintained manifest previously let a release ship with aipm.config.ts
    bumped but the manifest still pointing at the old version — auto-update silently kept serving the
    pre-release artifact. Mirrors the existing name-consistency check: a new version-consistency
    FindingCode, hard severity, one finding per mismatched manifest.

    Fixes a related scaffold bug this check surfaced: aipm scaffold wrote aipm.config.ts with
    version: '0.1.0' while every per-target scaffolded manifest wrote version: '0.0.1', so a
    freshly-scaffolded plugin failed version-consistency immediately. aipm scaffold now emits
    '0.0.1' consistently everywhere.

Patch Changes

  • #78 d0ac824 Thanks @mike-north! - Fix the emitted Cursor controller-shim runner (hooks/cursor-shim.mjs) dropping a handler's
    denial message when it gates via the top-level continue: false shape. Per the Claude Code hooks
    contract, a continue: false denial carries its user-facing message in stopReasonreason is
    scoped to the separate decision: "block" shape. The runner's interpretPreToolUse and
    interpretUserPromptSubmit only ever read parsed.reason, so a handler following the documented
    continue/stopReason contract still denied correctly but Cursor showed the user a bare,
    unexplained denial.

    Both interpreters now fall back reasonstopReason when translating a continue: false
    denial to Cursor's flat control JSON (agent_message for preToolUse, user_message for
    beforeSubmitPrompt), preferring reason if a handler sets both. The existing
    decision: "block" (with reason) behavior is unchanged, and a continue: false payload with
    neither field still denies without a message and without crashing. (#57)

  • #79 f061725 Thanks @mike-north! - hooks/payload-adapter is no longer emitted for a plugin whose hooks/claude.yaml declares zero hook events (hooks: {}) — previously the 83-line adapter script (and its sentinel) were emitted even when nothing in the plugin could ever invoke them. The emitted --schema output is now pre-sorted at generation time instead of spawning jq -S . on every invocation (byte-identical output, no runtime cost), the generated banners in hooks/payload-adapter and hooks/cursor-shim.mjs now qualify their docs/specs/*.md pointers with @ai-plugin-marketplace/tools so they resolve for a reader in a consumer repo, and hooks/payload-adapter's temp-file mktemp call now uses an explicit template.

@ai-plugin-marketplace/cli@0.6.0

Choose a tag to compare

@github-actions github-actions released this 19 Jul 14:52
ab2f711

Minor Changes

  • #81 cb16c38 Thanks @mike-north! - Guard aipm build against a stale installed toolkit silently reverting generated artifacts.

    Every sentinel-carrying generated artifact is now stamped with the @ai-plugin-marketplace/core
    version that produced it (_generated.version in JSON outputs, a # version: line in
    inline/sidecar outputs). Before writing anything, aipm build compares the installed core version
    against the version stamped into existing committed artifacts: if the installed toolkit is older
    (by semver precedence), the build refuses with a non-zero exit and a message naming both versions
    and suggesting pnpm install. This closes the failure mode where a checkout with a stale
    node_modules regenerates committed outputs with an older generator and silently reverts a shipped
    fix. Equal-or-newer installs, first-time/unstamped trees, and same-version rebuilds proceed as
    before and (re)stamp with the installed version.

    • New BuildOptions.forceDowngrade and the aipm build --force-downgrade flag override the guard.
    • The freshness check ignores the version stamp, so a version bump alone no longer marks committed
      artifacts stale.
  • #84 6d2ee20 Thanks @mike-north! - aipm validate (and lint()'s correctness/version-consistency rule) now fails when a declared
    target's manifest version field (.claude-plugin/plugin.json, .codex-plugin/plugin.json,
    .cursor-plugin/plugin.json, gemini-extension.json, POWER.md frontmatter,
    .plugin/plugin.json) does not match aipm.config.ts's version. Installs are keyed by manifest
    version, so a stale author-maintained manifest previously let a release ship with aipm.config.ts
    bumped but the manifest still pointing at the old version — auto-update silently kept serving the
    pre-release artifact. Mirrors the existing name-consistency check: a new version-consistency
    FindingCode, hard severity, one finding per mismatched manifest.

    Fixes a related scaffold bug this check surfaced: aipm scaffold wrote aipm.config.ts with
    version: '0.1.0' while every per-target scaffolded manifest wrote version: '0.0.1', so a
    freshly-scaffolded plugin failed version-consistency immediately. aipm scaffold now emits
    '0.0.1' consistently everywhere.

Patch Changes

@ai-plugin-marketplace/core@0.8.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 14:33
b238307

Minor Changes

  • #72 6100387 Thanks @mike-north! - Add aipm lint [path] [--as <mode>] [--format text|json|sarif] [--rule <id>=<severity> ...], exposing the lint engine core (#61) via the CLI with machine-readable output.

    • text (default): grouped by file as file:line:col ruleId severity message; --verbose appends the docs URL; diagnostics without a range render as file ruleId severity message (position segment omitted, never zero-filled).
    • json: the raw Diagnostic[] plus a summary envelope (errorCount/warnCount/infoCount/fileCount).
    • sarif: SARIF 2.1.0, one rules[] entry per distinct rule id — validated in tests against the official SARIF 2.1.0 JSON Schema.
    • Exit codes: 0 no error-severity diagnostics, 1 errors present, 2 usage error (unknown --format, malformed --rule, or an unsupported --as mode — only aipm-repo is implemented; foreign discovery modes are a later issue).
    • --rule <id>=<severity> (repeatable) overrides a rule's severity post-hoc, or drops its diagnostics entirely with =off.
    • New core export: applyRuleSeverityOverrides(diagnostics, overrides), the pure filter backing --rule.
    • aipm validate behavior and exit codes are unchanged.
  • #70 2b34d54 Thanks @mike-north! - Add the position-aware lint engine core (Diagnostic/Rule/document-layer types, lint()) and migrate every existing validate() check onto it, with aipm validate behavior unchanged.

    • New packages/core/src/lint/ module: Diagnostic, Range, Rule, RuleContext types (L-D1/L-D4), a position-aware document layer for JSON (jsonc-parser), YAML (the yaml package's CST), and markdown frontmatter, and a pure diagnosticToFinding() mapping back to the legacy Finding shape.
    • Every existing validate check (envelope shape, per-target schema, envelope adherence, frontmatter parsing, name consistency, MCP key sync, marketplace registration, freshness, default-marketplace-name) is now backed by a Rule object carrying its legacy FindingCode.
    • Four new correctness/* rules: broken-file-ref, unknown-hook-event, invalid-matcher, duplicate-component-name.
    • New public exports: lint(path, options): Promise<LintResult>, plus Diagnostic, Range, Rule, RuleContext, Document/JsonDocument/YamlDocument/FrontmatterDocument, Fix, Position, LintOptions, LintResult, and ConfigCache.
    • jsonc-parser is now a direct dependency (previously transitive only).

Patch Changes

  • #60 d1e1af3 Thanks @mike-north! - Anchor the Cursor controller-shim invocation to ${CLAUDE_PLUGIN_ROOT:-.} instead of a cwd-relative
    path. The 0.7.0 transform emitted gating hook commands as node ./hooks/cursor-shim.mjs …, which
    assumed Cursor runs plugin hook commands with cwd = plugin root — an assumption Cursor does not
    guarantee. Because shimmed entries set failClosed: true, a shim path that failed to resolve did
    not merely disable the gate: node exited "Cannot find module" and every gated tool call was
    denied
    for that plugin under Cursor.

    The emitted command is now
    node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/cursor-shim.mjs" <event> -- '<handler>' (double-quoted, so a
    plugin root containing spaces survives shell expansion). The :-. fallback matters because Cursor's
    behavior differs by consumption layout: for an installed plugin, ${CLAUDE_PLUGIN_ROOT} is set
    to the plugin's install path (confirmed only by Cursor staff forum posts, not the official docs), so
    the invocation anchors there regardless of cwd; for project-level/colocated hooks (a project's
    own .cursor/hooks.json), Cursor does not set the variable at all — verified empirically against a
    real cursor-agent build — so an unconditional ${CLAUDE_PLUGIN_ROOT} anchor would expand to an
    empty string and, combined with failClosed: true, deny every gated call. The . fallback resolves
    relative to cwd (project root for project-level hooks), preserving the previously-working colocated
    behavior.

    The enforcement UAT now covers both layouts explicitly: a project-level/colocated scenario with
    CLAUDE_PLUGIN_ROOT deleted from the spawn environment, and an installed-plugin scenario with the
    hook cwd and the shim directory deliberately different and CLAUDE_PLUGIN_ROOT set — the case the
    original workspace-colocated UAT could not detect. (#56)

@ai-plugin-marketplace/cli@0.5.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 14:33
b238307

Minor Changes

  • #72 6100387 Thanks @mike-north! - Add aipm lint [path] [--as <mode>] [--format text|json|sarif] [--rule <id>=<severity> ...], exposing the lint engine core (#61) via the CLI with machine-readable output.
    • text (default): grouped by file as file:line:col ruleId severity message; --verbose appends the docs URL; diagnostics without a range render as file ruleId severity message (position segment omitted, never zero-filled).
    • json: the raw Diagnostic[] plus a summary envelope (errorCount/warnCount/infoCount/fileCount).
    • sarif: SARIF 2.1.0, one rules[] entry per distinct rule id — validated in tests against the official SARIF 2.1.0 JSON Schema.
    • Exit codes: 0 no error-severity diagnostics, 1 errors present, 2 usage error (unknown --format, malformed --rule, or an unsupported --as mode — only aipm-repo is implemented; foreign discovery modes are a later issue).
    • --rule <id>=<severity> (repeatable) overrides a rule's severity post-hoc, or drops its diagnostics entirely with =off.
    • New core export: applyRuleSeverityOverrides(diagnostics, overrides), the pure filter backing --rule.
    • aipm validate behavior and exit codes are unchanged.

Patch Changes

@ai-plugin-marketplace/core@0.7.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 04:09
60f59b7

Minor Changes

  • #40 8251430 Thanks @mike-north! - Contract-translate Cursor controller hooks with a generated fail-closed shim. The shipped Cursor
    hooks transform is observer-only: a Claude-authored block/deny gate emitted through it fails OPEN on
    Cursor, because the two harnesses' handler contracts diverge on tool identity (Shell vs Bash),
    event casing, control-output shape, and failure default. aipm build now translates that contract
    for gating events so controller hooks enforce correctly.

    Classification is static, by event: a committed GATING_EVENTS set (PreToolUse,
    UserPromptSubmit) is treated as controllers; PostToolUse/Stop fire after the decision point,
    cannot block, and stay on the byte-identical observer path. For a gating event, each generated
    hooks/cursor.json entry's command becomes node ./hooks/cursor-shim.mjs <cursorEvent> -- <original handler command> with failClosed: true (the handler keeps its own args verbatim after
    the -- boundary).

    When a plugin has at least one gating-event hook, the build additionally emits a static Node runner
    hooks/cursor-shim.mjs plus its hooks/cursor-shim.mjs.generated sidecar sentinel (the runner is
    pure executable JS, so the sentinel lives in the companion file). The runner reads Cursor's hook
    stdin, translates it to a Claude envelope (ShellBash, PascalCase event, session_id /
    tool_input.command pass-through), spawns the handler, and translates the handler's Claude control
    output back to Cursor's flat control JSON (permissionDecisionpermission; decision:"block" +
    reason → permission:"deny" + agent_message; beforeSubmitPrompt block →
    { continue: false, user_message }). It is fail-closed: a non-zero handler exit, malformed handler
    output, bad arguments, or a spawn error emit a deny and exit 2, always as valid JSON. The
    cursorHooksFileSchema now accepts failClosed on an entry, and all three artifacts are
    freshness-checked byte-for-byte.

  • #54 600e3a0 Thanks @mike-north! - GeneratedFile.target now accepts 'shared' in addition to a TargetId (the new
    GeneratedFileTarget = TargetId | 'shared' type). Two build artifacts genuinely have no single
    owning target — hooks/payload-adapter (and its sidecar), emitted for any plugin authoring hooks
    regardless of which targets it declares, and the generated-root sidecar manifest
    (.aipm/generated-root.json), which spans every emitted single-artifact-host/registry owner — and
    were previously attributed to an arbitrary, deterministically-chosen single target as a workaround.
    Both now report target: 'shared' instead.

    Consumers reading BuildResult.artifacts[].target and narrowing on TargetId should account for
    the new 'shared' value; a switch over TargetId alone will no longer be exhaustive against
    GeneratedFileTarget.

  • #49 feabd3b Thanks @mike-north! - Emit a generated cross-harness hook payload adapter (hooks/payload-adapter) for every plugin
    that authors hooks/claude.yaml. hooks/claude.yaml is authored once in the Claude Code dialect;
    Codex is near-identical but not quite (tool_response vs tool_output, extra additive fields), so
    plugin hook handler code previously had to re-derive those deltas by hand. The adapter is a static
    sh + jq filter, emitted regardless of which target(s) the plugin's envelope declares, that
    normalizes any supported harness's raw hook stdin payload into one documented canonical shape (the
    Claude Code hook envelope, additively extended) — see docs/specs/payload-adapter.md.

    Behavior: the canonical shape is the Claude Code hook envelope; Codex's tool_response gains a
    canonical tool_output alongside it (never removing the original field); a harness: {name}
    envelope is added, detected from Codex's additive-only fields (turn_id/model/tool_response/
    agent_transcript_path), then the CODEX_HOME environment variable as a secondary signal, then a
    recognized PascalCase hook_event_name, else "unknown"; is_subagent is added for every payload
    from a non-empty agent_id; payload-adapter --schema prints the canonical JSON Schema plus a
    single-sourced contract version and exits 0 without reading stdin; a missing jq on PATH
    degrades to a byte-for-byte stdin passthrough, exit 0 (never breaks a hook chain); output key order
    is sorted at every nesting level for deterministic, golden-able output.

    The adapter is a byte-exact static asset (like the existing Cursor controller-hook shim) — every
    plugin that authors hooks receives identical bytes, and hooks/payload-adapter.generated carries
    its sidecar sentinel so freshness compares it byte-for-byte alongside the existing generated hook
    artifacts.

Patch Changes

  • #43 9937b99 Thanks @mike-north! - Harden the generated Cursor controller-hook shim (hooks/cursor-shim.mjs).

    • Shell fidelity. The transform now embeds the original Claude handler command as a single
      POSIX-single-quoted token after the -- sentinel, and the runner executes everything after --
      through a shell (spawnSync(cmd, { shell: true, … })) — matching Claude's own sh -c hook
      model. A handler command using env-var refs, quoting, or its own args now execs correctly on
      Cursor instead of failing to run (and denying).
    • No stdout truncation. The runner flushes stdout before exiting
      (process.stdout.write(json, () => process.exit(code))) on the fail-closed, allow/continue, and
      interpret paths, so a large allow decision is never truncated into malformed JSON.
    • Explicit spawn maxBuffer (64 MB). A handler emitting more than the default 1 MB of stdout is
      no longer misread as a spawn failure and denied.
    • Single YAML parse for Cursor. "Has a gating hook?" is derived from the already-converted
      Cursor document rather than a second parse of the source.
    • Single source of truth for the tool table. The runner's CURSOR_TO_CLAUDE_TOOLS is generated
      from the exported const at emit time (stable, sorted key order — the .mjs stays
      byte-deterministic), so the two copies cannot drift.

    Fail-closed safety is unchanged: a non-zero handler exit, malformed handler output, bad argv, spawn
    failure, or internal error still emits a deny and exits 2, always as valid JSON.

@ai-plugin-marketplace/cli@0.4.1

Choose a tag to compare

@github-actions github-actions released this 16 Jul 04:09
60f59b7

Patch Changes

@ai-plugin-marketplace/core@0.6.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 23:13
7d02405

Minor Changes

  • #35 0f4eece Thanks @mike-north! - Add a Cursor hooks build target. When a plugin's envelope includes cursor and it ships a
    hooks/claude.yaml source, aipm build now emits a Cursor-format hooks/cursor.json derived
    mechanically from that source — parallel to the existing claudehooks/claude.json and
    geminihooks/hooks.json fan-out.

    The transform renames Claude events to Cursor's camelCase vocabulary (PreToolUsepreToolUse,
    PostToolUsepostToolUse, Stopstop, UserPromptSubmitbeforeSubmitPrompt),
    translates matcher tool names (BashShell; Read/Write/Edit/Grep identity; unmapped
    matchers pass through), and reshapes Claude's nested matcher blocks into Cursor's flat
    { command, type?, matcher? } entries under a { version: 1, hooks: … } envelope. Source events
    with no Cursor equivalent are dropped. The generated file carries the standard _generated
    sentinel and is freshness-checked like the other hook JSONs.

    aipm validate now validates a present hooks/cursor.json against a strict schema (HARD
    schema-invalid on failure), and the Cursor manifest guidance points the hooks field at
    ./hooks/cursor.json instead of the Claude-format hooks/claude.json.

  • #30 a560f7c Thanks @mike-north! - Add soft Open Plugins conformance advisories on the native claude/cursor/codex targets.

    aipm validate now surfaces a new soft open-plugins-conformance finding (added to the
    FindingCode union) that nudges native plugins toward Open Plugins portability without ever failing
    them — it never flips ValidationResult.passed. Two advisories fire today:

    • Name-grammar drift — a plugin name that is valid for the native target but violates the Open
      Plugins name grammar (e.g. a--b or a trailing hyphen, which the native scaffold-slug regex
      accepts but Open Plugins rejects). It fires only for otherwise-native-valid manifests, so a broken
      name still gets its usual hard finding without a duplicate advisory.
    • Metadata-dir isolation — a non-plugin.json entry in a plugin's vendor metadata directory
      (.claude-plugin/ / .cursor-plugin/ / .codex-plugin/), which Open Plugins requires to hold
      only plugin.json.

    Also hardens path-traversal rejection: a .. segment in the mcpServers (and Codex apps/hooks)
    config-path fields — previously unchecked because those paths are not existence-validated — is now a
    hard schema-invalid across all three targets, matching the existing rejection on component paths.

    The Open Plugins name grammar is now a single shared source of truth
    (targets/open-plugins-conformance.ts) consumed by both the open-plugins target schema (where a
    violation is hard) and these advisories (where it is soft).

  • #28 38e53a7 Thanks @mike-north! - Add Open Plugins as a 7th host target (open-plugins).

    Open Plugins (open-plugins.com, v1.0.0) is a vendor-neutral external
    standard for the on-disk shape of an AI-assistant plugin. Declaring 'open-plugins' in a plugin's
    envelope now emits an Open-Plugins-conformant .plugin/plugin.json manifest and a repo-root
    marketplace.json registry (the 4th generated registry, at Open Plugins lookup position 1),
    projected from the same authored source that feeds every other target.

    The target validates the manifest against the Open Plugins name grammar and component-path rules
    (each path must be ./-relative with no ..), checks that declared component paths resolve on
    disk, and enforces metadata-directory isolation via a new hard metadata-dir-isolation finding
    (the .plugin/ directory must contain only plugin.json). Adds 'open-plugins' to the TargetId
    union and 'metadata-dir-isolation' to the FindingCode union (both additive).

    The repo-root marketplace.json is protected by the generated-root collision guard: a pre-existing
    marketplace.json the toolkit did not generate raises a hard root-artifact-collision and is never
    overwritten or orphan-removed.

@ai-plugin-marketplace/cli@0.4.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 23:13
7d02405

Minor Changes

  • #28 38e53a7 Thanks @mike-north! - Recognize the new open-plugins target across the CLI. aipm list-targets now lists
    open-plugins, aipm scaffold includes it in the default envelope (emitting .plugin/plugin.json
    and a repo-root marketplace.json), and aipm add-target <plugin> open-plugins, aipm build, and
    aipm validate handle it like any other host target.

Patch Changes