nexakit 0.0.0-stage → 0.2.0
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/.claude-plugin/marketplace.json +28 -0
- package/LICENSE +26 -0
- package/README.md +79 -2
- package/agents/claude/docs-researcher.md +9 -0
- package/agents/claude/implementation-tutor.md +9 -0
- package/agents/claude/project-explorer.md +9 -0
- package/agents/claude/quality-reviewer.md +9 -0
- package/agents/claude/requirements-analyst.md +9 -0
- package/agents/claude/solution-architect.md +9 -0
- package/agents/codex/docs-researcher.toml +10 -0
- package/agents/codex/implementation-tutor.toml +10 -0
- package/agents/codex/project-explorer.toml +10 -0
- package/agents/codex/quality-reviewer.toml +10 -0
- package/agents/codex/requirements-analyst.toml +11 -0
- package/agents/codex/solution-architect.toml +10 -0
- package/assets/brand/README.md +38 -0
- package/assets/brand/agent-toolkit-mark.png +0 -0
- package/assets/brand/agent-toolkit-mark.svg +13 -0
- package/assets/brand/agent-toolkit-tui-hero.png +0 -0
- package/assets/brand/nexakit-readme-hero.png +0 -0
- package/assets/brand/nexakit-readme-hero.svg +21 -0
- package/assets/runtime/package-lock.json +1425 -0
- package/bin/agent-task.js +14 -0
- package/bin/agent-toolkit-gemini.js +13 -0
- package/bin/agent-toolkit-inbox.js +14 -0
- package/bin/agent-toolkit-mcp.js +17 -0
- package/bin/agent-toolkit.js +13 -0
- package/bin/nexakit.js +13 -0
- package/bin/project-memory-mcp.js +22 -0
- package/bin/project-memory.js +7 -0
- package/catalogs/skills/api-design-principles/LICENSE.txt +21 -0
- package/catalogs/skills/api-design-principles/SKILL.md +110 -0
- package/catalogs/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/catalogs/skills/api-design-principles/assets/rest-api-template.py +185 -0
- package/catalogs/skills/api-design-principles/references/details.md +413 -0
- package/catalogs/skills/api-design-principles/references/graphql-schema-design.md +589 -0
- package/catalogs/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/catalogs/skills/nodejs-backend-patterns/LICENSE.txt +21 -0
- package/catalogs/skills/nodejs-backend-patterns/SKILL.md +45 -0
- package/catalogs/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/catalogs/skills/nodejs-backend-patterns/references/details.md +599 -0
- package/catalogs/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
- package/catalogs/skills/supabase-postgres-best-practices/LICENSE.txt +21 -0
- package/catalogs/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/_template.md +34 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/catalogs/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
- package/catalogs/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/catalogs/skills/vercel-composition-patterns/LICENSE.txt +27 -0
- package/catalogs/skills/vercel-composition-patterns/README.md +60 -0
- package/catalogs/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/catalogs/skills/vercel-composition-patterns/metadata.json +11 -0
- package/catalogs/skills/vercel-composition-patterns/rules/_sections.md +29 -0
- package/catalogs/skills/vercel-composition-patterns/rules/_template.md +24 -0
- package/catalogs/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/catalogs/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/catalogs/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/catalogs/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/catalogs/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/catalogs/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/catalogs/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/catalogs/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/catalogs/skills/vercel-react-best-practices/AGENTS.md +3810 -0
- package/catalogs/skills/vercel-react-best-practices/LICENSE.txt +27 -0
- package/catalogs/skills/vercel-react-best-practices/README.md +123 -0
- package/catalogs/skills/vercel-react-best-practices/SKILL.md +149 -0
- package/catalogs/skills/vercel-react-best-practices/metadata.json +15 -0
- package/catalogs/skills/vercel-react-best-practices/rules/_sections.md +46 -0
- package/catalogs/skills/vercel-react-best-practices/rules/_template.md +28 -0
- package/catalogs/skills/vercel-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
- package/catalogs/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/catalogs/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/catalogs/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-defer-await.md +82 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/catalogs/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +60 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/catalogs/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/catalogs/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/catalogs/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/catalogs/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/catalogs/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-request-idle-callback.md +105 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/catalogs/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/catalogs/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +149 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-no-shared-module-state.md +50 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
- package/catalogs/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/catalogs/skills/webapp-testing/LICENSE.txt +202 -0
- package/catalogs/skills/webapp-testing/SKILL.md +96 -0
- package/catalogs/skills/webapp-testing/examples/console_logging.py +35 -0
- package/catalogs/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/catalogs/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/catalogs/skills/webapp-testing/scripts/with_server.py +106 -0
- package/catalogs/skills.json +1115 -0
- package/docs/skills/evidencias/2026-10-28-evaluacion-inicial-mcp-seguridad.md +118 -0
- package/docs/skills/evidencias/2026-10-28-incorporacion-mcp-seguridad.json +152 -0
- package/docs/skills/evidencias/2026-10-28-recomendador-preparacion.md +50 -0
- package/package.json +73 -4
- package/plugins/nexakit-modelo/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-modelo/hooks/hooks.json +1 -0
- package/plugins/nexakit-modelo/hooks/register.js +48 -0
- package/plugins/nexakit-modelo/hooks/status.js +8 -0
- package/plugins/nexakit-modelo/tests/register.test.ts +21 -0
- package/plugins/nexakit-panel/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-panel/README.md +20 -0
- package/plugins/nexakit-panel/hooks/hooks.json +1 -0
- package/plugins/nexakit-panel/hooks/lienzo.js +382 -0
- package/plugins/nexakit-panel/hooks/panel.js +279 -0
- package/plugins/nexakit-panel/hooks/register.tsx +248 -0
- package/plugins/nexakit-panel/tests/lienzo.test.ts +142 -0
- package/plugins/nexakit-panel/tests/panel.test.ts +240 -0
- package/plugins/nexakit-panel/tests/register.test.ts +167 -0
- package/plugins/nexakit-panel/types/index.d.ts +41 -0
- package/plugins/nexakit-sesion/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-sesion/README.md +12 -0
- package/plugins/nexakit-sesion/hooks/aviso.js +38 -0
- package/plugins/nexakit-sesion/hooks/hooks.json +1 -0
- package/plugins/nexakit-sesion/hooks/register.js +28 -0
- package/plugins/nexakit-sesion/tests/register.test.ts +96 -0
- package/plugins/niveles/.claude-plugin/plugin.json +5 -0
- package/plugins/niveles/agents/nivel-alto.md +13 -0
- package/plugins/niveles/agents/nivel-exigente.md +13 -0
- package/plugins/niveles/agents/nivel-ligero.md +13 -0
- package/plugins/niveles/agents/nivel-normal.md +13 -0
- package/schemas/agent-client-adapter.schema.json +786 -0
- package/schemas/agent-client-auth-diagnostic.schema.json +801 -0
- package/schemas/agent-client-isolated-variant.schema.json +105 -0
- package/schemas/agent-client-isolated-verification.schema.json +99 -0
- package/schemas/agent-client-managed-preferences-invocation.schema.json +67 -0
- package/schemas/agent-client-managed-preferences.schema.json +63 -0
- package/schemas/agent-client-native-diagnostics.schema.json +85 -0
- package/schemas/agent-client-native-evaluation.schema.json +171 -0
- package/schemas/agent-client-native-preflight.schema.json +90 -0
- package/schemas/agent-client-output.schema.json +551 -0
- package/schemas/agent-client-runtime-fixture.schema.json +90 -0
- package/schemas/agent-client-runtime-local.schema.json +95 -0
- package/schemas/agent-client-runtime.schema.json +1288 -0
- package/schemas/agent-client-selective-config.schema.json +77 -0
- package/schemas/agent-client-supervised-local.schema.json +135 -0
- package/schemas/agent-client-supervised.schema.json +186 -0
- package/schemas/agent-model-preference.schema.json +573 -0
- package/schemas/agent-model-selection.schema.json +311 -0
- package/schemas/agent-task-approvals.schema.json +362 -0
- package/schemas/agent-task-prepare.schema.json +1324 -0
- package/schemas/agent-task-verify.schema.json +198 -0
- package/schemas/project-memory-update.schema.json +33 -0
- package/schemas/project-memory.schema.json +298 -0
- package/schemas/skill-catalog.schema.json +246 -0
- package/schemas/task-workflow-plan-output.schema.json +35 -0
- package/scripts/prepare-package.js +6 -0
- package/scripts/validate-skill-catalog.js +41 -0
- package/skills/cerrar-avisens/SKILL.md +12 -0
- package/skills/cerrar-memoria/SKILL.md +96 -0
- package/skills/cerrar-memoria/references/cierre.md +76 -0
- package/skills/decision-records/SKILL.md +27 -0
- package/skills/decision-records/agents/openai.yaml +7 -0
- package/skills/decision-records/references/adr-template.md +35 -0
- package/skills/differential-review/LICENSE.txt +427 -0
- package/skills/differential-review/SKILL.md +225 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/agents/openai.yaml +6 -0
- package/skills/differential-review/assets/trail-of-bits-mark.svg +1 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/elegir-modelo/SKILL.md +68 -0
- package/skills/elegir-modelo/modelos.mjs +108 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +71 -0
- package/skills/gemini/SKILL.md +52 -0
- package/skills/imagen/SKILL.md +12 -0
- package/skills/investigar/SKILL.md +11 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +236 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/ordenar-proyecto/SKILL.md +56 -0
- package/skills/project-handoff/SKILL.md +53 -0
- package/skills/project-handoff/agents/openai.yaml +7 -0
- package/skills/project-handoff/references/handoff-template.md +35 -0
- package/skills/quality-gate/LICENSE.txt +26 -0
- package/skills/quality-gate/SKILL.md +44 -0
- package/skills/quality-gate/agents/openai.yaml +7 -0
- package/skills/quality-gate/references/review-checklist.md +42 -0
- package/skills/revisar/SKILL.md +11 -0
- package/skills/revisar-skill/LICENSE-find-skills.txt +26 -0
- package/skills/revisar-skill/SKILL.md +94 -0
- package/skills/revision-cruzada/SKILL.md +59 -0
- package/skills/web-design-guidelines/AGENTS.md +156 -0
- package/skills/web-design-guidelines/LICENSE.txt +21 -0
- package/skills/web-design-guidelines/SKILL.md +29 -0
- package/src/agent-models.js +131 -0
- package/src/brand.js +117 -0
- package/src/claude-code-runner.js +642 -0
- package/src/cli-executable.js +88 -0
- package/src/cli.js +778 -0
- package/src/client-adapter-plan.js +271 -0
- package/src/client-auth-diagnostic-parser.js +211 -0
- package/src/client-auth-diagnostic.js +257 -0
- package/src/client-isolated-variant.js +106 -0
- package/src/client-isolated-verification.js +130 -0
- package/src/client-local-platform.js +14 -0
- package/src/client-managed-preferences-invocation.js +209 -0
- package/src/client-managed-preferences.js +78 -0
- package/src/client-native-access.js +134 -0
- package/src/client-native-diagnostics.js +253 -0
- package/src/client-native-evaluation.js +106 -0
- package/src/client-native-preflight-files.js +68 -0
- package/src/client-native-preflight.js +95 -0
- package/src/client-output-json.js +118 -0
- package/src/client-output-parser.js +278 -0
- package/src/client-runtime-admission.js +80 -0
- package/src/client-runtime-coordinator.js +70 -0
- package/src/client-runtime-core.js +92 -0
- package/src/client-runtime-fixture-permit.js +20 -0
- package/src/client-runtime-fixture-protocol.js +39 -0
- package/src/client-runtime-state.js +67 -0
- package/src/client-runtime-store-files.js +91 -0
- package/src/client-runtime-store.js +239 -0
- package/src/client-runtime-supervisor.js +146 -0
- package/src/client-selective-config.js +195 -0
- package/src/client-selective-files.js +126 -0
- package/src/client-supervised-codes.js +32 -0
- package/src/client-supervised-contract.js +52 -0
- package/src/client-supervised-coordinator.js +36 -0
- package/src/client-supervised-permit.js +20 -0
- package/src/client-supervised-protocol.js +37 -0
- package/src/client-supervised-store.js +167 -0
- package/src/client-supervised-supervisor.js +167 -0
- package/src/client-supervised.js +160 -0
- package/src/codex-cli-runner.js +358 -0
- package/src/codex-delivery.js +101 -0
- package/src/connect.js +703 -0
- package/src/doctor.js +795 -0
- package/src/gemini-cli.js +76 -0
- package/src/gemini-files.js +45 -0
- package/src/gemini-tools.js +249 -0
- package/src/init.js +473 -0
- package/src/install-lock.js +127 -0
- package/src/install-registry.js +61 -0
- package/src/legacy-repair.js +71 -0
- package/src/level-agents.js +111 -0
- package/src/link-states.js +172 -0
- package/src/memory-connection-artifacts.js +192 -0
- package/src/memory-connection-descriptor.js +158 -0
- package/src/memory-connection-diagnostic.js +177 -0
- package/src/memory-connection-files.js +191 -0
- package/src/memory-connection-manifest.js +118 -0
- package/src/memory-connection-package.js +176 -0
- package/src/memory-connection-plan.js +211 -0
- package/src/memory-connection-skill.js +211 -0
- package/src/memory-connection-toml.js +153 -0
- package/src/memory-locator.js +118 -0
- package/src/memory-mcp-policy.js +181 -0
- package/src/memory-mcp-server.js +166 -0
- package/src/memory-mcp-transport.js +59 -0
- package/src/missing-dependencies.js +21 -0
- package/src/model-command.js +147 -0
- package/src/model-preference.js +235 -0
- package/src/model-selection.js +368 -0
- package/src/native/managed-preferences.c +100 -0
- package/src/native/managed-preferences.h +17 -0
- package/src/package-lock-path.js +13 -0
- package/src/panel-cli.js +182 -0
- package/src/peer-handoff.js +27 -0
- package/src/peer-inbox-cli.js +211 -0
- package/src/peer-review-mcp.js +210 -0
- package/src/peer-review-store.js +532 -0
- package/src/platform.js +24 -0
- package/src/project-root.js +41 -0
- package/src/project-signals.js +339 -0
- package/src/recommend-report.js +77 -0
- package/src/review-instructions.js +211 -0
- package/src/review-policy.js +35 -0
- package/src/revision-adapters.js +157 -0
- package/src/revision-context.js +86 -0
- package/src/revision-isolation.js +222 -0
- package/src/revision-process.js +103 -0
- package/src/revision-provenance.js +89 -0
- package/src/revision-snapshot.js +120 -0
- package/src/revision.js +242 -0
- package/src/runtime-fixtures/runtime-supervisor/child.cjs +3 -0
- package/src/runtime-fixtures/runtime-supervisor/fixture.cjs +49 -0
- package/src/runtime-fixtures/supervised/child.cjs +3 -0
- package/src/runtime-fixtures/supervised/fixture.cjs +47 -0
- package/src/sensitive-data.js +21 -0
- package/src/setup-apply.js +231 -0
- package/src/setup-plan.js +231 -0
- package/src/setup-state.js +277 -0
- package/src/setup-tui.js +555 -0
- package/src/skill-catalog-add.js +91 -0
- package/src/skill-catalog-audit.js +68 -0
- package/src/skill-catalog.js +170 -0
- package/src/skill-files.js +39 -0
- package/src/skill-recommendations.js +1673 -0
- package/src/skill-scan-report.js +14 -0
- package/src/skill-scan.js +178 -0
- package/src/stable-command.js +53 -0
- package/src/stable-install.js +222 -0
- package/src/status.js +200 -0
- package/src/superpowers.js +133 -0
- package/src/sync-apply.js +227 -0
- package/src/sync-files.js +109 -0
- package/src/sync-inspect.js +394 -0
- package/src/sync-ledger.js +94 -0
- package/src/sync-markdown.js +151 -0
- package/src/sync-proposal.js +84 -0
- package/src/sync-record.js +459 -0
- package/src/sync.js +360 -0
- package/src/task-approvals.js +367 -0
- package/src/task-cli.js +79 -0
- package/src/task-external-verifier.js +584 -0
- package/src/task-prepare-files.js +115 -0
- package/src/task-prepare.js +180 -0
- package/src/task-verify-cli.js +67 -0
- package/src/task-verify-files.js +188 -0
- package/src/task-verify.js +118 -0
- package/src/task-workflow-mcp.js +544 -0
- package/src/task-workflow-store.js +254 -0
- package/src/terminal-text.js +68 -0
- package/src/toolkit-cli.js +543 -0
- package/src/toolkit-commands-guide.js +107 -0
- package/src/toolkit-setup.js +614 -0
- package/src/usage-metrics.js +43 -0
- package/templates/agent-team/01-encargo.md +158 -0
- package/templates/agent-team/02-propuesta.md +132 -0
- package/templates/agent-team/03-revision.md +150 -0
- package/templates/agent-team/README.md +213 -0
- package/templates/model-inventory/01-inventario.md +154 -0
- package/templates/model-inventory/README.md +115 -0
- package/templates/project-memory/README.md +67 -0
- package/templates/project-memory/project-memory.example.json +53 -0
- package/templates/project-memory/skill/project-id/SKILL.md +37 -0
- package/templates/project-memory/skill/project-id/references/project-profile.md +44 -0
- package/templates/project-memory/vault/00-INICIO/Estado actual.md +44 -0
- package/templates/project-memory/vault/00-INICIO/Fuentes de verdad.md +30 -0
- package/templates/project-memory/vault/00-INICIO/Inicio del proyecto.md +44 -0
- package/templates/project-memory/vault/00-INICIO/Mantenimiento de la memoria.md +26 -0
- package/templates/project-memory/vault/00-INICIO/Mapa de la memoria.md +33 -0
- package/templates/project-memory/vault/00-INICIO/Registro de sincronizaci/303/263n.md" +28 -0
- package/templates/project-memory/vault/01-PRODUCTO/Producto.md +16 -0
- package/templates/project-memory/vault/02-ARQUITECTURA/Arquitectura.md +16 -0
- package/templates/project-memory/vault/03-IMPLEMENTACION/Implementaci/303/263n.md +15 -0
- package/templates/project-memory/vault/04-DECISIONES/Decisiones.md +19 -0
- package/templates/project-memory/vault/05-ENTREGAS/Entregas.md +12 -0
- package/templates/project-memory/vault/06-OPERACION/Operaci/303/263n.md +15 -0
- package/templates/project-memory/vault/07-AGENTES/Protocolo de agentes.md +33 -0
- package/templates/project-memory/vault/08-RECURSOS/Recursos.md +12 -0
- package/templates/project-memory/vault/09-ARCHIVO/README.md +5 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { acquireLock } from './install-lock.js';
|
|
5
|
+
import { recordEntries } from './install-registry.js';
|
|
6
|
+
import { relinkIfUnchanged } from './link-states.js';
|
|
7
|
+
import { actualRoot, ensureStableInstall, nexakitDir } from './stable-install.js';
|
|
8
|
+
import { TOOLKIT_ROOT, packageVersion, registrationCommand, resolveNodeExecutable, setupToolkit } from './toolkit-setup.js';
|
|
9
|
+
|
|
10
|
+
const samePath = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
11
|
+
const exists = file => fs.access(file).then(() => true, () => false);
|
|
12
|
+
|
|
13
|
+
// Finds the global links that point into a root that no longer exists, `from`, and that nexakit cannot prove
|
|
14
|
+
// are its own (no entry in the install registry). Only skill names this version ships are considered.
|
|
15
|
+
// Nothing is written here: the person has to confirm what this lists.
|
|
16
|
+
export async function planLegacyRepair({
|
|
17
|
+
from, agent = 'all', homeDirectory = os.homedir(), toolkitRoot = TOOLKIT_ROOT, environment = process.env, codexHome
|
|
18
|
+
} = {}) {
|
|
19
|
+
const dead = path.resolve(from);
|
|
20
|
+
const setup = await setupToolkit({ agent, homeDirectory, toolkitRoot, environment, ...(codexHome && { codexHome }) });
|
|
21
|
+
const links = setup.skills
|
|
22
|
+
.filter(skill => skill.state === 'roto-ajeno' && skill.root && samePath(skill.root, dead))
|
|
23
|
+
.map(({ client, name, target, literal }) => ({ client, name, target, literal }))
|
|
24
|
+
.sort((a, b) => a.client.localeCompare(b.client) || a.name.localeCompare(b.name));
|
|
25
|
+
const mcp = {};
|
|
26
|
+
for (const [client, state] of Object.entries(setup.mcp.states)) {
|
|
27
|
+
if (state.state === 'roto-ajeno' && state.root && samePath(state.root, dead)) mcp[client] = { name: state.name, literal: state.literal };
|
|
28
|
+
}
|
|
29
|
+
return { from: dead, agent, activeRoot: setup.activeRoot, ephemeral: setup.ephemeral && !setup.otherCopy, links, mcp };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Repoints the links of the plan to the copy of nexakit in use and records them as nexakit's own.
|
|
33
|
+
// Each link is read again right before writing: one that changed since the plan is left alone.
|
|
34
|
+
export async function applyLegacyRepair(plan, {
|
|
35
|
+
homeDirectory = os.homedir(), toolkitRoot = TOOLKIT_ROOT, environment = process.env, runNpm, packageSpec, lockOptions,
|
|
36
|
+
acquire = acquireLock
|
|
37
|
+
} = {}) {
|
|
38
|
+
const home = await fs.realpath(homeDirectory);
|
|
39
|
+
const lock = await acquire(nexakitDir(home), lockOptions);
|
|
40
|
+
try {
|
|
41
|
+
if (plan.ephemeral && plan.links.length) {
|
|
42
|
+
await ensureStableInstall({
|
|
43
|
+
homeDirectory: home, version: await packageVersion(await fs.realpath(toolkitRoot)), lock,
|
|
44
|
+
...(runNpm && { runNpm }), ...(packageSpec && { packageSpec })
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const entries = [], skipped = [];
|
|
48
|
+
let changed = 0;
|
|
49
|
+
for (const link of plan.links) {
|
|
50
|
+
const source = path.join(plan.activeRoot, 'skills', link.name);
|
|
51
|
+
if (!(await exists(path.join(source, 'SKILL.md')))) { skipped.push(link.name); continue; }
|
|
52
|
+
await lock.assertHeld();
|
|
53
|
+
if (await relinkIfUnchanged({ target: link.target, expectedLiteral: link.literal, source }) === 'cambio') { changed++; continue; }
|
|
54
|
+
entries.push({ destino: link.target, apunta: await fs.readlink(link.target), raiz: plan.activeRoot });
|
|
55
|
+
}
|
|
56
|
+
await recordEntries(nexakitDir(home), entries, lock);
|
|
57
|
+
|
|
58
|
+
// The MCP entry is not rewritten here: its client owns that file, so only the exact commands are given.
|
|
59
|
+
const mcpCommands = [];
|
|
60
|
+
const script = path.join(plan.activeRoot, 'bin/agent-toolkit-mcp.js');
|
|
61
|
+
if (Object.keys(plan.mcp ?? {}).length && await exists(script)) {
|
|
62
|
+
const nodePath = await resolveNodeExecutable(environment);
|
|
63
|
+
for (const [client, current] of Object.entries(plan.mcp)) {
|
|
64
|
+
mcpCommands.push(registrationCommand(client, { state: 'roto-ajeno', name: current.name }, nodePath, script));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { repaired: entries.length, changed, skipped, mcpCommands, stableRoot: actualRoot(home) };
|
|
68
|
+
} finally {
|
|
69
|
+
await lock.release();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { agentModels } from "./agent-models.js";
|
|
6
|
+
|
|
7
|
+
// Codex has no plugins, so its level agents are files. Claude's come from the `niveles` plugin.
|
|
8
|
+
// The top level is "alto"; xhigh is asked per spawn, only after high failed.
|
|
9
|
+
const LEVELS = [["Ligero", "ligero"], ["Normal", "normal"], ["Exigente", "exigente"], ["Alto", "alto"]];
|
|
10
|
+
const MODEL = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,79}$/u;
|
|
11
|
+
|
|
12
|
+
export class LevelAgentsError extends Error {
|
|
13
|
+
constructor(message) { super(message); this.code = "level-agents"; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function content(slug, level, { model, effort }) {
|
|
17
|
+
const task = `nivel ${level} (${model}, ${effort})`;
|
|
18
|
+
return [`# Generado por \`agent-toolkit-inbox agentes\`. Ese comando nunca sobrescribe ni borra: lo anterior queda como .anterior-*.`,
|
|
19
|
+
`name = ${JSON.stringify(`nivel_${slug}`)}`,
|
|
20
|
+
`description = ${JSON.stringify(`Ejecuta una tarea delegada de ${task}. Úsalo solo cuando la skill revision-cruzada indique este nivel.`)}`,
|
|
21
|
+
`model = ${JSON.stringify(model)}`, `model_reasoning_effort = ${JSON.stringify(effort)}`,
|
|
22
|
+
`developer_instructions = """`,
|
|
23
|
+
`Ejecutas una tarea que te delega la sesión principal de la revisión cruzada, con el ${task}.`,
|
|
24
|
+
"Haz solo lo que pide la delegación y respeta las reglas del proyecto (AGENTS.md y sus skills).",
|
|
25
|
+
"Si la tarea es de código: cambio mínimo, pruebas que lo cubran, y ejecútalas junto con el lint o la compilación que correspondan.",
|
|
26
|
+
"No hagas commit, push ni PR, y no envíes, apruebes ni reclames encargos de revisión.",
|
|
27
|
+
"Termina con los cambios por archivo, las comprobaciones ejecutadas con su resultado real y lo que no comprobaste.",
|
|
28
|
+
`"""`, ""].join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function current(file) {
|
|
32
|
+
try {
|
|
33
|
+
const stat = await fs.lstat(file);
|
|
34
|
+
// A link or a directory is never read through or replaced silently: it counts as different.
|
|
35
|
+
return stat.isFile() ? await fs.readFile(file, "utf8") : false;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === "ENOENT") return null;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Plans the Codex level agents from the live catalog; writes nothing.
|
|
43
|
+
export async function levelAgentPlan({ codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex") } = {}) {
|
|
44
|
+
const { codex, levels } = await agentModels({ codexHome });
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const [level, slug] of LEVELS) {
|
|
47
|
+
const target = path.join(codexHome, "agents", `nivel_${slug}.toml`);
|
|
48
|
+
const pick = levels.find(entry => entry.level === level)?.codex;
|
|
49
|
+
const base = { level: slug, name: `nivel_${slug}`, path: target, model: pick?.model ?? null, effort: pick?.effort ?? null };
|
|
50
|
+
if (!codex.available) { files.push({ ...base, state: "sin-catalogo" }); continue; }
|
|
51
|
+
if (!pick || !MODEL.test(pick.model)) { files.push({ ...base, state: "sin-modelo" }); continue; }
|
|
52
|
+
const wanted = content(slug, level, pick);
|
|
53
|
+
const existing = await current(target);
|
|
54
|
+
files.push({ ...base, content: wanted, state: existing === null ? "nuevo" : existing === wanted ? "igual" : "distinto" });
|
|
55
|
+
}
|
|
56
|
+
return { catalog: codex.available, files };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function directory(dir) {
|
|
60
|
+
await fs.mkdir(dir, { recursive: true });
|
|
61
|
+
const stat = await fs.lstat(dir);
|
|
62
|
+
if (!stat.isDirectory()) throw new LevelAgentsError(`${dir} debe ser un directorio y no un enlace; no se escribió nada en él.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Publishing with link() fails instead of replacing whatever appeared at the target.
|
|
66
|
+
async function publish(file) {
|
|
67
|
+
const temporary = path.join(path.dirname(file.path), `.nivel-${randomUUID()}.tmp`);
|
|
68
|
+
try {
|
|
69
|
+
await fs.writeFile(temporary, file.content, { flag: "wx", mode: 0o644 });
|
|
70
|
+
await fs.link(temporary, file.path);
|
|
71
|
+
return true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error.code === "EEXIST") return false;
|
|
74
|
+
throw error;
|
|
75
|
+
} finally { await fs.unlink(temporary).catch(() => {}); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Never overwrites or deletes: a different file is kept under a unique `.anterior-*` name, and only with `force`.
|
|
79
|
+
export async function writeLevelAgents(plan, { force = false } = {}) {
|
|
80
|
+
for (const file of plan.files) {
|
|
81
|
+
if (file.state !== "nuevo" && !(force && file.state === "distinto")) continue;
|
|
82
|
+
await directory(path.dirname(file.path));
|
|
83
|
+
if (file.state === "distinto") {
|
|
84
|
+
file.anterior = `${file.path}.anterior-${new Date().toISOString().replace(/\D/gu, "").slice(0, 14)}-${randomUUID().slice(0, 8)}`;
|
|
85
|
+
try { await fs.rename(file.path, file.anterior); }
|
|
86
|
+
catch (error) { if (error.code !== "ENOENT") throw error; file.anterior = null; }
|
|
87
|
+
}
|
|
88
|
+
if (await publish(file)) file.state = file.state === "nuevo" ? "escrito" : "actualizado";
|
|
89
|
+
else file.state = "distinto";
|
|
90
|
+
}
|
|
91
|
+
return plan;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const LABEL = { nuevo: "se crea", escrito: "creado", igual: "sin cambios", distinto: "ya existe y es distinto: no se toca",
|
|
95
|
+
actualizado: "actualizado", "sin-modelo": "sin modelo de esa categoría", "sin-catalogo": "catálogo no disponible" };
|
|
96
|
+
|
|
97
|
+
export function levelAgentsTable({ catalog, files }) {
|
|
98
|
+
const lines = ["**Agentes por nivel de Codex**", "", "| Nivel | Nombre | Modelo | Esfuerzo | Estado |", "| --- | --- | --- | --- | --- |"];
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
lines.push(`| ${file.level} | \`${file.name}\` | ${file.model ? `\`${file.model}\`` : "—"} | ${file.effort ? `\`${file.effort}\`` : "—"} | ${LABEL[file.state]} |`);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
if (!catalog) lines.push("No se pudo leer el catálogo de Codex (`models_cache.json`); no se escribe nada hasta que se lea bien.");
|
|
104
|
+
const pending = files.filter(file => file.state === "nuevo").length, different = files.filter(file => file.state === "distinto").length;
|
|
105
|
+
if (pending) lines.push(`Vista previa: no se escribió nada. Ejecuta \`agent-toolkit-inbox agentes --escribir\` para crear ${pending}.`);
|
|
106
|
+
if (different) lines.push(`Hay ${different} ${different === 1 ? "archivo" : "archivos"} con otro contenido que no se ${different === 1 ? "toca" : "tocan"}. Con \`--escribir --forzar\` se actualizan y el anterior queda como \`.anterior-*\` junto al archivo.`);
|
|
107
|
+
for (const file of files.filter(item => item.anterior)) lines.push(`Anterior conservado: \`${file.anterior}\`.`);
|
|
108
|
+
if (!pending && !different && catalog) lines.push("Los agentes están al día.");
|
|
109
|
+
lines.push("");
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { IS_WINDOWS } from './platform.js';
|
|
5
|
+
|
|
6
|
+
// Evaluated in this order; the first one that holds wins.
|
|
7
|
+
export const LINK_STATES = Object.freeze(['falta', 'conflicto', 'efimero', 'instalado', 'otra-copia', 'roto-propio', 'roto-ajeno', 'quitada']);
|
|
8
|
+
// What an apply acts on, and what counts as ready.
|
|
9
|
+
export const ACTIONABLE_LINK_STATES = Object.freeze(['falta', 'efimero', 'roto-propio']);
|
|
10
|
+
export const READY_LINK_STATES = Object.freeze(['instalado', 'otra-copia']);
|
|
11
|
+
// `quitada`: nexakit instaló el enlace y el usuario lo quitó. No es pendiente: se respeta hasta que pida `--reponer`.
|
|
12
|
+
export const REMOVED_BY_USER_STATE = 'quitada';
|
|
13
|
+
export const SETTLED_LINK_STATES = Object.freeze([...READY_LINK_STATES, REMOVED_BY_USER_STATE]);
|
|
14
|
+
|
|
15
|
+
const PACKAGE_NAMES = new Set(['nexakit', 'agent-toolkit']);
|
|
16
|
+
const MAX_PACKAGE_BYTES = 1_048_576;
|
|
17
|
+
const BROKEN_CODES = new Set(['ENOENT', 'ENOTDIR', 'ELOOP']);
|
|
18
|
+
|
|
19
|
+
const normalized = value => (IS_WINDOWS ? value.toLowerCase() : value);
|
|
20
|
+
const samePath = (a, b) => normalized(a) === normalized(b);
|
|
21
|
+
const lstatOrNull = file => fs.lstat(file).catch(error => {
|
|
22
|
+
if (error.code === 'ENOENT') return null;
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const isEphemeralPath = value => path.resolve(value).split(path.sep).includes('_npx');
|
|
27
|
+
|
|
28
|
+
// A copy of nexakit inside an npx cache: `…/_npx/<hash>/node_modules/nexakit` (or the old name, agent-toolkit).
|
|
29
|
+
// Anything else under `_npx` belongs to another package and is never treated as nexakit's.
|
|
30
|
+
export function isEphemeralNexakitRoot(root) {
|
|
31
|
+
const parts = path.resolve(root).split(path.sep);
|
|
32
|
+
const n = parts.length;
|
|
33
|
+
return n >= 4 && parts[n - 4] === '_npx' && parts[n - 2] === 'node_modules' && PACKAGE_NAMES.has(parts[n - 1]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function readLink(target) {
|
|
37
|
+
const info = await lstatOrNull(target);
|
|
38
|
+
if (!info) return { exists: false, isLink: false };
|
|
39
|
+
if (!info.isSymbolicLink()) return { exists: true, isLink: false };
|
|
40
|
+
const literal = await fs.readlink(target);
|
|
41
|
+
return { exists: true, isLink: true, literal, absolute: path.resolve(path.dirname(target), literal) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Strips `suffix` from the end of `value`; null when it does not end with it.
|
|
45
|
+
function rootFrom(value, suffix) {
|
|
46
|
+
const tail = path.normalize(suffix);
|
|
47
|
+
const resolved = path.resolve(value);
|
|
48
|
+
if (!normalized(resolved).endsWith(normalized(path.sep + tail))) return null;
|
|
49
|
+
return resolved.slice(0, resolved.length - tail.length - 1) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function recognizedRoot(resolvedPath, suffix) {
|
|
53
|
+
const root = rootFrom(resolvedPath, suffix);
|
|
54
|
+
if (!root) return null;
|
|
55
|
+
const file = path.join(root, 'package.json');
|
|
56
|
+
const info = await lstatOrNull(file);
|
|
57
|
+
if (!info?.isFile() || info.isSymbolicLink() || info.size > MAX_PACKAGE_BYTES) return null;
|
|
58
|
+
try {
|
|
59
|
+
const pkg = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
60
|
+
return PACKAGE_NAMES.has(pkg?.name) ? root : null;
|
|
61
|
+
} catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function realpathOrNull(file) {
|
|
65
|
+
try { return await fs.realpath(file); } catch (error) {
|
|
66
|
+
if (BROKEN_CODES.has(error.code)) return null;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function classifyLink({ target, suffix, activeRoots = [], isOwned = () => false }) {
|
|
72
|
+
const link = await readLink(target);
|
|
73
|
+
if (!link.exists) return { target, suffix, state: 'falta' };
|
|
74
|
+
if (!link.isLink) return { target, suffix, state: 'conflicto' };
|
|
75
|
+
return classifyReference({ target, literal: link.literal, absolute: link.absolute, suffix, activeRoots, isOwned });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Classifies a path that points into a copy of nexakit without being a link itself, such as the script
|
|
79
|
+
// an MCP entry runs. `target` identifies the reference for `isOwned`; `absolute` defaults to the literal.
|
|
80
|
+
export async function classifyReference({ target, literal, absolute = path.resolve(literal), suffix, activeRoots = [], isOwned = () => false }) {
|
|
81
|
+
const base = { target, suffix };
|
|
82
|
+
const resolved = await realpathOrNull(absolute);
|
|
83
|
+
|
|
84
|
+
// Before `instalado`, so a link into the cache this run came from is never taken as good. It must point at
|
|
85
|
+
// exactly `<copy of nexakit in an npx cache>/<suffix>`; another package there is not nexakit's to move.
|
|
86
|
+
const ephemeralRoot = [absolute, resolved].filter(Boolean)
|
|
87
|
+
.map(candidate => rootFrom(candidate, suffix)).find(root => root && isEphemeralNexakitRoot(root));
|
|
88
|
+
if (ephemeralRoot) return { ...base, state: 'efimero', literal, root: ephemeralRoot };
|
|
89
|
+
|
|
90
|
+
if (resolved) {
|
|
91
|
+
const tail = path.normalize(suffix);
|
|
92
|
+
for (const candidate of activeRoots) {
|
|
93
|
+
if (isEphemeralPath(candidate)) continue;
|
|
94
|
+
const real = await realpathOrNull(candidate);
|
|
95
|
+
if (real && samePath(resolved, path.join(real, tail))) return { ...base, state: 'instalado', literal, root: real };
|
|
96
|
+
}
|
|
97
|
+
const other = await recognizedRoot(resolved, suffix);
|
|
98
|
+
if (other) return { ...base, state: 'otra-copia', literal, root: other };
|
|
99
|
+
return { ...base, state: 'conflicto', literal };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const owned = await isOwned(target, literal);
|
|
103
|
+
return { ...base, state: owned ? 'roto-propio' : 'roto-ajeno', literal, root: rootFrom(absolute, suffix) ?? undefined };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function chooseActiveRoot(items, fallbackRoot) {
|
|
107
|
+
const counts = new Map();
|
|
108
|
+
for (const item of items) {
|
|
109
|
+
if (item.state !== 'otra-copia' || !item.root || isEphemeralPath(item.root)) continue;
|
|
110
|
+
counts.set(item.root, (counts.get(item.root) ?? 0) + 1);
|
|
111
|
+
}
|
|
112
|
+
let best = null;
|
|
113
|
+
for (const [root, count] of counts) {
|
|
114
|
+
if (count * 2 > items.length && (!best || count > best.count)) best = { root, count };
|
|
115
|
+
}
|
|
116
|
+
return best?.root ?? fallbackRoot;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function legacyGroups(items, { skillNames, mcpDeadRoot } = {}) {
|
|
120
|
+
const groups = new Map();
|
|
121
|
+
for (const item of items) {
|
|
122
|
+
if (item.state !== 'roto-ajeno' || !item.root || !skillNames?.has(path.basename(item.target))) continue;
|
|
123
|
+
groups.set(item.root, [...(groups.get(item.root) ?? []), item.target]);
|
|
124
|
+
}
|
|
125
|
+
return [...groups]
|
|
126
|
+
.filter(([root, targets]) => targets.length >= 3 || (mcpDeadRoot !== undefined && root === mcpDeadRoot))
|
|
127
|
+
.map(([root, targets]) => ({ root, targets }));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// `expectedLiteral` is the readlink value the plan saw; null means the path must not exist yet.
|
|
131
|
+
// Re-reads right before writing and touches nothing when the link is no longer what the plan saw.
|
|
132
|
+
// `relative` writes the link relative to its own folder (Windows junctions are always absolute).
|
|
133
|
+
export async function relinkIfUnchanged({ target, expectedLiteral, source, relative = false }) {
|
|
134
|
+
const type = IS_WINDOWS ? 'junction' : 'dir';
|
|
135
|
+
const absoluteSource = path.resolve(source);
|
|
136
|
+
const linkValue = relative && !IS_WINDOWS ? path.relative(path.dirname(target), absoluteSource) : absoluteSource;
|
|
137
|
+
const info = await lstatOrNull(target);
|
|
138
|
+
|
|
139
|
+
if (expectedLiteral === null) {
|
|
140
|
+
if (info) return 'cambio';
|
|
141
|
+
try { await fs.symlink(linkValue, target, type); } catch (error) {
|
|
142
|
+
if (error.code === 'EEXIST') return 'cambio';
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
return 'hecho';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!info?.isSymbolicLink() || await fs.readlink(target) !== expectedLiteral) return 'cambio';
|
|
149
|
+
|
|
150
|
+
if (IS_WINDOWS) {
|
|
151
|
+
// A junction cannot be renamed over; this window is not atomic there.
|
|
152
|
+
await fs.unlink(target);
|
|
153
|
+
await fs.symlink(linkValue, target, type);
|
|
154
|
+
return 'hecho';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Build the new link beside the old one and rename over it, so the name is never missing.
|
|
158
|
+
const temporary = `${target}.nx-${crypto.randomUUID()}`;
|
|
159
|
+
await fs.symlink(linkValue, temporary, type);
|
|
160
|
+
try {
|
|
161
|
+
if (await fs.readlink(target) !== expectedLiteral) {
|
|
162
|
+
await fs.unlink(temporary);
|
|
163
|
+
return 'cambio';
|
|
164
|
+
}
|
|
165
|
+
await fs.rename(temporary, target);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
await fs.unlink(temporary).catch(() => {});
|
|
168
|
+
if (error.code === 'ENOENT' || error.code === 'EINVAL') return 'cambio';
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
return 'hecho';
|
|
172
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { resolvePackageLock } from './package-lock-path.js';
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createMemoryConnectionDescriptor, copyMemoryConnectionData, freezeMemoryConnectionData,
|
|
9
|
+
memoryConnectionHash, MEMORY_CONNECTION_FACTS, MEMORY_CONNECTION_VERSION
|
|
10
|
+
} from "./memory-connection-descriptor.js";
|
|
11
|
+
|
|
12
|
+
const MIB = 1048576;
|
|
13
|
+
export const MEMORY_CONNECTION_ARTIFACT_LIMITS = Object.freeze({
|
|
14
|
+
nodeBytes: 256 * MIB, smallBytes: MIB / 2, lockBytes: 2 * MIB,
|
|
15
|
+
totalBytes: 260 * MIB, chunkBytes: 65536,
|
|
16
|
+
pathChars: 4096, components: 64, reportBytes: 131072
|
|
17
|
+
});
|
|
18
|
+
export const MEMORY_CONNECTION_ARTIFACT_OPEN_FLAGS = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
19
|
+
const HASH_KEYS = ["nodeSha256", "binSha256", "manifestSha256", "packageJsonSha256", "packageLockSha256"];
|
|
20
|
+
class Rejected extends Error {
|
|
21
|
+
constructor(code, state = "blocked") { super(code); this.code = code; this.state = state; }
|
|
22
|
+
}
|
|
23
|
+
const reject = (code, state) => { throw new Rejected(code, state); };
|
|
24
|
+
const errno = (e) => e instanceof Error && typeof e.code === "string" ? e.code : "";
|
|
25
|
+
const within = (root, full) => full === root || full.startsWith(root + "/");
|
|
26
|
+
const fileSignature = (s) => JSON.stringify([s.dev, s.ino, s.uid, s.gid, s.mode, s.nlink, s.size, s.mtimeMs, s.ctimeMs]);
|
|
27
|
+
const dirSignature = (s) => JSON.stringify([s.dev, s.ino, s.uid, s.gid, s.mode]);
|
|
28
|
+
const inode = (s) => JSON.stringify([s.dev, s.ino]);
|
|
29
|
+
|
|
30
|
+
function inputData(input) {
|
|
31
|
+
try {
|
|
32
|
+
const data = copyMemoryConnectionData(input);
|
|
33
|
+
if (!data || Array.isArray(data) || Object.keys(data).length !== 2 ||
|
|
34
|
+
!Object.hasOwn(data, "descriptor") || !Object.hasOwn(data, "expectedHashes")) reject("MC_ARTIFACT_INPUT", "invalid");
|
|
35
|
+
const descriptor = createMemoryConnectionDescriptor(data.descriptor?.identity);
|
|
36
|
+
if (memoryConnectionHash(data.descriptor) !== memoryConnectionHash(descriptor)) reject("MC_ARTIFACT_INPUT", "invalid");
|
|
37
|
+
const hashes = data.expectedHashes;
|
|
38
|
+
if (!hashes || Array.isArray(hashes) || Object.keys(hashes).length !== HASH_KEYS.length ||
|
|
39
|
+
!HASH_KEYS.every(key => Object.hasOwn(hashes, key) && typeof hashes[key] === "string" && /^[a-f0-9]{64}$/.test(hashes[key]))) {
|
|
40
|
+
reject("MC_ARTIFACT_INPUT", "invalid");
|
|
41
|
+
}
|
|
42
|
+
const { toolkitRoot, vaultPath, manifestPath, nodePath } = descriptor.identity;
|
|
43
|
+
if (within(toolkitRoot, vaultPath) || within(vaultPath, toolkitRoot) ||
|
|
44
|
+
!manifestPath.startsWith(vaultPath + "/")) reject("MC_ARTIFACT_INPUT", "invalid");
|
|
45
|
+
const entries = [
|
|
46
|
+
{ key: "nodeSha256", full: nodePath, kind: "node", limit: MEMORY_CONNECTION_ARTIFACT_LIMITS.nodeBytes },
|
|
47
|
+
{ key: "binSha256", full: descriptor.server.args[0], kind: "code", limit: MEMORY_CONNECTION_ARTIFACT_LIMITS.smallBytes },
|
|
48
|
+
{ key: "manifestSha256", full: manifestPath, kind: "manifest", limit: MEMORY_CONNECTION_ARTIFACT_LIMITS.smallBytes },
|
|
49
|
+
{ key: "packageJsonSha256", full: toolkitRoot + "/package.json", kind: "code", limit: MEMORY_CONNECTION_ARTIFACT_LIMITS.smallBytes },
|
|
50
|
+
{ key: "packageLockSha256", full: toolkitRoot + "/package-lock.json", kind: "code", limit: MEMORY_CONNECTION_ARTIFACT_LIMITS.lockBytes }
|
|
51
|
+
];
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (entry.full.length > MEMORY_CONNECTION_ARTIFACT_LIMITS.pathChars ||
|
|
54
|
+
entry.full.split("/").length - 1 > MEMORY_CONNECTION_ARTIFACT_LIMITS.components ||
|
|
55
|
+
entries.some(other => other !== entry && within(entry.full, other.full))) reject("MC_ARTIFACT_INPUT", "invalid");
|
|
56
|
+
}
|
|
57
|
+
return { descriptor, hashes, entries, roots: [toolkitRoot, vaultPath] };
|
|
58
|
+
} catch { reject("MC_ARTIFACT_INPUT", "invalid"); }
|
|
59
|
+
}
|
|
60
|
+
function safeDir(s, uid, owned) {
|
|
61
|
+
if (!s.isDirectory() || s.isSymbolicLink() || !(s.mode & 0o100)) reject("MC_ARTIFACT_PARENT_UNSAFE");
|
|
62
|
+
if (owned) {
|
|
63
|
+
if (s.uid !== uid || s.mode & 0o7022) reject("MC_ARTIFACT_PARENT_UNSAFE");
|
|
64
|
+
} else {
|
|
65
|
+
const sharedSticky = s.uid === 0 && (s.mode & 0o7777) === 0o1777;
|
|
66
|
+
if (![0, uid].includes(s.uid) || !sharedSticky && s.mode & 0o7022) reject("MC_ARTIFACT_PARENT_UNSAFE");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function safeFile(s, entry, uid) {
|
|
70
|
+
if (!s.isFile() || s.isSymbolicLink() || s.nlink !== 1) reject("MC_ARTIFACT_FILE_UNSAFE");
|
|
71
|
+
const mode = s.mode & 0o7777;
|
|
72
|
+
if (entry.kind === "node") {
|
|
73
|
+
if (![0, uid].includes(s.uid) || (mode & 0o500) !== 0o500 || mode & 0o7022) reject("MC_ARTIFACT_FILE_UNSAFE");
|
|
74
|
+
} else if (s.uid !== uid || (entry.kind === "manifest" ? mode !== 0o600 : ![0o600, 0o644].includes(mode))) {
|
|
75
|
+
reject("MC_ARTIFACT_FILE_UNSAFE");
|
|
76
|
+
}
|
|
77
|
+
if (!Number.isSafeInteger(s.size) || s.size < 0 || s.size > entry.limit) reject("MC_ARTIFACT_FILE_LIMIT");
|
|
78
|
+
}
|
|
79
|
+
async function snapshot(io, entry, roots, uid) {
|
|
80
|
+
const parents = []; let current = "/";
|
|
81
|
+
const root = await io.lstat(current); safeDir(root, uid, false);
|
|
82
|
+
parents.push({ path: current, signature: dirSignature(root), inode: inode(root) });
|
|
83
|
+
const parts = entry.full.slice(1).split("/");
|
|
84
|
+
for (const part of parts.slice(0, -1)) {
|
|
85
|
+
current = path.posix.join(current, part); const s = await io.lstat(current);
|
|
86
|
+
safeDir(s, uid, roots.some(owned => within(owned, current)));
|
|
87
|
+
parents.push({ path: current, signature: dirSignature(s), inode: inode(s) });
|
|
88
|
+
}
|
|
89
|
+
const stat = await io.lstat(entry.full); safeFile(stat, entry, uid);
|
|
90
|
+
return { entry, parents, stat };
|
|
91
|
+
}
|
|
92
|
+
async function recheck(io, item, roots, uid) {
|
|
93
|
+
for (const parent of item.parents) {
|
|
94
|
+
let s; try { s = await io.lstat(parent.path); } catch { reject("MC_ARTIFACT_CHANGED"); }
|
|
95
|
+
safeDir(s, uid, roots.some(owned => within(owned, parent.path)));
|
|
96
|
+
if (dirSignature(s) !== parent.signature) reject("MC_ARTIFACT_CHANGED");
|
|
97
|
+
}
|
|
98
|
+
let named;
|
|
99
|
+
try { named = await io.lstat(item.entry.full); } catch { reject("MC_ARTIFACT_CHANGED"); }
|
|
100
|
+
safeFile(named, item.entry, uid);
|
|
101
|
+
if (fileSignature(named) !== fileSignature(item.stat)) reject("MC_ARTIFACT_CHANGED");
|
|
102
|
+
}
|
|
103
|
+
async function recheckAll(io, items, roots, uid) {
|
|
104
|
+
for (const item of items) await recheck(io, item, roots, uid);
|
|
105
|
+
}
|
|
106
|
+
function rejectAliases(items, roots) {
|
|
107
|
+
const files = items.map(item => inode(item.stat));
|
|
108
|
+
if (new Set(files).size !== files.length) reject("MC_ARTIFACT_ALIAS");
|
|
109
|
+
const directories = roots.map(root => items.flatMap(item => item.parents).find(p => p.path === root)?.inode);
|
|
110
|
+
if (directories.some(value => value === undefined) || new Set(directories).size !== directories.length) reject("MC_ARTIFACT_ALIAS");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verify five explicit byte pins, NOT executable semantics, active runtime,
|
|
115
|
+
* manifest/schema validity or installed dependency/import closure. No discovery,
|
|
116
|
+
* candidate execution/import, node_modules enumeration, install or write.
|
|
117
|
+
* Trusted IO doubles are simulations, never native local-read attestations.
|
|
118
|
+
* Rechecks are cooperative observations, not atomic openat or ABA isolation.
|
|
119
|
+
*/
|
|
120
|
+
export async function inspectMemoryConnectionArtifacts(input, dependencies = {}) {
|
|
121
|
+
let data = null, nativeIO = false;
|
|
122
|
+
const budget = { read: 0 };
|
|
123
|
+
const report = (state, details = {}) => {
|
|
124
|
+
const matched = state === "matched";
|
|
125
|
+
const result = {
|
|
126
|
+
schemaVersion: MEMORY_CONNECTION_VERSION, state, hashes: null,
|
|
127
|
+
metadataSha256: null, bytesRead: budget.read, reasons: [], ...details,
|
|
128
|
+
facts: {
|
|
129
|
+
...MEMORY_CONNECTION_FACTS, verificationScope: "base-artifacts-only",
|
|
130
|
+
evidence: matched ? nativeIO ? "local-artifact-files" : "simulated-local-artifacts" : "none",
|
|
131
|
+
artifactPathsVerified: matched && nativeIO, localReadVerified: matched && nativeIO,
|
|
132
|
+
bytePinsMatched: matched, installedDependenciesVerified: false,
|
|
133
|
+
manifestSemanticsVerified: false, runtimeVersionVerified: false,
|
|
134
|
+
runtimeCompatibilityVerified: false, inspectedCandidatesExecuted: false
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
if (Buffer.byteLength(JSON.stringify(result)) > MEMORY_CONNECTION_ARTIFACT_LIMITS.reportBytes) reject("MC_ARTIFACT_REPORT_LIMIT");
|
|
138
|
+
return freezeMemoryConnectionData(result);
|
|
139
|
+
};
|
|
140
|
+
try {
|
|
141
|
+
data = inputData(input);
|
|
142
|
+
nativeIO = dependencies.io === undefined;
|
|
143
|
+
const io = dependencies.io ?? fs, uid = process.getuid?.();
|
|
144
|
+
if (!["darwin", "linux"].includes(process.platform) || !Number.isSafeInteger(uid) || constants.O_NOFOLLOW === undefined) reject("MC_ARTIFACT_PLATFORM");
|
|
145
|
+
data.entries.find(entry => entry.key === 'packageLockSha256').full = await resolvePackageLock(data.descriptor.identity.toolkitRoot, io);
|
|
146
|
+
const items = [];
|
|
147
|
+
for (const entry of data.entries) items.push(await snapshot(io, entry, data.roots, uid));
|
|
148
|
+
rejectAliases(items, data.roots);
|
|
149
|
+
if (items.reduce((sum, item) => sum + item.stat.size, 0) > MEMORY_CONNECTION_ARTIFACT_LIMITS.totalBytes) reject("MC_ARTIFACT_TOTAL_LIMIT");
|
|
150
|
+
await recheckAll(io, items, data.roots, uid);
|
|
151
|
+
// One reusable small buffer, not an allocation of a Node binary's size.
|
|
152
|
+
const chunk = Buffer.alloc(MEMORY_CONNECTION_ARTIFACT_LIMITS.chunkBytes), hashes = {};
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
await recheckAll(io, items, data.roots, uid);
|
|
155
|
+
let handle;
|
|
156
|
+
try { handle = await io.open(item.entry.full, MEMORY_CONNECTION_ARTIFACT_OPEN_FLAGS); }
|
|
157
|
+
catch (e) { reject(errno(e) === "ELOOP" ? "MC_ARTIFACT_FILE_UNSAFE" : "MC_ARTIFACT_UNREADABLE"); }
|
|
158
|
+
const hash = createHash("sha256");
|
|
159
|
+
try {
|
|
160
|
+
const opened = await handle.stat(); safeFile(opened, item.entry, uid);
|
|
161
|
+
if (fileSignature(opened) !== fileSignature(item.stat)) reject("MC_ARTIFACT_CHANGED");
|
|
162
|
+
await recheck(io, item, data.roots, uid);
|
|
163
|
+
let offset = 0;
|
|
164
|
+
while (offset < item.stat.size) {
|
|
165
|
+
const request = Math.min(chunk.length, item.stat.size - offset, MEMORY_CONNECTION_ARTIFACT_LIMITS.totalBytes - budget.read);
|
|
166
|
+
if (request <= 0) reject("MC_ARTIFACT_TOTAL_LIMIT");
|
|
167
|
+
const { bytesRead } = await handle.read(chunk, 0, request, offset);
|
|
168
|
+
if (!Number.isSafeInteger(bytesRead) || bytesRead < 0 || bytesRead > request) reject("MC_ARTIFACT_READ");
|
|
169
|
+
budget.read += bytesRead; // Retained even if metadata/pin/hash later fails.
|
|
170
|
+
if (!bytesRead) reject("MC_ARTIFACT_CHANGED");
|
|
171
|
+
offset += bytesRead; hash.update(chunk.subarray(0, bytesRead));
|
|
172
|
+
}
|
|
173
|
+
const after = await handle.stat(); safeFile(after, item.entry, uid);
|
|
174
|
+
if (fileSignature(after) !== fileSignature(item.stat)) reject("MC_ARTIFACT_CHANGED");
|
|
175
|
+
await recheck(io, item, data.roots, uid);
|
|
176
|
+
} finally {
|
|
177
|
+
try { await handle.close(); } catch { reject("MC_ARTIFACT_CLOSE"); }
|
|
178
|
+
}
|
|
179
|
+
await recheck(io, item, data.roots, uid);
|
|
180
|
+
const value = hash.digest("hex");
|
|
181
|
+
if (value !== data.hashes[item.entry.key]) reject("MC_ARTIFACT_PIN_MISMATCH");
|
|
182
|
+
hashes[item.entry.key] = value;
|
|
183
|
+
}
|
|
184
|
+
await recheckAll(io, items, data.roots, uid);
|
|
185
|
+
const metadataSha256 = createHash("sha256").update(JSON.stringify(items.map(item => ({
|
|
186
|
+
role: item.entry.key, path: item.entry.full, parents: item.parents, signature: fileSignature(item.stat)
|
|
187
|
+
})))).digest("hex");
|
|
188
|
+
return report("matched", { hashes, metadataSha256, reasons: ["MC_ARTIFACT_MATCH"] });
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return report(e instanceof Rejected ? e.state : "blocked", { reasons: [e instanceof Rejected ? e.code : "MC_ARTIFACT_IO"] });
|
|
191
|
+
}
|
|
192
|
+
}
|