diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index c187173..6915168 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pinmind", - "version": "0.10.1", + "version": "0.11.0", "description": "Adaptive RU/EN task controller with deterministic routing, outcome contracts, and verified evidence.", "author": { "name": "Pinmind Project" diff --git a/.grok-plugin/plugin.json b/.grok-plugin/plugin.json index a17a05c..36b482f 100644 --- a/.grok-plugin/plugin.json +++ b/.grok-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pinmind", - "version": "0.10.1", + "version": "0.11.0", "description": "Adaptive RU/EN task controller with deterministic routing, outcome contracts, and verified evidence.", "author": { "name": "Pinmind Project" diff --git a/.node-version b/.node-version index 60ade1a..b7179ae 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -24.19.0 +24.20.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index fe64239..1254eb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/2.0.0/), and ## [Unreleased] +## [0.11.0] - 2026-09-01 + +### Added + +- A single `UserPromptSubmit` orchestration hook that keeps Pinmind as the sole + controller and selects passive Skillstate checkpoints only for durable work. +- Regression coverage for short-task exclusion, durable-task admission, + confirmation boundaries, and Ponytail's code-only role. + +### Changed + +- Document the fixed hierarchy: Pinmind controls, Skillstate records bounded + checkpoints, and Ponytail simplifies code. +- Advance the pinned release runtime to Node `24.20.0`. + ## [0.10.1] - 2026-08-24 ### Added diff --git a/LANGUAGE_ROUTING.md b/LANGUAGE_ROUTING.md index af1c211..0eec5eb 100644 --- a/LANGUAGE_ROUTING.md +++ b/LANGUAGE_ROUTING.md @@ -1,6 +1,6 @@ # Language routing and activation evaluation -Status: Phase A is included in the stable `0.6` line, following its experimental introduction in `0.5.0-experimental`. The current `0.10.1` source has a **457-case deterministic routing regression corpus**, **64 closed-schema language-evaluation cases** (32 development and 32 frozen release-gate cases), **28 host-activation fixtures**, and a separate phased-transfer regression for `CONTEXT_READY`. The local evaluator and release gate are implemented; fresh-host selection and end-task utility remain separate, unproven layers. The release split is a fixed regression gate, not a statistically independent benchmark. This document does not claim that ChatGPT, Codex, or Grok will activate Pinmind for every possible phrase. +Status: Phase A is included in the stable `0.6` line, following its experimental introduction in `0.5.0-experimental`. The current `0.11.0` source has a **457-case deterministic routing regression corpus**, **64 closed-schema language-evaluation cases** (32 development and 32 frozen release-gate cases), **28 host-activation fixtures**, and a separate phased-transfer regression for `CONTEXT_READY`. The local evaluator and release gate are implemented; fresh-host selection and end-task utility remain separate, unproven layers. The release split is a fixed regression gate, not a statistically independent benchmark. This document does not claim that ChatGPT, Codex, or Grok will activate Pinmind for every possible phrase. ## Decision diff --git a/PRIVACY.md b/PRIVACY.md index c4eabb3..f1d4db9 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Privacy -Pinmind `0.10.1` is a skills-only plugin. It provides local instructions, a bundled kernel, and offline AEP, language-routing, and A/B/C evaluation inputs; it has no account system, connector, MCP server, telemetry service, or project-operated network endpoint. +Pinmind `0.11.0` is a local skill and prompt-routing hook. It provides local instructions, a bundled kernel, and offline AEP, language-routing, and A/B/C evaluation inputs; it has no account system, connector, MCP server, telemetry service, or project-operated network endpoint. The hook returns routing context only and does not persist the submitted prompt. Pinmind processes only the task context and files made available by the active ChatGPT or Codex host. Data handling by that host and by any separately enabled tool is governed by the corresponding provider and user configuration. diff --git a/README.md b/README.md index 1768d8a..12435cd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Pinmind is a Russian-and-English workflow controller packaged as a Grok skill and a Codex App plugin. It classifies non-trivial work, including colloquial and lightly misspelled Russian, applies a process proportional to risk, composes specialist skills, and requires current evidence before calling a task complete. -Current stable version: `0.10.1`. +Current stable version: `0.11.0`. - GitHub repository marketplace: included in this repository. - Universal Plugins Directory: **not listed yet**. ChatGPT catalog steps apply only after OpenAI approval and a live listing check. @@ -16,18 +16,18 @@ Current stable version: `0.10.1`. 1. Add the pinned stable repository marketplace: ```bash - codex plugin marketplace add iammedved/Pinmind --ref v0.10.1 + codex plugin marketplace add iammedved/Pinmind --ref v0.11.0 ``` 2. Start Codex, run `/plugins`, select **Pinmind Project**, and install **Pinmind**. 3. Start a new Codex session. 4. Run `/skills` and confirm that `pinmind` is available. -The repository marketplace is separate from OpenAI's universal Plugins Directory. Pinning `v0.10.1` selects this exact stable release; omit `--ref` only when you intentionally want the reviewed latest repository state. +The repository marketplace is separate from OpenAI's universal Plugins Directory. Pinning `v0.11.0` selects this exact stable release; omit `--ref` only when you intentionally want the reviewed latest repository state. ### Recommended design-enabled installation order -Pinmind `0.10.1` can deterministically select two maintained external design +Pinmind `0.11.0` can deterministically select two maintained external design skills. They are not bundled with Pinmind and must be installed separately. For a fresh project, use this order: @@ -57,7 +57,7 @@ For a fresh project, use this order: 3. Install the reviewed Pinmind release: ```bash - codex plugin marketplace add iammedved/Pinmind --ref v0.10.1 + codex plugin marketplace add iammedved/Pinmind --ref v0.11.0 ``` Open `/plugins`, install **Pinmind** from **Pinmind Project**, and start a @@ -67,7 +67,7 @@ For a fresh project, use this order: `ui-ux-pro-max` are available. The historical `frontend-skill` is deprecated upstream and is not part of -Pinmind `0.10.1` design routing. Do not install it for this workflow. +Pinmind `0.11.0` design routing. Do not install it for this workflow. ### Codex CLI: upgrade or reinstall a reviewed revision @@ -93,7 +93,7 @@ recovery path; it is not the supported public upgrade path. 4. If Pinmind does not appear immediately, restart Codex. 5. Run `/skills` and confirm that `pinmind` is available. -No extra setup is required. Pinmind is currently skills-only: it needs no connector, external account, API key, or MCP server. +Pinmind needs no connector, external account, API key, or MCP server. The bundled local hook only classifies the submitted prompt and adds routing context; it performs no task action itself. ### Grok Build CLI: install for every new chat @@ -130,7 +130,7 @@ Pinmind is not currently available in the public Plugins Directory. After the li 4. Start a new chat. 5. Send: `@Pinmind Audit this repository without changing files. Determine the route first and report only verified findings.` -No connector, external account, API key, or additional configuration is required for the current skills-only package. +No connector, external account, API key, or additional configuration is required for the current local skill-and-hook package. Official OpenAI guidance: [install and use plugins](https://learn.chatgpt.com/docs/plugins), [package plugins and repository marketplaces](https://developers.openai.com/plugins/build/plugins), and [build or install skills](https://learn.chatgpt.com/docs/build-skills). @@ -146,11 +146,11 @@ Official OpenAI guidance: [install and use plugins](https://learn.chatgpt.com/do Implicit selection is probabilistic. Explicit invocation is the reliable choice for critical work. -### Design-skill routing in 0.10.1 +### Design-skill routing in 0.11.0 Earlier Pinmind releases could compose specialist skills only through general instructions. That left the choice between overlapping design skills to the -host or the current prompt. Version `0.10.1` adds a deterministic post-route +host or the current prompt. Version `0.11.0` includes a deterministic post-route decision: - **Impeccable only** for an explicit Impeccable command or an end-to-end @@ -167,10 +167,9 @@ Pinmind makes this choice with `design route` after its main process route. The specialist result never grants permission to edit, publish, deploy, use network services, or change Figma. Those boundaries still come from the user. -`0.10.1` is a patch release because the existing main route, contracts, -lifecycle, and authority model remain backward compatible. The release fixes -ambiguous design-skill composition, documents the external installation -requirements, and excludes the deprecated `frontend-skill`. +`0.11.0` is a minor release because Pinmind now owns automatic orchestration. +It decides whether a task needs passive Skillstate checkpoints while keeping +Ponytail limited to code simplification. Pinmind `0.10.0` added journaled `abandon`, non-destructive `archive`, proportional debugging/TDD/architecture/review guidance, and a compact readability profile. Superpowers and dyslex.ai remain research inputs: they are not installed, imported, or made runtime dependencies. @@ -264,7 +263,7 @@ publish or display Codex cachebuster build metadata. The stable public line starts at `0.6.0`. Backward-compatible fixes use patch versions such as `0.6.1`, `0.6.2`, `0.6.3`, `0.8.1`, `0.8.2`, `0.9.1`, and `0.10.1`; backward-compatible feature releases use a -new minor such as `0.7.0`, `0.8.0`, `0.9.0`, or `0.10.0`. Existing experimental tags are immutable history and +new minor such as `0.7.0`, `0.8.0`, `0.9.0`, `0.10.0`, or `0.11.0`. Existing experimental tags are immutable history and are never moved to newer commits. ## Validation @@ -288,8 +287,8 @@ this is a review-visible tamper-evidence boundary, not a cryptographically independent benchmark. For an auditable inventory, Pinmind counts top-level `test(` declarations rather -than quoting Node's runtime summary. The current manifest records 121 declarations -across ten test files, plus fixture-case counts for routes, activation, AEP, +than quoting Node's runtime summary. The current manifest records 124 declarations +across eleven test files, plus fixture-case counts for routes, activation, AEP, parallel admission, and language evaluation. These are separate dimensions and are not presented as one inflated "test count." diff --git a/ROADMAP.md b/ROADMAP.md index 59e8611..e8dad06 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,8 +2,8 @@ ## Verified baseline -The current stable baseline is `0.10.1`, published as tag `v0.10.1`. See -[CHANGELOG.md](CHANGELOG.md). The `0.10.1` release +The current stable baseline is `0.11.0`, published as tag `v0.11.0`. See +[CHANGELOG.md](CHANGELOG.md). The `0.11.0` release provides deterministic post-activation routing, local run-state containment and recovery diagnostics, baseline/freshness evidence, read-only `final check`, and explicit `finalize`. It also includes the reviewed P0 routing contrasts, required @@ -47,15 +47,15 @@ only unfinished, measurable work; it is not implementation authority. ## Now -The `0.10.1` release keeps speech-act routing, a compact `SKILL.md`, and a split +The `0.11.0` release keeps speech-act routing, a compact `SKILL.md`, and a split kernel. AEP, A/B/C experiments, and P2 stay out of chat-time runtime. Remaining work is comparable host-run A/B/C evidence, fresh-host activation evidence, and independent release-corpus governance, not another workflow controller. -### Remaining boundaries after the 0.10.1 release +### Remaining boundaries after the 0.11.0 release - Re-run two fresh implicit and two fresh explicit Codex CLI sessions from the - immutable installed `v0.10.1` tag. Record the host version, package tree hash, + immutable installed `v0.11.0` tag. Record the host version, package tree hash, selected cache path, first route, and whether routing preceded task tools. - Directly inspect the Pinmind card and composer entry in a fresh Codex App session. The manifest and cache can prove which tracked image is configured; diff --git a/evals/release-manifest.json b/evals/release-manifest.json index c033ca6..9d39668 100644 --- a/evals/release-manifest.json +++ b/evals/release-manifest.json @@ -1,13 +1,18 @@ { "schemaVersion": 1, "algorithm": "sha256", - "nodeVersion": "24.19.0", + "nodeVersion": "24.20.0", "frozenInputs": [ { "role": "router", "path": "skills/pinmind/scripts/lib/route.mjs", "sha256": "808643bbb66cf8458a239a7be66bce9ef2f91a2e75901520fcaeae5dcec0b79c" }, + { + "role": "orchestration-hook", + "path": "hooks/pinmind-orchestrator.mjs", + "sha256": "c6d0806d6d94d719b41b0e1e923f66670396fa820ac24becdefe16b8d721479a" + }, { "role": "design-specialist-router", "path": "skills/pinmind/scripts/lib/design-skills.mjs", @@ -70,6 +75,14 @@ } ], "commands": [ + { + "id": "orchestration-hook-tests", + "argv": [ + "node", + "--test", + "tests/orchestration-hook.test.mjs" + ] + }, { "id": "kernel-tests", "argv": [ @@ -282,6 +295,10 @@ ], "inventory": { "testFiles": [ + { + "path": "tests/orchestration-hook.test.mjs", + "topLevelDeclarations": 3 + }, { "path": "tests/kernel.test.mjs", "topLevelDeclarations": 63 diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..7c29651 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "node \"${PLUGIN_ROOT}/hooks/pinmind-orchestrator.mjs\"", + "timeout": 2, + "statusMessage": "Pinmind is routing the task" + } + ] + } + ] + } +} diff --git a/hooks/pinmind-orchestrator.mjs b/hooks/pinmind-orchestrator.mjs new file mode 100644 index 0000000..e0869e5 --- /dev/null +++ b/hooks/pinmind-orchestrator.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env node +import { fileURLToPath } from 'node:url'; + +import { routeTask } from '../skills/pinmind/scripts/lib/route.mjs'; + +// Persistent Pinmind runs currently exist only for software changes. +const DURABLE_ROUTE = new Set(['software-change']); + +export function needsSkillstate(prompt, route) { + if (!DURABLE_ROUTE.has(route.route) || route.needsHumanConfirmation) return false; + const text = String(prompt).trim(); + const stages = [...text.matchAll(/\b(?:audit|analy[sz]e|inspect|implement|fix|test|verify|refactor|review|document|build|update|migrate|debug)\b|(?:аудит|проанализ|проверь|исправ|реализ|добав|протест|рефактор|подготов|настрой|обнов|внедр|мигрир|исслед)/giu)].length; + return text.length >= 600 || stages >= 3 || route.executionSpan !== 'local' || route.risk === 'high' || /(?:многоэтап|долг(?:ая|ий|ую)|нескольк(?:о|их) этап|interruption|resume|сначала.+(?:потом|затем))/iu.test(text); +} + +export function hookOutputForPrompt(prompt) { + if (typeof prompt !== 'string' || !prompt.trim()) return {}; + const route = routeTask({ text: prompt }); + if (route.route === 'simple' || route.route === 'operational') return {}; + const durable = needsSkillstate(prompt, route); + const statePolicy = durable + ? 'This task needs durable state. After Pinmind initializes its run, use $skillstate-runtime only for passive `skillstate checkpoint` records at clean phase boundaries. Never run `skillstate run` or start another controller.' + : 'This task does not need Skillstate. Do not start or invoke it.'; + return { + hookSpecificOutput: { + hookEventName: 'UserPromptSubmit', + additionalContext: `origin=pinmind. Pinmind is the sole workflow controller for this turn; load and follow $pinmind before task tools. ${statePolicy} Ponytail may simplify code, but it never controls routing, state, authority, or phase order.`, + }, + }; +} + +function startHook() { + let input = ''; + let finished = false; + function finish() { + if (finished) return; + finished = true; + try { process.stdout.write(JSON.stringify(hookOutputForPrompt(JSON.parse(input.replace(/^\uFEFF/, '')).prompt))); } + catch { process.stdout.write('{}'); } + } + process.stdin.on('data', chunk => { input += chunk; }); + process.stdin.on('end', finish); + process.stdin.on('error', finish); + process.stdin.resume(); + const timeout = setTimeout(finish, 1000); + process.stdin.once('end', () => clearTimeout(timeout)); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) startHook(); diff --git a/scripts/verify-release.mjs b/scripts/verify-release.mjs index 0430955..f2f1619 100644 --- a/scripts/verify-release.mjs +++ b/scripts/verify-release.mjs @@ -48,6 +48,7 @@ const TEST_FILE_KEYS = new Set(['path', 'topLevelDeclarations']); export const EXPECTED_FROZEN_INPUTS = Object.freeze([ ['router', 'skills/pinmind/scripts/lib/route.mjs'], + ['orchestration-hook', 'hooks/pinmind-orchestrator.mjs'], ['design-specialist-router', 'skills/pinmind/scripts/lib/design-skills.mjs'], ['mixed-decomposition', 'skills/pinmind/scripts/lib/decomposition.mjs'], ['goal-context', 'skills/pinmind/scripts/lib/goal-context.mjs'], @@ -63,6 +64,7 @@ export const EXPECTED_FROZEN_INPUTS = Object.freeze([ ]); export const EXPECTED_COMMANDS = Object.freeze([ + Object.freeze({ id: 'orchestration-hook-tests', argv: Object.freeze(['node', '--test', 'tests/orchestration-hook.test.mjs']) }), Object.freeze({ id: 'kernel-tests', argv: Object.freeze(['node', '--test', 'tests/kernel.test.mjs']) }), Object.freeze({ id: 'terminal-lifecycle-tests', argv: Object.freeze(['node', '--test', 'tests/terminal-lifecycle.test.mjs']) }), Object.freeze({ id: 'goal-context-tests', argv: Object.freeze(['node', '--test', 'tests/goal-context.test.mjs']) }), @@ -93,6 +95,7 @@ export const EXPECTED_COMMANDS = Object.freeze([ ]); const EXPECTED_TEST_FILES = Object.freeze([ + 'tests/orchestration-hook.test.mjs', 'tests/kernel.test.mjs', 'tests/terminal-lifecycle.test.mjs', 'tests/goal-context.test.mjs', diff --git a/skills/pinmind/SKILL.md b/skills/pinmind/SKILL.md index fdb11d7..5dbae35 100644 --- a/skills/pinmind/SKILL.md +++ b/skills/pinmind/SKILL.md @@ -54,6 +54,19 @@ Check `.pinmind/active.json` before a new persistent run. Reconcile it read-only Keep specialist skills (PDF, Superpowers, `/design`, `/execute-plan`). Pinmind wraps them; it does not replace them. +## Durable state and code simplicity + +Pinmind is the only workflow controller. The bundled `UserPromptSubmit` hook may mark a non-trivial request with `origin=pinmind` and decide whether it is durable enough for Skillstate. Never let Skillstate route the task, choose actions, or start its `run` controller beneath Pinmind. + +When the hook selects durable state, keep `.pinmind` as the source of truth. After `init` and at clean contract, execution, verification, and final phase boundaries, run: + +```bash +skillstate doctor --workspace +skillstate checkpoint --workspace --pinmind-run +``` + +If either command fails, continue under Pinmind and report that durable checkpoints are unavailable; do not start a second controller. Skip Skillstate for short or one-step work. Ponytail may select the smallest code change and review over-engineering, but it never changes routing, authority, state, or phase order. + ## UI design specialists After the main route, if the request may design, change, review, or fix a visual interface, run the deterministic specialist router before loading a UI skill: diff --git a/tests/kernel.test.mjs b/tests/kernel.test.mjs index 2367046..dbc3962 100644 --- a/tests/kernel.test.mjs +++ b/tests/kernel.test.mjs @@ -551,7 +551,7 @@ test('public release documentation, license, metadata, evaluation guides, and he const escapedBaseVersion = baseVersion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); assert.match(manifest.version, /^\d+\.\d+\.\d+$/); - assert.equal(baseVersion, '0.10.1'); + assert.equal(baseVersion, '0.11.0'); assert.equal(grokPlugin.name, manifest.name); assert.equal(grokPlugin.version, baseVersion); assert.match(manifest.description, /^Adaptive RU\/EN task controller/); @@ -559,7 +559,7 @@ test('public release documentation, license, metadata, evaluation guides, and he assert.match(agent, /short_description:\s*"Adaptive verified RU\/EN task controller"/); for (const section of ['## Install, configure, and run', '## What Pinmind does', '## Kernel CLI', '## Versioning', '## Limitations']) assert.match(readme, new RegExp(section)); assert.match(readme, new RegExp(`Current stable version:\\s*\`${escapedBaseVersion}\``)); - assert.match(readme, /codex plugin marketplace add iammedved\/Pinmind --ref v0\.10\.1/); + assert.match(readme, /codex plugin marketplace add iammedved\/Pinmind --ref v0\.11\.0/); assert.doesNotMatch(readme, /--ref v0\.9\.1/); assert.doesNotMatch(readme, /Current (?:source|stable) version:[^\n]*unreleased/i); assert.match(readme, /Universal Plugins Directory:\s*\*\*not listed yet\*\*/); diff --git a/tests/orchestration-hook.test.mjs b/tests/orchestration-hook.test.mjs new file mode 100644 index 0000000..ec55289 --- /dev/null +++ b/tests/orchestration-hook.test.mjs @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +import { hookOutputForPrompt, needsSkillstate } from '../hooks/pinmind-orchestrator.mjs'; + +test('Pinmind hook is the only controller and selects Skillstate only for durable work', () => { + assert.deepEqual(hookOutputForPrompt('Переведи слово hello.'), {}); + const short = hookOutputForPrompt('Проверь этот небольшой модуль и расскажи о найденных ошибках.'); + assert.match(short.hookSpecificOutput.additionalContext, /Pinmind is the sole workflow controller/); + assert.match(short.hookSpecificOutput.additionalContext, /does not need Skillstate/); + const long = hookOutputForPrompt('Проведи аудит репозитория, исправь корень проблемы, добавь тесты и проверь итог во всех затронутых модулях.'); + assert.match(long.hookSpecificOutput.additionalContext, /\$skillstate-runtime/); + assert.match(long.hookSpecificOutput.additionalContext, /passive `skillstate checkpoint`/); + assert.doesNotMatch(long.hookSpecificOutput.additionalContext, /automatically use.*skillstate run/i); + assert.match(long.hookSpecificOutput.additionalContext, /Ponytail may simplify code/); +}); + +test('Skillstate admission preserves confirmation and route boundaries', () => { + assert.equal(needsSkillstate('long '.repeat(200), { route: 'software-change', executionSpan: 'multi-system', risk: 'high', needsHumanConfirmation: true }), false); + assert.equal(needsSkillstate('long '.repeat(200), { route: 'simple', executionSpan: 'multi-system', risk: 'high', needsHumanConfirmation: false }), false); + assert.equal(needsSkillstate('аудит '.repeat(200), { route: 'audit', executionSpan: 'cross-cutting', risk: 'high', needsHumanConfirmation: false }), false); + assert.equal(needsSkillstate('исследование '.repeat(200), { route: 'investigation', executionSpan: 'cross-cutting', risk: 'high', needsHumanConfirmation: false }), false); + assert.equal(needsSkillstate('Проверь, исправь и протестируй.', { route: 'software-change', executionSpan: 'local', risk: 'medium', needsHumanConfirmation: false }), true); +}); + +test('plugin registers one bounded Pinmind orchestration hook', async () => { + const hooks = JSON.parse(await readFile(new URL('../hooks/hooks.json', import.meta.url), 'utf8')); + const registration = hooks.hooks.UserPromptSubmit[0].hooks[0]; + assert.equal(registration.type, 'command'); + assert.match(registration.command, /pinmind-orchestrator\.mjs/); + assert.ok(registration.timeout > 0 && registration.timeout <= 5); +}); diff --git a/tests/release-verification.test.mjs b/tests/release-verification.test.mjs index 8fc1441..e1c18ec 100644 --- a/tests/release-verification.test.mjs +++ b/tests/release-verification.test.mjs @@ -27,12 +27,12 @@ test('canonical release manifest, workflow, plugin, and skill metadata validate' const manifest = await loadReleaseManifest(root); const result = await validateReleaseManifest(manifest, root); assert.equal(result.ok, true); - assert.equal(result.nodeVersion, '24.19.0'); - assert.equal(result.frozenInputs, 13); + assert.equal(result.nodeVersion, '24.20.0'); + assert.equal(result.frozenInputs, 14); assert.equal(result.commands, EXPECTED_COMMANDS.length); - assert.equal(result.inventory.testFiles.length, 10); + assert.equal(result.inventory.testFiles.length, 11); assert.equal(result.inventory.fixtureCases.routes, 457); - assert.deepEqual(await validateWorkflow(root), { ok: true, nodeVersion: '24.19.0' }); + assert.deepEqual(await validateWorkflow(root), { ok: true, nodeVersion: '24.20.0' }); const plugin = await validatePluginAndSkills(root); assert.equal(plugin.ok, true); assert.deepEqual(plugin.skills, ['pinmind']); });