@amsterdamdatalabs/enact-extensions 0.1.37 → 0.1.47
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/README.md +67 -108
- package/bundled-extensions.json +6 -0
- package/dist/create/enact.d.ts.map +1 -1
- package/dist/create/enact.js +2 -0
- package/dist/create/enact.js.map +1 -1
- package/dist/create/index.d.ts +3 -0
- package/dist/create/index.d.ts.map +1 -1
- package/dist/create/index.js +6 -0
- package/dist/create/index.js.map +1 -1
- package/dist/create/kimi.d.ts +16 -0
- package/dist/create/kimi.d.ts.map +1 -0
- package/dist/create/kimi.js +87 -0
- package/dist/create/kimi.js.map +1 -0
- package/dist/create/opencode.d.ts +129 -0
- package/dist/create/opencode.d.ts.map +1 -0
- package/dist/create/opencode.js +696 -0
- package/dist/create/opencode.js.map +1 -0
- package/dist/index.d.ts +12 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/internal/agents.d.ts +12 -2
- package/dist/internal/agents.d.ts.map +1 -1
- package/dist/internal/agents.js +119 -3
- package/dist/internal/agents.js.map +1 -1
- package/dist/internal/extensions-table.d.ts +58 -0
- package/dist/internal/extensions-table.d.ts.map +1 -0
- package/dist/internal/extensions-table.js +134 -0
- package/dist/internal/extensions-table.js.map +1 -0
- package/dist/internal/file-ops.d.ts +36 -0
- package/dist/internal/file-ops.d.ts.map +1 -0
- package/dist/internal/file-ops.js +89 -0
- package/dist/internal/file-ops.js.map +1 -0
- package/dist/internal/hooks.d.ts +56 -0
- package/dist/internal/hooks.d.ts.map +1 -0
- package/dist/internal/hooks.js +128 -0
- package/dist/internal/hooks.js.map +1 -0
- package/dist/internal/json-merge.d.ts +63 -0
- package/dist/internal/json-merge.d.ts.map +1 -0
- package/dist/internal/json-merge.js +122 -0
- package/dist/internal/json-merge.js.map +1 -0
- package/dist/internal/lock-ownership.d.ts +41 -0
- package/dist/internal/lock-ownership.d.ts.map +1 -0
- package/dist/internal/lock-ownership.js +24 -0
- package/dist/internal/lock-ownership.js.map +1 -0
- package/dist/internal/platform.d.ts.map +1 -1
- package/dist/internal/platform.js +90 -4
- package/dist/internal/platform.js.map +1 -1
- package/dist/internal/schema.js.map +1 -1
- package/dist/internal/toml-block.d.ts +59 -0
- package/dist/internal/toml-block.d.ts.map +1 -0
- package/dist/internal/toml-block.js +113 -0
- package/dist/internal/toml-block.js.map +1 -0
- package/dist/internal/types.d.ts +2 -1
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/projection.d.ts +47 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +180 -0
- package/dist/projection.js.map +1 -0
- package/dist/validate/index.d.ts +9 -4
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +56 -7
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/kimi.d.ts +3 -0
- package/dist/validate/kimi.d.ts.map +1 -0
- package/dist/validate/kimi.js +11 -0
- package/dist/validate/kimi.js.map +1 -0
- package/dist/validate/opencode.d.ts +3 -0
- package/dist/validate/opencode.d.ts.map +1 -0
- package/dist/validate/opencode.js +11 -0
- package/dist/validate/opencode.js.map +1 -0
- package/dist/validate/run.d.ts +39 -0
- package/dist/validate/run.d.ts.map +1 -0
- package/dist/validate/run.js +63 -0
- package/dist/validate/run.js.map +1 -0
- package/extensions/enact-loop/.agents/plugin.json +12 -8
- package/extensions/enact-loop/scripts/validate.mjs +113 -2
- package/extensions/enact-loop/skills/enact-loop/SKILL.md +36 -466
- package/extensions/enact-loop/skills/enact-loop/references/loop-contract.md +114 -0
- package/extensions/enact-loop/skills/loop-budget/SKILL.md +57 -0
- package/extensions/enact-loop/skills/loop-intake/SKILL.md +59 -0
- package/extensions/enact-repo/.agents/plugin.json +55 -0
- package/extensions/enact-repo/.mcp.json +20 -0
- package/extensions/enact-repo/README.md +90 -0
- package/extensions/enact-repo/hooks/hooks.json +98 -0
- package/extensions/enact-repo/lean-ctx/config.toml +35 -0
- package/extensions/enact-repo/lean-ctx/layout.toml +5 -0
- package/extensions/enact-repo/skills/aca-sandboxes/SKILL.md +53 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/capabilities.md +52 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/install.md +87 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/prerequisites.md +52 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/quickstart.md +164 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/reference-advanced.md +251 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/reference.md +287 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/response-cues.md +71 -0
- package/extensions/enact-repo/skills/aca-sandboxes/references/scenarios.md +219 -0
- package/extensions/enact-repo/skills/aca-sandboxes/version.json +4 -0
- package/extensions/enact-repo/skills/backend-remove-deadcode/SKILL.md +96 -0
- package/extensions/enact-repo/skills/cmux-leader/SKILL.md +61 -0
- package/extensions/enact-repo/skills/cmux-send/SKILL.md +61 -0
- package/extensions/enact-repo/skills/code-review/SKILL.md +100 -0
- package/extensions/enact-repo/skills/codebase-design/SKILL.md +98 -0
- package/extensions/enact-repo/skills/codebase-design/references/DEEPENING.md +37 -0
- package/extensions/enact-repo/skills/codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/extensions/enact-repo/skills/codebase-design/references/TESTABILITY.md +29 -0
- package/extensions/enact-repo/skills/computer-use/SKILL.md +76 -0
- package/extensions/enact-repo/skills/debug-issue/SKILL.md +40 -0
- package/extensions/enact-repo/skills/diagnosing-bugs/SKILL.md +100 -0
- package/extensions/enact-repo/skills/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +34 -0
- package/extensions/enact-repo/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/extensions/enact-repo/skills/domain-modeling/SKILL.md +86 -0
- package/extensions/enact-repo/skills/domain-modeling/references/ADR-FORMAT.md +47 -0
- package/extensions/enact-repo/skills/domain-modeling/references/CONTEXT-FORMAT.md +60 -0
- package/extensions/enact-repo/skills/explore-codebase/SKILL.md +41 -0
- package/extensions/enact-repo/skills/frontend-design/LICENSE.txt +177 -0
- package/extensions/enact-repo/skills/frontend-design/SKILL.md +56 -0
- package/extensions/enact-repo/skills/grill-me/SKILL.md +20 -0
- package/extensions/enact-repo/skills/grill-with-docs/SKILL.md +20 -0
- package/extensions/enact-repo/skills/handoff/SKILL.md +28 -0
- package/extensions/enact-repo/skills/http-contract-migration/SKILL.md +74 -0
- package/extensions/enact-repo/skills/http-contract-migration/references/compatibility-and-gates.md +26 -0
- package/extensions/enact-repo/skills/http-contract-migration/references/hexagonal-boundary.md +16 -0
- package/extensions/enact-repo/skills/http-contract-migration/references/publication-and-adoption.md +31 -0
- package/extensions/enact-repo/skills/http-contract-migration/references/wire-fidelity-and-dto.md +31 -0
- package/extensions/enact-repo/skills/implement/SKILL.md +28 -0
- package/extensions/enact-repo/skills/improve-codebase-architecture/SKILL.md +80 -0
- package/extensions/enact-repo/skills/improve-codebase-architecture/references/HTML-REPORT.md +123 -0
- package/extensions/enact-repo/skills/interaction-design/SKILL.md +30 -0
- package/extensions/enact-repo/skills/interaction-design/references/Workflow.md +146 -0
- package/extensions/enact-repo/skills/interaction-design/references/animation-libraries-gsap.md +220 -0
- package/extensions/enact-repo/skills/interaction-design/references/animation-libraries-native.md +110 -0
- package/extensions/enact-repo/skills/interaction-design/references/animation-libraries.md +178 -0
- package/extensions/enact-repo/skills/interaction-design/references/interaction-patterns.md +166 -0
- package/extensions/enact-repo/skills/interaction-design/references/microinteraction-patterns-advanced.md +285 -0
- package/extensions/enact-repo/skills/interaction-design/references/microinteraction-patterns.md +254 -0
- package/extensions/enact-repo/skills/interaction-design/references/scroll-animations-reveals.md +213 -0
- package/extensions/enact-repo/skills/interaction-design/references/scroll-animations.md +209 -0
- package/extensions/enact-repo/skills/lean-ctx/SKILL.md +81 -0
- package/extensions/enact-repo/skills/orca-cli/SKILL.md +88 -0
- package/extensions/enact-repo/skills/orchestration/SKILL.md +80 -0
- package/extensions/enact-repo/skills/prototype/SKILL.md +37 -0
- package/extensions/enact-repo/skills/prototype/references/LOGIC.md +79 -0
- package/extensions/enact-repo/skills/prototype/references/UI.md +112 -0
- package/extensions/enact-repo/skills/refactor-safely/SKILL.md +44 -0
- package/extensions/enact-repo/skills/remove-deadcode/SKILL.md +54 -0
- package/extensions/enact-repo/skills/research/SKILL.md +27 -0
- package/extensions/enact-repo/skills/resolving-merge-conflicts/SKILL.md +28 -0
- package/extensions/enact-repo/skills/review-changes/SKILL.md +45 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/SKILL.md +88 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/accent-color.mdx +149 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/adding-custom-styles.mdx +635 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/align-content.mdx +300 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/align-items.mdx +241 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/align-self.mdx +294 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/animation.mdx +286 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/appearance.mdx +180 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/aspect-ratio.mdx +98 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-blur.mdx +109 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-brightness.mdx +82 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-contrast.mdx +82 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-grayscale.mdx +99 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-hue-rotate.mdx +173 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-invert.mdx +99 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-opacity.mdx +103 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-saturate.mdx +98 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter-sepia.mdx +95 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backdrop-filter.mdx +113 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/backface-visibility.mdx +110 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-attachment.mdx +126 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-blend-mode.mdx +79 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-clip.mdx +85 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-color.mdx +176 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-image.mdx +349 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-origin.mdx +64 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-position.mdx +171 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-repeat.mdx +127 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/background-size.mdx +100 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/block-size.mdx +190 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-collapse.mdx +181 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-color.mdx +316 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-radius.mdx +347 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-spacing.mdx +117 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-style.mdx +129 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/border-width.mdx +344 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/box-decoration-break.mdx +71 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/box-shadow.mdx +497 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/box-sizing.mdx +175 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/break-after.mdx +38 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/break-before.mdx +38 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/break-inside.mdx +34 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/caption-side.mdx +190 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/caret-color.mdx +66 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/clear.mdx +224 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/color-scheme.mdx +81 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/color.mdx +125 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/colors.mdx +927 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/columns.mdx +310 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/compatibility.mdx +193 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/content.mdx +129 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/cursor.mdx +104 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/dark-mode.mdx +191 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/detecting-classes-in-source-files.mdx +315 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/display.mdx +523 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/editor-setup.mdx +90 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/field-sizing.mdx +75 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/fill.mdx +100 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-blur.mdx +114 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-brightness.mdx +102 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-contrast.mdx +102 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-drop-shadow.mdx +230 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-grayscale.mdx +101 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-hue-rotate.mdx +171 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-invert.mdx +93 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-saturate.mdx +109 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter-sepia.mdx +92 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/filter.mdx +113 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex-basis.mdx +144 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex-direction.mdx +130 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex-grow.mdx +129 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex-shrink.mdx +95 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex-wrap.mdx +118 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/flex.mdx +164 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/float.mdx +197 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-family.mdx +119 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-feature-settings.mdx +52 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-size.mdx +184 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-smoothing.mdx +67 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-stretch.mdx +138 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-style.mdx +62 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-variant-numeric.mdx +228 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/font-weight.mdx +105 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/forced-color-adjust.mdx +152 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/functions-and-directives.mdx +322 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/gap.mdx +100 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-auto-columns.mdx +43 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-auto-flow.mdx +58 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-auto-rows.mdx +43 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-column.mdx +124 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-row.mdx +115 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-template-columns.mdx +101 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/grid-template-rows.mdx +108 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/height.mdx +260 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/hover-focus-and-other-states.mdx +4935 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/hyphens.mdx +112 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/inline-size.mdx +191 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/isolation.mdx +30 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/justify-content.mdx +348 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/justify-items.mdx +286 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/justify-self.mdx +282 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/letter-spacing.mdx +100 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/line-clamp.mdx +110 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/line-height.mdx +147 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/list-style-image.mdx +80 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/list-style-position.mdx +67 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/list-style-type.mdx +86 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/margin.mdx +397 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-clip.mdx +66 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-composite.mdx +70 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-image.mdx +933 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-mode.mdx +114 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-origin.mdx +66 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-position.mdx +117 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-repeat.mdx +169 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-size.mdx +104 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mask-type.mdx +98 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/max-block-size.mdx +153 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/max-height.mdx +164 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/max-inline-size.mdx +185 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/max-width.mdx +214 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/min-block-size.mdx +146 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/min-height.mdx +150 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/min-inline-size.mdx +153 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/min-width.mdx +153 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/mix-blend-mode.mdx +103 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/object-fit.mdx +147 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/object-position.mdx +182 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/opacity.mdx +84 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/order.mdx +94 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/outline-color.mdx +130 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/outline-offset.mdx +72 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/outline-style.mdx +134 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/outline-width.mdx +91 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/overflow-wrap.mdx +130 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/overflow.mdx +665 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/overscroll-behavior.mdx +151 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/padding.mdx +232 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/perspective-origin.mdx +127 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/perspective.mdx +137 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/place-content.mdx +257 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/place-items.mdx +269 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/place-self.mdx +257 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/pointer-events.mdx +101 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/position.mdx +399 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/preflight.mdx +291 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/resize.mdx +110 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/responsive-design.mdx +666 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/rotate.mdx +291 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scale.mdx +360 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-behavior.mdx +39 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-margin.mdx +268 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-padding.mdx +255 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-snap-align.mdx +281 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-snap-stop.mdx +198 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/scroll-snap-type.mdx +284 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/skew.mdx +352 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/stroke-width.mdx +74 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/stroke.mdx +103 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/styling-with-utility-classes.mdx +1020 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/table-layout.mdx +212 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-align.mdx +162 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-decoration-color.mdx +155 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-decoration-line.mdx +143 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-decoration-style.mdx +75 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-decoration-thickness.mdx +75 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-indent.mdx +74 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-overflow.mdx +103 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-shadow.mdx +177 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-transform.mdx +106 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-underline-offset.mdx +74 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/text-wrap.mdx +144 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/theme.mdx +1142 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/top-right-bottom-left.mdx +296 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/touch-action.mdx +95 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transform-origin.mdx +130 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transform-style.mdx +110 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transform.mdx +51 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transition-behavior.mdx +76 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transition-delay.mdx +76 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transition-duration.mdx +82 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transition-property.mdx +137 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/transition-timing-function.mdx +89 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/translate.mdx +487 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/upgrade-guide.mdx +910 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/user-select.mdx +114 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/vertical-align.mdx +194 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/visibility.mdx +287 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/white-space.mdx +211 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/width.mdx +264 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/will-change.mdx +47 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/word-break.mdx +94 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs/z-index.mdx +104 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs-catalog.md +251 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs-index.tsx +237 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/docs-source.txt +6 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/references/gotchas.md +18 -0
- package/extensions/enact-repo/skills/tailwind-4-docs/scripts/sync_tailwind_docs.py +183 -0
- package/extensions/enact-repo/skills/tdd/SKILL.md +49 -0
- package/extensions/enact-repo/skills/tdd/references/mocking.md +59 -0
- package/extensions/enact-repo/skills/tdd/references/tests.md +77 -0
- package/extensions/enact-repo/skills/teach/SKILL.md +88 -0
- package/extensions/enact-repo/skills/teach/references/GLOSSARY-FORMAT.md +35 -0
- package/extensions/enact-repo/skills/teach/references/LEARNING-RECORD-FORMAT.md +46 -0
- package/extensions/enact-repo/skills/teach/references/MISSION-FORMAT.md +31 -0
- package/extensions/enact-repo/skills/teach/references/PHILOSOPHY.md +65 -0
- package/extensions/enact-repo/skills/teach/references/RESOURCES-FORMAT.md +32 -0
- package/extensions/enact-repo/skills/testing/SKILL.md +54 -0
- package/extensions/enact-repo/skills/to-spec/SKILL.md +85 -0
- package/extensions/enact-repo/skills/to-tickets/SKILL.md +75 -0
- package/extensions/enact-repo/skills/to-tickets/references/TICKET-TEMPLATES.md +39 -0
- package/extensions/enact-repo/skills/triage/SKILL.md +99 -0
- package/extensions/enact-repo/skills/triage/references/AGENT-BRIEF.md +207 -0
- package/extensions/enact-repo/skills/triage/references/NEEDS-INFO-TEMPLATE.md +17 -0
- package/extensions/enact-repo/skills/triage/references/OUT-OF-SCOPE.md +105 -0
- package/extensions/enact-repo/skills/ui-ux-pro-max/SKILL.md +32 -0
- package/extensions/enact-repo/skills/ui-ux-pro-max/references/Workflow.md +207 -0
- package/extensions/enact-repo/skills/ui-ux-pro-max/references/workflow-reference.md +175 -0
- package/extensions/enact-repo/skills/wayfinder/SKILL.md +100 -0
- package/extensions/enact-repo/skills/wayfinder/references/TEMPLATES.md +35 -0
- package/extensions/enact-repo/skills/wayfinder/references/TICKET-TYPES.md +8 -0
- package/extensions/enact-repo/skills/writing-great-skills/SKILL.md +93 -0
- package/extensions/enact-repo/skills/writing-great-skills/references/GLOSSARY.md +201 -0
- package/package.json +18 -14
- package/scripts/enact-extensions.mjs +255 -873
- package/scripts/lib/allowed-plugins.mjs +52 -0
- package/scripts/lib/run-repo-doctor.mjs +571 -0
- package/scripts/lib/run-repo-install.mjs +1579 -0
- package/scripts/lib/run-sync.mjs +1 -1
- package/scripts/lib/run-validate.mjs +23 -37
- package/scripts/lib/skill-validator.mjs +872 -0
- package/spec/enact.json +21 -3
- package/spec/enact.md +13 -12
- package/spec/kimi.json +100 -0
- package/spec/opencode.json +64 -0
- package/dist/install.d.ts +0 -235
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js +0 -697
- package/dist/install.js.map +0 -1
- package/dist/internal/claude.d.ts +0 -65
- package/dist/internal/claude.d.ts.map +0 -1
- package/dist/internal/claude.js +0 -200
- package/dist/internal/claude.js.map +0 -1
- package/dist/internal/codex.d.ts +0 -43
- package/dist/internal/codex.d.ts.map +0 -1
- package/dist/internal/codex.js +0 -211
- package/dist/internal/codex.js.map +0 -1
- package/dist/internal/cursor.d.ts +0 -2
- package/dist/internal/cursor.d.ts.map +0 -1
- package/dist/internal/cursor.js +0 -6
- package/dist/internal/cursor.js.map +0 -1
- package/dist/principles.d.ts +0 -28
- package/dist/principles.d.ts.map +0 -1
- package/dist/principles.js +0 -159
- package/dist/principles.js.map +0 -1
- package/dist/provision.d.ts +0 -30
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -202
- package/dist/provision.js.map +0 -1
- package/extensions/dev-state/.agents/plugin.json +0 -36
- package/extensions/dev-state/skills/dev-state-plan-graduation/SKILL.md +0 -194
- package/extensions/dev-state/skills/dev-state-plan-graduation/agents/openai.yaml +0 -4
- package/extensions/dev-state/skills/dev-state-plan-graduation/references/reference.md +0 -130
- package/extensions/enact-context/.agents/plugin.json +0 -41
- package/extensions/enact-context/.mcp.json +0 -8
- package/extensions/enact-context/README.md +0 -25
- package/extensions/enact-context/assets/icon.png +0 -0
- package/extensions/enact-context/assets/logo.png +0 -0
- package/extensions/enact-context/hooks/hooks.json +0 -115
- package/extensions/enact-context/skills/enact-context/SKILL.md +0 -151
- package/extensions/enact-context/skills/enact-context/scripts/install.sh +0 -69
- package/extensions/enact-core/.agents/plugin.json +0 -40
- package/extensions/enact-core/OPERATING-PRINCIPLES.md +0 -7
- package/extensions/enact-core/hooks/hooks.json +0 -26
- package/extensions/enact-evolve/.agents/plugin.json +0 -47
- package/extensions/enact-evolve/agents/evolve-session-analyst.toml +0 -37
- package/extensions/enact-evolve/assets/icon.png +0 -0
- package/extensions/enact-evolve/assets/logo.png +0 -0
- package/extensions/enact-evolve/skills/mined-investigate-loop/SKILL.md +0 -68
- package/extensions/enact-evolve/skills/session-analysis/SKILL.md +0 -98
- package/extensions/enact-evolve/skills/session-analysis/scripts/run-evolve-analysis.sh +0 -343
- package/extensions/enact-factory/.agents/plugin.json +0 -49
- package/extensions/enact-factory/.mcp.json +0 -8
- package/extensions/enact-factory/agents/architect.toml +0 -34
- package/extensions/enact-factory/agents/code-reviewer.toml +0 -33
- package/extensions/enact-factory/agents/critic.toml +0 -39
- package/extensions/enact-factory/agents/executor.toml +0 -26
- package/extensions/enact-factory/agents/explore.toml +0 -25
- package/extensions/enact-factory/agents/planner.toml +0 -26
- package/extensions/enact-factory/agents/verifier.toml +0 -33
- package/extensions/enact-factory/assets/icon.png +0 -0
- package/extensions/enact-factory/assets/logo.png +0 -0
- package/extensions/enact-factory/skills/advisor/SKILL.md +0 -82
- package/extensions/enact-factory/skills/ai-slop-cleaner/SKILL.md +0 -57
- package/extensions/enact-factory/skills/autonomous-runner/SKILL.md +0 -353
- package/extensions/enact-factory/skills/azdo-ci-strategy/SKILL.md +0 -309
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/build-failures.md +0 -60
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/cli-reference.md +0 -87
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/policies-and-pipelines.md +0 -132
- package/extensions/enact-factory/skills/azdo-ci-strategy/references/troubleshooting.md +0 -53
- package/extensions/enact-factory/skills/cmux-leader/SKILL.md +0 -74
- package/extensions/enact-factory/skills/cmux-send/SKILL.md +0 -50
- package/extensions/enact-factory/skills/committee/SKILL.md +0 -80
- package/extensions/enact-factory/skills/deep-interview/SKILL.md +0 -68
- package/extensions/enact-factory/skills/drive-loop/SKILL.md +0 -394
- package/extensions/enact-factory/skills/drive-loop/references/contract-schema.md +0 -127
- package/extensions/enact-factory/skills/handoff/SKILL.md +0 -75
- package/extensions/enact-factory/skills/hyperplan/SKILL.md +0 -59
- package/extensions/enact-factory/skills/looplan/SKILL.md +0 -120
- package/extensions/enact-factory/skills/plan/SKILL.md +0 -103
- package/extensions/enact-factory/skills/remove-deadcode/SKILL.md +0 -46
- package/extensions/enact-factory/skills/research/SKILL.md +0 -83
- package/extensions/enact-factory/skills/review/SKILL.md +0 -67
- package/extensions/enact-factory/skills/security-research/SKILL.md +0 -57
- package/extensions/enact-factory/skills/tdd/SKILL.md +0 -62
- package/extensions/enact-factory/skills/testing-strategy/SKILL.md +0 -172
- package/extensions/enact-factory/skills/trace/SKILL.md +0 -42
- package/extensions/enact-factory/skills/ultraqa/SKILL.md +0 -85
- package/extensions/enact-factory/skills/work-with-workitem/SKILL.md +0 -56
- package/extensions/enact-factory/skills/workitem-triage/SKILL.md +0 -42
- package/extensions/enact-wiki/.agents/plugin.json +0 -43
- package/extensions/enact-wiki/.mcp.json +0 -15
- package/extensions/enact-wiki/README.md +0 -44
- package/extensions/enact-wiki/assets/icon.png +0 -0
- package/extensions/enact-wiki/assets/logo.png +0 -0
- package/extensions/enact-wiki/skills/document-parser/SKILL.md +0 -17
- package/extensions/enact-wiki/skills/document-parser/scripts/parse.sh +0 -60
- package/extensions/enact-wiki/skills/document-parser/skill.json +0 -9
- package/extensions/enact-wiki/skills/enact-wiki/SKILL.md +0 -30
- package/extensions/enact-wiki/skills/enact-wiki/references/ingest.md +0 -62
- package/extensions/enact-wiki/skills/enact-wiki/references/manage.md +0 -34
- package/extensions/enact-wiki/skills/enact-wiki/references/query.md +0 -59
- package/extensions/enact-wiki/skills/search-lab/SKILL.md +0 -57
- package/extensions/enact-wiki/skills/search-lab/scripts/analyze.ts +0 -23
- package/extensions/enact-wiki/skills/wiki/SKILL.md +0 -42
- package/extensions/net-revenue-management/.agents/plugin.json +0 -40
- package/extensions/net-revenue-management/.mcp.json +0 -9
- package/extensions/net-revenue-management/skills/net-revenue-risks/SKILL.md +0 -30
- package/extensions/net-revenue-management/skills/net-revenue-scenario/SKILL.md +0 -31
- package/extensions/plugin-dev/.agents/plugin.json +0 -41
- package/extensions/plugin-dev/agents/agent-creator.md +0 -199
- package/extensions/plugin-dev/agents/plugin-validator.md +0 -91
- package/extensions/plugin-dev/agents/skill-reviewer.md +0 -212
- package/extensions/plugin-dev/skills/agent-development/SKILL.md +0 -641
- package/extensions/plugin-dev/skills/agent-development/examples/agent-creation-prompt.md +0 -250
- package/extensions/plugin-dev/skills/agent-development/examples/complete-agent-examples.md +0 -461
- package/extensions/plugin-dev/skills/agent-development/references/advanced-agent-fields.md +0 -246
- package/extensions/plugin-dev/skills/agent-development/references/agent-creation-system-prompt.md +0 -216
- package/extensions/plugin-dev/skills/agent-development/references/permission-modes-rules.md +0 -226
- package/extensions/plugin-dev/skills/agent-development/references/system-prompt-design.md +0 -464
- package/extensions/plugin-dev/skills/agent-development/references/triggering-examples.md +0 -474
- package/extensions/plugin-dev/skills/agent-development/scripts/create-agent-skeleton.sh +0 -176
- package/extensions/plugin-dev/skills/agent-development/scripts/test-agent-trigger.sh +0 -227
- package/extensions/plugin-dev/skills/agent-development/scripts/validate-agent.sh +0 -227
- package/extensions/plugin-dev/skills/create-plugin/SKILL.md +0 -505
- package/extensions/plugin-dev/skills/hook-development/SKILL.md +0 -861
- package/extensions/plugin-dev/skills/hook-development/examples/load-context.sh +0 -55
- package/extensions/plugin-dev/skills/hook-development/examples/validate-bash.sh +0 -57
- package/extensions/plugin-dev/skills/hook-development/examples/validate-write.sh +0 -48
- package/extensions/plugin-dev/skills/hook-development/references/advanced.md +0 -871
- package/extensions/plugin-dev/skills/hook-development/references/hook-input-schemas.md +0 -145
- package/extensions/plugin-dev/skills/hook-development/references/migration.md +0 -392
- package/extensions/plugin-dev/skills/hook-development/references/patterns.md +0 -430
- package/extensions/plugin-dev/skills/hook-development/scripts/README.md +0 -181
- package/extensions/plugin-dev/skills/hook-development/scripts/hook-linter.sh +0 -153
- package/extensions/plugin-dev/skills/hook-development/scripts/test-hook.sh +0 -276
- package/extensions/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh +0 -159
- package/extensions/plugin-dev/skills/mcp-integration/SKILL.md +0 -775
- package/extensions/plugin-dev/skills/mcp-integration/examples/http-server.json +0 -20
- package/extensions/plugin-dev/skills/mcp-integration/examples/sse-server.json +0 -19
- package/extensions/plugin-dev/skills/mcp-integration/examples/stdio-server.json +0 -38
- package/extensions/plugin-dev/skills/mcp-integration/examples/ws-server.json +0 -26
- package/extensions/plugin-dev/skills/mcp-integration/references/authentication.md +0 -601
- package/extensions/plugin-dev/skills/mcp-integration/references/server-discovery.md +0 -190
- package/extensions/plugin-dev/skills/mcp-integration/references/server-types.md +0 -572
- package/extensions/plugin-dev/skills/mcp-integration/references/tool-usage.md +0 -623
- package/extensions/plugin-dev/skills/plugin-dev-guide/SKILL.md +0 -221
- package/extensions/plugin-dev/skills/plugin-structure/SKILL.md +0 -705
- package/extensions/plugin-dev/skills/plugin-structure/examples/advanced-plugin.md +0 -774
- package/extensions/plugin-dev/skills/plugin-structure/examples/minimal-plugin.md +0 -83
- package/extensions/plugin-dev/skills/plugin-structure/examples/standard-plugin.md +0 -611
- package/extensions/plugin-dev/skills/plugin-structure/references/advanced-topics.md +0 -289
- package/extensions/plugin-dev/skills/plugin-structure/references/component-patterns.md +0 -592
- package/extensions/plugin-dev/skills/plugin-structure/references/github-actions.md +0 -233
- package/extensions/plugin-dev/skills/plugin-structure/references/headless-ci-mode.md +0 -193
- package/extensions/plugin-dev/skills/plugin-structure/references/manifest-reference.md +0 -625
- package/extensions/plugin-dev/skills/plugin-structure/references/output-styles.md +0 -116
- package/extensions/plugin-dev/skills/skill-development/SKILL.md +0 -562
- package/extensions/plugin-dev/skills/skill-development/examples/complete-skill.md +0 -465
- package/extensions/plugin-dev/skills/skill-development/examples/frontmatter-templates.md +0 -167
- package/extensions/plugin-dev/skills/skill-development/examples/minimal-skill.md +0 -111
- package/extensions/plugin-dev/skills/skill-development/references/advanced-frontmatter.md +0 -225
- package/extensions/plugin-dev/skills/skill-development/references/commands-vs-skills.md +0 -39
- package/extensions/plugin-dev/skills/skill-development/references/skill-creation-workflow.md +0 -379
- package/extensions/plugin-dev/skills/skill-development/references/skill-creator-original.md +0 -210
- package/extensions/plugin-dev/skills/start/SKILL.md +0 -82
- package/scripts/check-hooks.mjs +0 -174
- package/scripts/check-principles.mjs +0 -116
- package/scripts/hooks/session-start-drift-check.mjs +0 -48
- package/scripts/install.sh +0 -70
- package/scripts/lib/auto-update.mjs +0 -75
- package/scripts/lib/bundle-hash.mjs +0 -137
- package/scripts/lib/hooks.mjs +0 -585
- package/scripts/lib/ledger.mjs +0 -163
- package/scripts/lib/migrate-artifacts.mjs +0 -144
- package/scripts/lib/outdated.mjs +0 -144
- package/scripts/lib/postinstall-offer.mjs +0 -83
- package/scripts/lib/principles.mjs +0 -109
- package/scripts/lib/provision-mcp.mjs +0 -16
- package/scripts/lib/resolve-plugin-root.mjs +0 -9
- package/scripts/lib/run-install.mjs +0 -500
- package/scripts/lib/run-prune.mjs +0 -94
- package/scripts/lib/run-uninstall.mjs +0 -244
- package/scripts/lib/run-update.mjs +0 -152
- package/scripts/lib/serve.mjs +0 -472
- package/scripts/lib/ups-router.mjs +0 -392
- package/scripts/postinstall.mjs +0 -101
- package/scripts/setup-enact-context.sh +0 -28
- package/scripts/version-bump.sh +0 -484
- package/web/assets/README.md +0 -111
- package/web/assets/logo-full.png +0 -0
- package/web/assets/logo-slim.png +0 -0
- package/web/assets/tokens/base.css +0 -45
- package/web/assets/tokens/colors.css +0 -248
- package/web/assets/tokens/effects.css +0 -24
- package/web/assets/tokens/fonts.css +0 -8
- package/web/assets/tokens/index.css +0 -18
- package/web/assets/tokens/spacing.css +0 -50
- package/web/index.html +0 -1188
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { Ajv } from "ajv";
|
|
5
|
+
import addFormats from "ajv-formats";
|
|
6
|
+
import { parseDocument } from "yaml";
|
|
7
|
+
|
|
8
|
+
export const ALL_SURFACE_TARGETS = ["claude", "codex", "cursor", "enact", "kimi"];
|
|
9
|
+
/** Opt-in surface a bundle may append after the required targets. */
|
|
10
|
+
export const OPT_IN_TARGET = "opencode";
|
|
11
|
+
export const SKILL_ROOT_DIRECTORIES = new Set(["scripts", "references", "assets"]);
|
|
12
|
+
/** Non-SKILL.md files still allowed directly under a skill root: license metadata and a version marker. */
|
|
13
|
+
export const SKILL_ROOT_ALLOWED_EXTRA_FILES = /^(LICENSE(\..+)?|version\.json)$/;
|
|
14
|
+
/**
|
|
15
|
+
* The only H2 headings SKILL.md is required to carry: `Use When` before
|
|
16
|
+
* `Do Not Use When`. Any other H2 headings are allowed, in any order.
|
|
17
|
+
*/
|
|
18
|
+
export const REQUIRED_SKILL_HEADINGS = ["Use When", "Do Not Use When"];
|
|
19
|
+
export const REQUIRED_BOUNDARY_HEADINGS = ["In scope", "Out of scope", "Fog of war"];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Plugins exempt from the fixed section sequence and the SKILL.md line cap.
|
|
23
|
+
*
|
|
24
|
+
* The eight-heading contract exists so an autonomous runner finds Purpose,
|
|
25
|
+
* State Contract, and Completion in a known place in every governance skill. It
|
|
26
|
+
* is a poor fit for imported reference material, whose own headings carry the
|
|
27
|
+
* method — forcing `tdd` into Purpose/Use When/Output destroys the thing that
|
|
28
|
+
* makes it useful. Listed plugins still get every other rule: frontmatter shape,
|
|
29
|
+
* in-plugin route reachability, resource layout, and slop detection.
|
|
30
|
+
*/
|
|
31
|
+
export const FREEFORM_SECTION_PLUGINS = new Set(["engineering"]);
|
|
32
|
+
export const CANONICAL_FACTORY_STORAGE =
|
|
33
|
+
"Factory artifacts go under `~/.enact/factory/projects/<key>/…`.";
|
|
34
|
+
|
|
35
|
+
const ALLOWED_FRONTMATTER_KEYS = new Set([
|
|
36
|
+
"name",
|
|
37
|
+
"description",
|
|
38
|
+
"disable-model-invocation",
|
|
39
|
+
"compatibility",
|
|
40
|
+
]);
|
|
41
|
+
const COMPONENT_FIELDS = [
|
|
42
|
+
"skills",
|
|
43
|
+
"agents",
|
|
44
|
+
"commands",
|
|
45
|
+
"hooks",
|
|
46
|
+
"mcpServers",
|
|
47
|
+
"apps",
|
|
48
|
+
"lsp",
|
|
49
|
+
"monitors",
|
|
50
|
+
"settings",
|
|
51
|
+
"references",
|
|
52
|
+
"bin",
|
|
53
|
+
"rules",
|
|
54
|
+
"_agents",
|
|
55
|
+
];
|
|
56
|
+
const COMMAND_SHAPED_LINE = /`[^`]*(\b(npm|git)\s+(run|test|start|build|install|ci|status|diff|log|show|stash|commit|push|pull|checkout|merge-base|rev-parse)\b|\b(node|npx|paseo|cargo|az|tsc|grep)\s+\S|loop_[a-z]+|factory_[a-z]+|exit 0)[^`]*`/i;
|
|
57
|
+
const EMOJI = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{26FF}\u{2705}\u{274C}\u{2713}-\u{2716}]/u;
|
|
58
|
+
const SLOP_PATTERNS = [
|
|
59
|
+
["slop-storage-banner", /Storage is GLOBAL|never write to a repo-local `?\.enact/i],
|
|
60
|
+
["slop-workspace-recipe", /~\/\.enact\/workspaces\.json|Resolve the project'?s global dir/i],
|
|
61
|
+
["slop-marketing", /\b(seamless|best practices|dive in|game[- ]changer|supercharge|effortless|cutting-edge|comprehensive|powerful|leverage(?:d|s|ing)?|elegant|closes? the gap|unlock|elevate|world-class|next-level|revolutionary|synerg(?:y|ies|ize)|utilize)\b/i],
|
|
62
|
+
["slop-filler", /\b(it'?s worth noting|it'?s important to note|note that|in summary|in conclusion|obviously|clearly)\b/i],
|
|
63
|
+
["slop-self-reference", /\b(generated by an AI|LLM-authored|as an AI)\b/i],
|
|
64
|
+
["slop-edit-scar", /as of \)|(?<![A-Za-z0-9_])\(\s*\)|\(,\s|^#{1,6}\s+.* —\s*$/im],
|
|
65
|
+
];
|
|
66
|
+
const manifestSchemaValidators = new Map();
|
|
67
|
+
const VALIDATOR_ROOT = resolve(import.meta.dirname, "..", "..");
|
|
68
|
+
|
|
69
|
+
function diagnostic(code, path, message) {
|
|
70
|
+
return { code, path: toPosix(path), message };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toPosix(value) {
|
|
74
|
+
return value.split(sep).join("/");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function lineCount(text) {
|
|
78
|
+
if (!text) return 0;
|
|
79
|
+
return text.split(/\r?\n/).length - (text.endsWith("\n") ? 1 : 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isIgnoredNoise(name) {
|
|
83
|
+
return name === ".DS_Store" || name === ".donor";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isDirectory(path) {
|
|
87
|
+
try {
|
|
88
|
+
return statSync(path).isDirectory();
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isFile(path) {
|
|
95
|
+
try {
|
|
96
|
+
return statSync(path).isFile();
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isWithin(root, candidate) {
|
|
103
|
+
const rel = relative(root, candidate);
|
|
104
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !rel.includes(`..${sep}`));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function safeRelative(root, raw) {
|
|
108
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
109
|
+
const cleaned = raw.trim().replace(/^\.\//, "");
|
|
110
|
+
if (cleaned.startsWith("/") || cleaned.split("/").includes("..")) return null;
|
|
111
|
+
const absolute = resolve(root, cleaned);
|
|
112
|
+
return isWithin(root, absolute) ? { absolute, relative: toPosix(cleaned.replace(/\/+$/, "")) } : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function listFiles(root) {
|
|
116
|
+
const files = [];
|
|
117
|
+
function walk(current) {
|
|
118
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
119
|
+
if (isIgnoredNoise(entry.name)) continue;
|
|
120
|
+
const absolute = join(current, entry.name);
|
|
121
|
+
const rel = toPosix(relative(root, absolute));
|
|
122
|
+
const stat = lstatSync(absolute);
|
|
123
|
+
if (stat.isSymbolicLink()) {
|
|
124
|
+
files.push({ absolute, relative: rel, symlink: true });
|
|
125
|
+
} else if (stat.isDirectory()) {
|
|
126
|
+
walk(absolute);
|
|
127
|
+
} else if (stat.isFile()) {
|
|
128
|
+
files.push({ absolute, relative: rel, symlink: false });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (isDirectory(root)) walk(root);
|
|
133
|
+
return files.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findGitRoot(start) {
|
|
137
|
+
try {
|
|
138
|
+
return execFileSync("git", ["-C", start, "rev-parse", "--show-toplevel"], {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
141
|
+
}).trim();
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function trackedFiles(repoRoot, treeish) {
|
|
148
|
+
const args = treeish
|
|
149
|
+
? ["-C", repoRoot, "ls-tree", "-r", "-z", "--name-only", treeish]
|
|
150
|
+
: ["-C", repoRoot, "ls-files", "-z"];
|
|
151
|
+
try {
|
|
152
|
+
const raw = execFileSync("git", args, {
|
|
153
|
+
encoding: "buffer",
|
|
154
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
155
|
+
});
|
|
156
|
+
return raw.toString("utf8").split("\0").filter(Boolean).map(toPosix);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
throw new Error(`Cannot read tracked files: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function gitFile(repoRoot, treeish, path) {
|
|
163
|
+
try {
|
|
164
|
+
return execFileSync("git", ["-C", repoRoot, "show", `${treeish}:${path}`], {
|
|
165
|
+
encoding: "utf8",
|
|
166
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
167
|
+
});
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function changedFiles(repoRoot, base, staged = false) {
|
|
174
|
+
const args = ["-C", repoRoot, "diff", "--name-only", "-z"];
|
|
175
|
+
if (staged) args.push("--cached");
|
|
176
|
+
else args.push(`${base}...HEAD`);
|
|
177
|
+
try {
|
|
178
|
+
return execFileSync("git", args, { encoding: "buffer", stdio: ["ignore", "pipe", "pipe"] })
|
|
179
|
+
.toString("utf8")
|
|
180
|
+
.split("\0")
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.map(toPosix);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
throw new Error(`Cannot read changed files: ${err instanceof Error ? err.message : String(err)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function frontmatter(text, path, diagnostics) {
|
|
189
|
+
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) {
|
|
190
|
+
diagnostics.push(diagnostic("frontmatter-missing", path, "SKILL.md must start with YAML frontmatter."));
|
|
191
|
+
return { metadata: null, body: text };
|
|
192
|
+
}
|
|
193
|
+
const lines = text.split(/\r?\n/);
|
|
194
|
+
const closing = lines.findIndex((line, index) => index > 0 && line === "---");
|
|
195
|
+
if (closing === -1) {
|
|
196
|
+
diagnostics.push(diagnostic("frontmatter-unterminated", path, "YAML frontmatter must end with a closing --- line."));
|
|
197
|
+
return { metadata: null, body: text };
|
|
198
|
+
}
|
|
199
|
+
const yaml = lines.slice(1, closing).join("\n");
|
|
200
|
+
const after = lines.slice(closing + 1).join("\n");
|
|
201
|
+
const document = parseDocument(yaml, { prettyErrors: true, uniqueKeys: true });
|
|
202
|
+
if (document.errors.length > 0) {
|
|
203
|
+
for (const error of document.errors) {
|
|
204
|
+
diagnostics.push(diagnostic("frontmatter-invalid-yaml", path, error.message));
|
|
205
|
+
}
|
|
206
|
+
return { metadata: null, body: after };
|
|
207
|
+
}
|
|
208
|
+
const metadata = document.toJS();
|
|
209
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
210
|
+
diagnostics.push(diagnostic("frontmatter-shape", path, "YAML frontmatter must be a mapping."));
|
|
211
|
+
return { metadata: null, body: after };
|
|
212
|
+
}
|
|
213
|
+
return { metadata, body: after };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function headings(body) {
|
|
217
|
+
const result = [];
|
|
218
|
+
let fenced = false;
|
|
219
|
+
for (const [index, line] of body.split(/\r?\n/).entries()) {
|
|
220
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
221
|
+
fenced = !fenced;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (fenced) continue;
|
|
225
|
+
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
226
|
+
if (match) result.push({ level: match[1].length, text: match[2], line: index + 1 });
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function sectionText(body, heading) {
|
|
232
|
+
const lines = body.split(/\r?\n/);
|
|
233
|
+
const start = heading.line;
|
|
234
|
+
const rest = lines.slice(start);
|
|
235
|
+
const end = rest.findIndex((line) => /^#{1,2}\s+/.test(line));
|
|
236
|
+
return (end === -1 ? rest : rest.slice(0, end)).join("\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function withoutFencedCode(text) {
|
|
240
|
+
const lines = [];
|
|
241
|
+
let fenced = false;
|
|
242
|
+
for (const line of text.split(/\r?\n/)) {
|
|
243
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
244
|
+
fenced = !fenced;
|
|
245
|
+
lines.push("");
|
|
246
|
+
} else {
|
|
247
|
+
lines.push(fenced ? "" : line);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return lines.join("\n");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resourceReferences(text, knownResources) {
|
|
254
|
+
const references = new Set();
|
|
255
|
+
for (const resource of knownResources) {
|
|
256
|
+
if (text.includes(resource)) references.add(resource);
|
|
257
|
+
}
|
|
258
|
+
for (const match of text.matchAll(/(?:scripts|references|assets)\/[A-Za-z0-9._/-]+/g)) {
|
|
259
|
+
const candidate = match[0].replace(/[),.;:]+$/, "");
|
|
260
|
+
if (!candidate || candidate.endsWith("/") || candidate.endsWith(".")) continue;
|
|
261
|
+
const preceding = match.index > 0 ? text[match.index - 1] : "";
|
|
262
|
+
if (preceding && /[A-Za-z0-9_./-]/.test(preceding)) continue;
|
|
263
|
+
const lineStart = text.lastIndexOf("\n", match.index) + 1;
|
|
264
|
+
const prefix = text.slice(lineStart, match.index);
|
|
265
|
+
if (candidate.startsWith("scripts/") && /(?:node|npx)\s+$/i.test(prefix)) continue;
|
|
266
|
+
references.add(candidate);
|
|
267
|
+
}
|
|
268
|
+
for (const match of text.matchAll(/\[[^\]]*]\((?:\.\/)?((?:scripts|references|assets)\/[A-Za-z0-9._/-]+)(?:#[^)\s]+)?\)/g)) {
|
|
269
|
+
const candidate = match[1].replace(/[),.;:]+$/, "");
|
|
270
|
+
if (!candidate.endsWith("/") && !candidate.endsWith(".")) references.add(candidate);
|
|
271
|
+
}
|
|
272
|
+
for (const match of text.matchAll(/`(?:\.\/)?((?:scripts|references|assets)\/[A-Za-z0-9._/-]+)`/g)) {
|
|
273
|
+
const candidate = match[1].replace(/[),.;:]+$/, "");
|
|
274
|
+
if (!candidate.endsWith("/") && !candidate.endsWith(".")) references.add(candidate);
|
|
275
|
+
}
|
|
276
|
+
return references;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function textResource(path) {
|
|
280
|
+
const extension = extname(path).toLowerCase();
|
|
281
|
+
if (!new Set([".md", ".mdx", ".txt", ".js", ".mjs", ".cjs", ".py", ".sh", ".json", ".yaml", ".yml"]).has(extension)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
return readFileSync(path, "utf8");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function validateScript(path, relativePath, diagnostics) {
|
|
288
|
+
const extension = extname(path).toLowerCase();
|
|
289
|
+
const command = extension === ".py"
|
|
290
|
+
? ["python3", ["-c", "import ast,pathlib,sys; ast.parse(pathlib.Path(sys.argv[1]).read_text())", path]]
|
|
291
|
+
: extension === ".sh"
|
|
292
|
+
? ["bash", ["-n", path]]
|
|
293
|
+
: new Set([".js", ".mjs", ".cjs"]).has(extension)
|
|
294
|
+
? [process.execPath, ["--check", path]]
|
|
295
|
+
: null;
|
|
296
|
+
if (!command) {
|
|
297
|
+
diagnostics.push(diagnostic("script-extension", relativePath, `Unsupported script extension ${extension || "(none)"}.`));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
execFileSync(command[0], command[1], { stdio: ["ignore", "pipe", "pipe"] });
|
|
302
|
+
} catch (err) {
|
|
303
|
+
const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
|
|
304
|
+
diagnostics.push(diagnostic("script-syntax", relativePath, stderr || "Script syntax check failed."));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizedParagraphs(body) {
|
|
309
|
+
return withoutFencedCode(body)
|
|
310
|
+
.split(/\n\s*\n/)
|
|
311
|
+
.map((part) => part.replace(/^#{1,6}\s+.*$/gm, "").replace(/[`*_>#-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase())
|
|
312
|
+
.filter((part) => part.length >= 120 && part !== CANONICAL_FACTORY_STORAGE.toLowerCase());
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function bundleOf(path) {
|
|
316
|
+
return /^extensions\/([^/]+)\//.exec(path)?.[1] ?? null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Flag a duplicate paragraph only when another occurrence of it sits in a
|
|
321
|
+
* skill belonging to the same bundle (same extensions/<bundle>/ prefix).
|
|
322
|
+
* A paragraph repeated only across different bundles is not a violation.
|
|
323
|
+
*/
|
|
324
|
+
function pushDuplicateParagraphDiagnostics(paragraphsByText, diagnostics) {
|
|
325
|
+
for (const paths of paragraphsByText.values()) {
|
|
326
|
+
if (paths.length < 2) continue;
|
|
327
|
+
for (const path of paths) {
|
|
328
|
+
const bundle = bundleOf(path);
|
|
329
|
+
const sameBundleOthers = paths.filter((other) => other !== path && bundleOf(other) === bundle);
|
|
330
|
+
if (sameBundleOthers.length === 0) continue;
|
|
331
|
+
diagnostics.push(diagnostic("slop-duplicate-paragraph", path, `Duplicate prose paragraph also appears in: ${sameBundleOthers.join(", ")}.`));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function validateSlop(text, path, diagnostics) {
|
|
337
|
+
const prose = withoutFencedCode(text);
|
|
338
|
+
for (const [code, pattern] of SLOP_PATTERNS) {
|
|
339
|
+
if (pattern.test(prose)) diagnostics.push(diagnostic(code, path, `AI-authoring residue matched ${pattern}.`));
|
|
340
|
+
}
|
|
341
|
+
if (/^\s*(?:[-*]\s+)?\*\*[^*\n]{60,}\*\*[.!?]?\s*$/m.test(prose)) {
|
|
342
|
+
diagnostics.push(diagnostic("slop-overbold", path, "Do not bold a whole sentence for emphasis."));
|
|
343
|
+
}
|
|
344
|
+
if (EMOJI.test(prose)) diagnostics.push(diagnostic("slop-emoji", path, "Emoji and decorative symbols are not allowed."));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* A genuine route reference names another skill in one of two documented
|
|
349
|
+
* shapes (see each skill's "Do Not Use When" bullets and closing routing
|
|
350
|
+
* sentences): a clause separated by a semicolon, e.g. "...; use
|
|
351
|
+
* `skill-name`.", or an explicit "belongs to `skill-name`" ownership
|
|
352
|
+
* statement. Plain prose that happens to mention a backticked, hyphenated
|
|
353
|
+
* token after "use" (e.g. naming a CLI executable like `orca-dev`) is not a
|
|
354
|
+
* route: it is not semicolon-delimited, so it does not match.
|
|
355
|
+
*/
|
|
356
|
+
const SKILL_ROUTE_REFERENCE = /(?:(?<=;\s+)use|\bbelongs to)\s+`([a-z][a-z0-9-]+)`/gi;
|
|
357
|
+
|
|
358
|
+
function validateSkillRoutes(skills, diagnostics) {
|
|
359
|
+
const names = new Set(skills.map((skill) => skill.skillName));
|
|
360
|
+
for (const skill of skills) {
|
|
361
|
+
for (const match of skill.body.matchAll(SKILL_ROUTE_REFERENCE)) {
|
|
362
|
+
const target = match[1];
|
|
363
|
+
if (target.includes("-") && !names.has(target)) {
|
|
364
|
+
diagnostics.push(diagnostic("skill-route-missing", skill.path, `Referenced skill does not exist in this plugin: ${target}.`));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validateCanonicalManifest(schemaRoot, manifest, pluginRelative, diagnostics) {
|
|
371
|
+
const schemaPath = join(schemaRoot, "spec", "enact.json");
|
|
372
|
+
if (!isFile(schemaPath)) {
|
|
373
|
+
diagnostics.push(diagnostic("manifest-schema", `${pluginRelative}/.agents/plugin.json`, "Canonical schema spec/enact.json is missing."));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
let validator = manifestSchemaValidators.get(schemaPath);
|
|
377
|
+
try {
|
|
378
|
+
if (!validator) {
|
|
379
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
380
|
+
addFormats(ajv);
|
|
381
|
+
validator = ajv.compile(JSON.parse(readFileSync(schemaPath, "utf8")));
|
|
382
|
+
manifestSchemaValidators.set(schemaPath, validator);
|
|
383
|
+
}
|
|
384
|
+
if (!validator(manifest)) {
|
|
385
|
+
for (const error of validator.errors ?? []) {
|
|
386
|
+
diagnostics.push(diagnostic(
|
|
387
|
+
"manifest-schema",
|
|
388
|
+
`${pluginRelative}/.agents/plugin.json`,
|
|
389
|
+
`${error.instancePath || "/"}: ${error.message ?? "invalid"}.`,
|
|
390
|
+
));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
diagnostics.push(diagnostic("manifest-schema", `${pluginRelative}/.agents/plugin.json`, `Cannot validate canonical manifest: ${err instanceof Error ? err.message : String(err)}`));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function validateSkill(skillRoot, pluginName) {
|
|
399
|
+
const diagnostics = [];
|
|
400
|
+
const skillName = basename(skillRoot);
|
|
401
|
+
const rootRelative = `extensions/${pluginName}/skills/${skillName}`;
|
|
402
|
+
const entries = readdirSync(skillRoot, { withFileTypes: true });
|
|
403
|
+
const resources = [];
|
|
404
|
+
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
if (isIgnoredNoise(entry.name)) continue;
|
|
407
|
+
const path = join(skillRoot, entry.name);
|
|
408
|
+
const relativePath = `${rootRelative}/${entry.name}`;
|
|
409
|
+
const stat = lstatSync(path);
|
|
410
|
+
if (stat.isSymbolicLink()) {
|
|
411
|
+
diagnostics.push(diagnostic("skill-symlink", relativePath, "Symlinks are not allowed in a skill."));
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (entry.isFile() && entry.name !== "SKILL.md") {
|
|
415
|
+
if (!SKILL_ROOT_ALLOWED_EXTRA_FILES.test(entry.name)) {
|
|
416
|
+
diagnostics.push(diagnostic("skill-root-file", relativePath, "Only SKILL.md, LICENSE, LICENSE.*, and version.json may be files directly under a skill root."));
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (entry.isDirectory() && !SKILL_ROOT_DIRECTORIES.has(entry.name)) {
|
|
421
|
+
diagnostics.push(diagnostic("skill-root-directory", relativePath, "Only scripts/, references/, and assets/ are allowed under a skill root."));
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (entry.isDirectory()) {
|
|
425
|
+
for (const file of listFiles(path)) {
|
|
426
|
+
const fullRelative = `${rootRelative}/${entry.name}/${file.relative}`;
|
|
427
|
+
if (file.symlink) diagnostics.push(diagnostic("skill-symlink", fullRelative, "Symlinks are not allowed in a skill."));
|
|
428
|
+
else resources.push({ ...file, relative: `${entry.name}/${file.relative}`, fullRelative });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const skillPath = join(skillRoot, "SKILL.md");
|
|
434
|
+
if (!isFile(skillPath)) {
|
|
435
|
+
diagnostics.push(diagnostic("skill-missing", `${rootRelative}/SKILL.md`, "Every skill directory requires SKILL.md."));
|
|
436
|
+
return { diagnostics, skillName, path: `${rootRelative}/SKILL.md`, body: "", paragraphs: [] };
|
|
437
|
+
}
|
|
438
|
+
const text = readFileSync(skillPath, "utf8");
|
|
439
|
+
const skillRelative = `${rootRelative}/SKILL.md`;
|
|
440
|
+
const freeformSections = FREEFORM_SECTION_PLUGINS.has(pluginName);
|
|
441
|
+
if (!freeformSections && lineCount(text) > 100) {
|
|
442
|
+
diagnostics.push(diagnostic("skill-line-limit", skillRelative, `SKILL.md has ${lineCount(text)} lines; maximum is 100.`));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const parsed = frontmatter(text, skillRelative, diagnostics);
|
|
446
|
+
if (parsed.metadata) {
|
|
447
|
+
for (const key of Object.keys(parsed.metadata)) {
|
|
448
|
+
if (!ALLOWED_FRONTMATTER_KEYS.has(key)) {
|
|
449
|
+
diagnostics.push(diagnostic("frontmatter-key", skillRelative, `Unexpected frontmatter key ${JSON.stringify(key)}.`));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (parsed.metadata.name !== skillName) {
|
|
453
|
+
diagnostics.push(diagnostic("frontmatter-name", skillRelative, `name must equal directory name ${JSON.stringify(skillName)}.`));
|
|
454
|
+
}
|
|
455
|
+
if (typeof parsed.metadata.description !== "string" || !parsed.metadata.description.trim()) {
|
|
456
|
+
diagnostics.push(diagnostic("frontmatter-description", skillRelative, "description must be a non-empty string."));
|
|
457
|
+
} else if (/^this skill should be used when\b/i.test(parsed.metadata.description.trim())) {
|
|
458
|
+
diagnostics.push(diagnostic("frontmatter-description-slop", skillRelative, "Use a direct description; do not start with generic trigger boilerplate."));
|
|
459
|
+
}
|
|
460
|
+
if (typeof parsed.metadata["disable-model-invocation"] !== "boolean") {
|
|
461
|
+
diagnostics.push(diagnostic("frontmatter-invocation", skillRelative, "disable-model-invocation must be an explicit boolean."));
|
|
462
|
+
}
|
|
463
|
+
if (parsed.metadata.compatibility !== undefined && typeof parsed.metadata.compatibility !== "string") {
|
|
464
|
+
diagnostics.push(diagnostic("frontmatter-compatibility", skillRelative, "compatibility must be a string when present."));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const body = parsed.body;
|
|
469
|
+
const allHeadings = headings(body);
|
|
470
|
+
const h1 = allHeadings.filter((entry) => entry.level === 1);
|
|
471
|
+
if (h1.length !== 1 || allHeadings[0] !== h1[0]) {
|
|
472
|
+
diagnostics.push(diagnostic("heading-h1", skillRelative, "SKILL.md must start its body with exactly one H1."));
|
|
473
|
+
}
|
|
474
|
+
const h2 = allHeadings.filter((entry) => entry.level === 2);
|
|
475
|
+
const actualH2 = h2.map((entry) => entry.text);
|
|
476
|
+
if (!freeformSections) {
|
|
477
|
+
const [useWhen, doNotUseWhen] = REQUIRED_SKILL_HEADINGS;
|
|
478
|
+
const useWhenIndex = actualH2.indexOf(useWhen);
|
|
479
|
+
const doNotUseWhenIndex = actualH2.indexOf(doNotUseWhen);
|
|
480
|
+
if (useWhenIndex === -1 || doNotUseWhenIndex === -1 || useWhenIndex > doNotUseWhenIndex) {
|
|
481
|
+
diagnostics.push(diagnostic(
|
|
482
|
+
"heading-contract",
|
|
483
|
+
skillRelative,
|
|
484
|
+
`SKILL.md must contain H2 headings \`${useWhen}\` and \`${doNotUseWhen}\`, with \`${useWhen}\` before \`${doNotUseWhen}\`. Any other H2 headings are allowed, in any order.`,
|
|
485
|
+
));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
for (const heading of h2) {
|
|
489
|
+
const content = sectionText(body, heading).trim();
|
|
490
|
+
if (!content) diagnostics.push(diagnostic("heading-empty", skillRelative, `## ${heading.text} must not be empty.`));
|
|
491
|
+
}
|
|
492
|
+
const boundaries = h2.find((entry) => entry.text === "Boundaries");
|
|
493
|
+
if (boundaries) {
|
|
494
|
+
const nextH2 = h2.find((entry) => entry.line > boundaries.line);
|
|
495
|
+
const boundaryH3 = allHeadings
|
|
496
|
+
.filter((entry) => entry.level === 3 && entry.line > boundaries.line && (!nextH2 || entry.line < nextH2.line))
|
|
497
|
+
.map((entry) => entry.text);
|
|
498
|
+
if (boundaryH3.join("\u0000") !== REQUIRED_BOUNDARY_HEADINGS.join("\u0000")) {
|
|
499
|
+
diagnostics.push(diagnostic("boundaries-contract", skillRelative, `## Boundaries must contain exact H3 headings: ${REQUIRED_BOUNDARY_HEADINGS.join(" → ")}.`));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const completion = h2.find((entry) => entry.text === "Completion");
|
|
503
|
+
if (completion && !COMMAND_SHAPED_LINE.test(sectionText(body, completion))) {
|
|
504
|
+
diagnostics.push(diagnostic("completion-proof", skillRelative, "## Completion requires a command-shaped proof line."));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const storageCount = body.split(CANONICAL_FACTORY_STORAGE).length - 1;
|
|
508
|
+
const factoryPathMentioned = /~\/\.enact\/factory\/projects\/[A-Za-z0-9_<>.-]+\//.test(body);
|
|
509
|
+
if (storageCount > 1) {
|
|
510
|
+
diagnostics.push(diagnostic("storage-duplicate", skillRelative, "The canonical Factory storage sentence may appear at most once."));
|
|
511
|
+
}
|
|
512
|
+
const h1Line = h1[0]?.line;
|
|
513
|
+
const firstLineAfterH1 = h1Line === undefined
|
|
514
|
+
? null
|
|
515
|
+
: body.split(/\r?\n/).slice(h1Line).find((line) => line.trim())?.trim() ?? null;
|
|
516
|
+
if (factoryPathMentioned && (storageCount !== 1 || firstLineAfterH1 !== CANONICAL_FACTORY_STORAGE)) {
|
|
517
|
+
diagnostics.push(diagnostic("storage-contract", skillRelative, `Factory path users must place exactly this sentence immediately after the H1: ${CANONICAL_FACTORY_STORAGE}`));
|
|
518
|
+
}
|
|
519
|
+
const noncanonicalStorage = withoutFencedCode(body)
|
|
520
|
+
.replaceAll(CANONICAL_FACTORY_STORAGE, "")
|
|
521
|
+
.replace(/`[^`]*`/g, "")
|
|
522
|
+
.match(/~\/\.enact\/factory\/projects\/|\bfactory\s+(?:artifacts?|storage|workspace)\s+(?:are|is|go|live|reside|belong|located|stored)\b|\b(?:store|write|save)\b[^\n.]{0,100}\b~\/\.enact\b/i);
|
|
523
|
+
if (noncanonicalStorage) {
|
|
524
|
+
diagnostics.push(diagnostic("storage-noncanonical", skillRelative, "Use only the canonical Factory storage sentence."));
|
|
525
|
+
}
|
|
526
|
+
validateSlop(body, skillRelative, diagnostics);
|
|
527
|
+
|
|
528
|
+
const resourceByRelative = new Map(resources.map((resource) => [resource.relative, resource]));
|
|
529
|
+
const reachable = new Set(["SKILL.md"]);
|
|
530
|
+
const pending = [{ relative: "SKILL.md", fullRelative: skillRelative, path: skillPath, text }];
|
|
531
|
+
const missingResources = new Set();
|
|
532
|
+
while (pending.length > 0) {
|
|
533
|
+
const current = pending.pop();
|
|
534
|
+
const currentText = current.text ?? textResource(current.path);
|
|
535
|
+
if (currentText === null || currentText === undefined) continue;
|
|
536
|
+
for (const reference of resourceReferences(currentText, resourceByRelative.keys())) {
|
|
537
|
+
const resource = resourceByRelative.get(reference);
|
|
538
|
+
if (!resource) {
|
|
539
|
+
const key = `${current.relative}\u0000${reference}`;
|
|
540
|
+
if (!missingResources.has(key)) {
|
|
541
|
+
diagnostics.push(diagnostic("resource-missing", current.fullRelative, `Local resource reference does not exist: ${reference}.`));
|
|
542
|
+
missingResources.add(key);
|
|
543
|
+
}
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (reachable.has(reference)) continue;
|
|
547
|
+
reachable.add(reference);
|
|
548
|
+
pending.push({ relative: reference, fullRelative: resource.fullRelative, path: resource.absolute });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
for (const resource of resources) {
|
|
552
|
+
const resourceText = textResource(resource.absolute);
|
|
553
|
+
if (/\.md$/i.test(resource.relative) && resourceText !== null && lineCount(resourceText) > 300) {
|
|
554
|
+
diagnostics.push(diagnostic("resource-line-limit", resource.fullRelative, `Markdown resource has ${lineCount(resourceText)} lines; maximum is 300.`));
|
|
555
|
+
}
|
|
556
|
+
if (/\.md$/i.test(resource.relative) && resourceText !== null) {
|
|
557
|
+
validateSlop(resourceText, resource.fullRelative, diagnostics);
|
|
558
|
+
}
|
|
559
|
+
if (!reachable.has(resource.relative)) {
|
|
560
|
+
diagnostics.push(diagnostic("resource-orphan", resource.fullRelative, "Resource is not reachable from SKILL.md."));
|
|
561
|
+
}
|
|
562
|
+
if (resource.relative.startsWith("scripts/")) validateScript(resource.absolute, resource.fullRelative, diagnostics);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
diagnostics,
|
|
567
|
+
skillName,
|
|
568
|
+
path: skillRelative,
|
|
569
|
+
paragraphs: normalizedParagraphs(body).map((paragraph) => ({ paragraph, path: skillRelative })),
|
|
570
|
+
metadata: parsed.metadata,
|
|
571
|
+
body,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function manifestPaths(pluginRoot, manifest, pluginRelative, diagnostics) {
|
|
576
|
+
const allowed = new Set([".agents/plugin.json", "README.md"]);
|
|
577
|
+
const paths = [];
|
|
578
|
+
for (const field of COMPONENT_FIELDS) {
|
|
579
|
+
const raw = manifest[field];
|
|
580
|
+
if (raw === undefined) continue;
|
|
581
|
+
const resolved = safeRelative(pluginRoot, raw);
|
|
582
|
+
if (!resolved) {
|
|
583
|
+
diagnostics.push(diagnostic("manifest-path", `${pluginRelative}/.agents/plugin.json`, `${field} must be a safe plugin-relative path.`));
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (!existsSync(resolved.absolute)) {
|
|
587
|
+
diagnostics.push(diagnostic("manifest-path-missing", `${pluginRelative}/.agents/plugin.json`, `${field} path does not exist: ${raw}`));
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
paths.push(resolved.relative);
|
|
591
|
+
}
|
|
592
|
+
const iface = manifest.interface;
|
|
593
|
+
if (iface && typeof iface === "object" && !Array.isArray(iface)) {
|
|
594
|
+
for (const field of ["composerIcon", "logo"]) {
|
|
595
|
+
const raw = iface[field];
|
|
596
|
+
if (raw === undefined) continue;
|
|
597
|
+
const resolved = safeRelative(pluginRoot, raw);
|
|
598
|
+
if (!resolved || !isFile(resolved.absolute)) {
|
|
599
|
+
diagnostics.push(diagnostic("manifest-asset", `${pluginRelative}/.agents/plugin.json`, `${field} must reference an existing plugin-relative file.`));
|
|
600
|
+
} else {
|
|
601
|
+
paths.push(resolved.relative);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (manifest.assets !== undefined) {
|
|
606
|
+
if (!Array.isArray(manifest.assets)) {
|
|
607
|
+
diagnostics.push(diagnostic("assets-path", `${pluginRelative}/.agents/plugin.json`, "assets must be an array of plugin-relative paths."));
|
|
608
|
+
} else {
|
|
609
|
+
for (const raw of manifest.assets) {
|
|
610
|
+
const resolved = safeRelative(pluginRoot, raw);
|
|
611
|
+
if (!resolved || !existsSync(resolved.absolute)) {
|
|
612
|
+
diagnostics.push(diagnostic("assets-path", `${pluginRelative}/.agents/plugin.json`, `assets entry must be a safe, existing plugin-relative path: ${JSON.stringify(raw)}`));
|
|
613
|
+
} else {
|
|
614
|
+
paths.push(resolved.relative);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (manifest.principles !== undefined) {
|
|
620
|
+
const resolved = safeRelative(pluginRoot, manifest.principles);
|
|
621
|
+
if (!resolved || !isFile(resolved.absolute)) {
|
|
622
|
+
diagnostics.push(diagnostic("principles-config", `${pluginRelative}/.agents/plugin.json`, "principles must reference an existing plugin-relative config file."));
|
|
623
|
+
} else {
|
|
624
|
+
paths.push(resolved.relative);
|
|
625
|
+
try {
|
|
626
|
+
const config = JSON.parse(readFileSync(resolved.absolute, "utf8"));
|
|
627
|
+
if (!Array.isArray(config.fragments)) {
|
|
628
|
+
diagnostics.push(diagnostic("principles-config", `${pluginRelative}/${resolved.relative}`, "Prompt config must declare a fragments array."));
|
|
629
|
+
} else {
|
|
630
|
+
for (const fragment of config.fragments) {
|
|
631
|
+
const fragmentPath = safeRelative(dirname(resolved.absolute), fragment?.file);
|
|
632
|
+
if (!fragmentPath || !isFile(fragmentPath.absolute) || !isWithin(pluginRoot, fragmentPath.absolute)) {
|
|
633
|
+
diagnostics.push(diagnostic("principles-fragment", `${pluginRelative}/${resolved.relative}`, "Each prompt fragment must reference an existing file inside the plugin."));
|
|
634
|
+
} else {
|
|
635
|
+
paths.push(toPosix(relative(pluginRoot, fragmentPath.absolute)));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
} catch (err) {
|
|
640
|
+
diagnostics.push(diagnostic("principles-config", `${pluginRelative}/${resolved.relative}`, `Cannot parse prompt config: ${err instanceof Error ? err.message : String(err)}`));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (typeof manifest.validate === "string") {
|
|
645
|
+
const localScript = /(?:^|\s)(?:node\s+)?(?:\.\/)?([A-Za-z0-9_./-]+\.(?:mjs|cjs|js|py|sh))(?:\s|$)/.exec(manifest.validate);
|
|
646
|
+
if (localScript) {
|
|
647
|
+
const resolved = safeRelative(pluginRoot, localScript[1]);
|
|
648
|
+
if (!resolved || !isFile(resolved.absolute)) {
|
|
649
|
+
diagnostics.push(diagnostic("manifest-validate-script", `${pluginRelative}/.agents/plugin.json`, `validate references a missing local script: ${localScript[1]}`));
|
|
650
|
+
} else {
|
|
651
|
+
paths.push(resolved.relative);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
for (const path of paths) allowed.add(path);
|
|
656
|
+
return allowed;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function validatePluginRoot(pluginRoot, pluginName, tracked, diagnostics, schemaRoot) {
|
|
660
|
+
const pluginRelative = `extensions/${pluginName}`;
|
|
661
|
+
const manifestPath = join(pluginRoot, ".agents", "plugin.json");
|
|
662
|
+
if (!isFile(manifestPath)) {
|
|
663
|
+
diagnostics.push(diagnostic("manifest-missing", `${pluginRelative}/.agents/plugin.json`, "Plugin requires a canonical .agents/plugin.json."));
|
|
664
|
+
return { skills: [] };
|
|
665
|
+
}
|
|
666
|
+
let manifest;
|
|
667
|
+
try {
|
|
668
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
669
|
+
} catch (err) {
|
|
670
|
+
diagnostics.push(diagnostic("manifest-invalid-json", `${pluginRelative}/.agents/plugin.json`, err instanceof Error ? err.message : String(err)));
|
|
671
|
+
return { skills: [] };
|
|
672
|
+
}
|
|
673
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
674
|
+
diagnostics.push(diagnostic("manifest-shape", `${pluginRelative}/.agents/plugin.json`, "Plugin manifest must be an object."));
|
|
675
|
+
return { skills: [] };
|
|
676
|
+
}
|
|
677
|
+
validateCanonicalManifest(schemaRoot, manifest, pluginRelative, diagnostics);
|
|
678
|
+
const requiredTargets = Array.isArray(manifest.targets) && manifest.targets.at(-1) === OPT_IN_TARGET
|
|
679
|
+
? manifest.targets.slice(0, -1)
|
|
680
|
+
: manifest.targets;
|
|
681
|
+
if (!Array.isArray(requiredTargets) || requiredTargets.join("\u0000") !== ALL_SURFACE_TARGETS.join("\u0000")) {
|
|
682
|
+
diagnostics.push(diagnostic("manifest-targets", `${pluginRelative}/.agents/plugin.json`, `targets must be exactly ${JSON.stringify(ALL_SURFACE_TARGETS)}, optionally followed by "${OPT_IN_TARGET}".`));
|
|
683
|
+
}
|
|
684
|
+
if (typeof manifest.version !== "string" || !/^\d+\.\d+\.\d+$/.test(manifest.version)) {
|
|
685
|
+
diagnostics.push(diagnostic("manifest-version", `${pluginRelative}/.agents/plugin.json`, "version must be semantic MAJOR.MINOR.PATCH."));
|
|
686
|
+
}
|
|
687
|
+
const allowed = manifestPaths(pluginRoot, manifest, pluginRelative, diagnostics);
|
|
688
|
+
const files = tracked ?? listFiles(pluginRoot).map((file) => `${pluginRelative}/${file.relative}`);
|
|
689
|
+
const extras = new Set();
|
|
690
|
+
for (const path of files) {
|
|
691
|
+
if (!path.startsWith(`${pluginRelative}/`)) continue;
|
|
692
|
+
const rel = path.slice(pluginRelative.length + 1);
|
|
693
|
+
const isAllowed = [...allowed].some((prefix) => rel === prefix || rel.startsWith(`${prefix}/`));
|
|
694
|
+
if (!isAllowed) extras.add(rel.split("/")[0]);
|
|
695
|
+
}
|
|
696
|
+
for (const extra of [...extras].sort()) {
|
|
697
|
+
diagnostics.push(diagnostic("plugin-root-entry", `${pluginRelative}/${extra}`, "Tracked plugin-root content must be manifest-declared, README.md, or an exact manifest asset reference."));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const skillResults = [];
|
|
701
|
+
const skillsRoot = join(pluginRoot, "skills");
|
|
702
|
+
if (isDirectory(skillsRoot)) {
|
|
703
|
+
for (const entry of readdirSync(skillsRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
704
|
+
if (isIgnoredNoise(entry.name)) continue;
|
|
705
|
+
if (entry.name.startsWith(".")) {
|
|
706
|
+
diagnostics.push(diagnostic("skills-hidden-entry", `${pluginRelative}/skills/${entry.name}`, "skills/ may not contain hidden entries."));
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (!entry.isDirectory()) {
|
|
710
|
+
diagnostics.push(diagnostic("skills-loose-file", `${pluginRelative}/skills/${entry.name}`, "skills/ may contain only skill directories."));
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
skillResults.push(validateSkill(join(skillsRoot, entry.name), pluginName));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
validateSkillRoutes(skillResults, diagnostics);
|
|
717
|
+
return { skills: skillResults };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function parseVersion(text) {
|
|
721
|
+
try {
|
|
722
|
+
const value = JSON.parse(text)?.version;
|
|
723
|
+
if (typeof value !== "string" || !/^\d+\.\d+\.\d+$/.test(value)) return null;
|
|
724
|
+
return value.split(".").map(Number);
|
|
725
|
+
} catch {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function manifestWithoutVersion(text) {
|
|
731
|
+
try {
|
|
732
|
+
const manifest = JSON.parse(text);
|
|
733
|
+
delete manifest.version;
|
|
734
|
+
return JSON.stringify(manifest);
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function versionIncreased(before, after) {
|
|
741
|
+
for (let index = 0; index < before.length; index += 1) {
|
|
742
|
+
if (after[index] !== before[index]) return after[index] > before[index];
|
|
743
|
+
}
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export function validateVersionChanges({ repoRoot, currentRoot, base, staged = false }) {
|
|
748
|
+
const diagnostics = [];
|
|
749
|
+
const changed = changedFiles(repoRoot, base, staged);
|
|
750
|
+
const pluginNames = new Set(
|
|
751
|
+
changed
|
|
752
|
+
.map((path) => /^extensions\/([^/]+)\//.exec(path)?.[1])
|
|
753
|
+
.filter(Boolean),
|
|
754
|
+
);
|
|
755
|
+
for (const pluginName of pluginNames) {
|
|
756
|
+
const prefix = `extensions/${pluginName}/`;
|
|
757
|
+
const manifestRel = `${prefix}.agents/plugin.json`;
|
|
758
|
+
const pluginChanges = changed.filter((path) => path.startsWith(prefix));
|
|
759
|
+
const previousManifest = gitFile(repoRoot, base, manifestRel);
|
|
760
|
+
const currentManifestPath = join(currentRoot, manifestRel);
|
|
761
|
+
const currentManifest = isFile(currentManifestPath) ? readFileSync(currentManifestPath, "utf8") : null;
|
|
762
|
+
if (!previousManifest || !currentManifest) continue;
|
|
763
|
+
const nonVersionPluginChange = pluginChanges.some((path) => path !== manifestRel) ||
|
|
764
|
+
manifestWithoutVersion(previousManifest) !== manifestWithoutVersion(currentManifest);
|
|
765
|
+
if (!nonVersionPluginChange) continue;
|
|
766
|
+
const before = parseVersion(previousManifest);
|
|
767
|
+
const after = parseVersion(currentManifest);
|
|
768
|
+
if (!before || !after || !versionIncreased(before, after)) {
|
|
769
|
+
diagnostics.push(diagnostic("plugin-version-not-bumped", manifestRel, `Plugin content changed but version did not increase from ${before?.join(".") ?? "invalid"} to ${after?.join(".") ?? "invalid"}.`));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return diagnostics;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Bundles kept in `extensions/` for REFERENCE only -- never installed, so their
|
|
776
|
+
// skill names cannot collide with anything at runtime. `engineering` is a fork
|
|
777
|
+
// of a subset of enact-repo's catalog, retained to compare against; it shares 19
|
|
778
|
+
// skill names with enact-repo by construction and exempting it is what makes the
|
|
779
|
+
// global-uniqueness rule enforceable rather than permanently red.
|
|
780
|
+
const REFERENCE_ONLY_PLUGINS = new Set(["engineering"]);
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* One skill name, one owner, across every installable extension.
|
|
784
|
+
*
|
|
785
|
+
* Skill identity is the directory name on every host, and directory nesting
|
|
786
|
+
* does NOT namespace it (Cursor: "The category folder is purely organizational";
|
|
787
|
+
* OpenCode v2 derives the id from the last path segment). Where a host reads a
|
|
788
|
+
* flat skills directory, two extensions shipping one name silently shadow each
|
|
789
|
+
* other. Keeping names globally unique is the only mitigation that works
|
|
790
|
+
* everywhere, so it is enforced at authoring time rather than discovered at
|
|
791
|
+
* install time.
|
|
792
|
+
*/
|
|
793
|
+
function pushSkillNameCollisionDiagnostics(skillOwners, diagnostics) {
|
|
794
|
+
for (const [skillName, owners] of skillOwners) {
|
|
795
|
+
if (owners.length < 2) continue;
|
|
796
|
+
const plugins = owners.map((o) => o.plugin).sort();
|
|
797
|
+
for (const owner of owners) {
|
|
798
|
+
diagnostics.push(
|
|
799
|
+
diagnostic(
|
|
800
|
+
"skill-name-collision",
|
|
801
|
+
owner.path,
|
|
802
|
+
`Skill name ${JSON.stringify(skillName)} is shipped by ${plugins.length} extensions (${plugins.join(", ")}). ` +
|
|
803
|
+
`Skill names are a global namespace -- on a host that reads a flat skills directory one silently shadows the other. Rename all but one.`,
|
|
804
|
+
),
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
export function validateRepository({ root, tracked = null }) {
|
|
811
|
+
const diagnostics = [];
|
|
812
|
+
const extensionsRoot = join(root, "extensions");
|
|
813
|
+
if (!isDirectory(extensionsRoot)) {
|
|
814
|
+
return [diagnostic("extensions-missing", "extensions", "Repository requires an extensions directory.")];
|
|
815
|
+
}
|
|
816
|
+
const presentTracked = tracked?.filter((path) => isFile(join(root, path))) ?? null;
|
|
817
|
+
const allParagraphs = new Map();
|
|
818
|
+
// Skill directory name -> the plugins shipping it. A skill name is a GLOBAL
|
|
819
|
+
// identifier: hosts that read a flat skills directory (OpenCode reads
|
|
820
|
+
// `.opencode/skills/<name>/SKILL.md`) key on the directory name alone, and
|
|
821
|
+
// nesting does not namespace it. Two extensions shipping one name there is
|
|
822
|
+
// last-writer-wins, silently.
|
|
823
|
+
const skillOwners = new Map();
|
|
824
|
+
for (const entry of readdirSync(extensionsRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
825
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
826
|
+
const plugin = validatePluginRoot(join(extensionsRoot, entry.name), entry.name, presentTracked, diagnostics, root);
|
|
827
|
+
for (const skill of plugin.skills) {
|
|
828
|
+
diagnostics.push(...skill.diagnostics);
|
|
829
|
+
if (!REFERENCE_ONLY_PLUGINS.has(entry.name) && typeof skill.skillName === "string") {
|
|
830
|
+
const owners = skillOwners.get(skill.skillName) ?? [];
|
|
831
|
+
owners.push({ plugin: entry.name, path: skill.path });
|
|
832
|
+
skillOwners.set(skill.skillName, owners);
|
|
833
|
+
}
|
|
834
|
+
for (const record of skill.paragraphs) {
|
|
835
|
+
const existing = allParagraphs.get(record.paragraph) ?? [];
|
|
836
|
+
existing.push(record.path);
|
|
837
|
+
allParagraphs.set(record.paragraph, existing);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
pushSkillNameCollisionDiagnostics(skillOwners, diagnostics);
|
|
842
|
+
pushDuplicateParagraphDiagnostics(allParagraphs, diagnostics);
|
|
843
|
+
return diagnostics.sort((a, b) => a.path.localeCompare(b.path) || a.code.localeCompare(b.code));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export function validatePlugin({ pluginRoot, tracked = null }) {
|
|
847
|
+
const root = findGitRoot(pluginRoot);
|
|
848
|
+
const pluginName = basename(pluginRoot);
|
|
849
|
+
const repoRelative = root ? toPosix(relative(root, pluginRoot)) : null;
|
|
850
|
+
const selectedTracked = tracked ?? (root && repoRelative?.startsWith("extensions/")
|
|
851
|
+
? trackedFiles(root)
|
|
852
|
+
.filter((path) => path.startsWith(`${repoRelative}/`))
|
|
853
|
+
.filter((path) => isFile(join(root, path)))
|
|
854
|
+
: null);
|
|
855
|
+
const diagnostics = [];
|
|
856
|
+
const result = validatePluginRoot(pluginRoot, pluginName, selectedTracked, diagnostics, VALIDATOR_ROOT);
|
|
857
|
+
for (const skill of result.skills) diagnostics.push(...skill.diagnostics);
|
|
858
|
+
const paragraphs = new Map();
|
|
859
|
+
for (const skill of result.skills) {
|
|
860
|
+
for (const record of skill.paragraphs) {
|
|
861
|
+
const existing = paragraphs.get(record.paragraph) ?? [];
|
|
862
|
+
existing.push(record.path);
|
|
863
|
+
paragraphs.set(record.paragraph, existing);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
pushDuplicateParagraphDiagnostics(paragraphs, diagnostics);
|
|
867
|
+
return diagnostics.sort((a, b) => a.path.localeCompare(b.path) || a.code.localeCompare(b.code));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export function formatDiagnostics(diagnostics) {
|
|
871
|
+
return diagnostics.map((item) => `[${item.code}] ${item.path}: ${item.message}`);
|
|
872
|
+
}
|