@fusengine/harness 0.1.90 → 0.1.91
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/dist/adapters/claude/index.d.mts +1 -1
- package/dist/adapters/claude/index.mjs +1 -1
- package/dist/adapters/cline/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +2 -2
- package/dist/adapters/codex/index.mjs +3 -2
- package/dist/adapters/cursor/index.d.mts +15 -28
- package/dist/adapters/cursor/index.mjs +35 -46
- package/dist/adapters/gemini/index.mjs +1 -1
- package/dist/adapters/hermes/index.d.mts +1 -1
- package/dist/adapters/hermes/index.mjs +1 -1
- package/dist/adapters/kimi/index.d.mts +1 -1
- package/dist/adapters/kimi/index.mjs +1 -1
- package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
- package/dist/cli/bin.mjs +429 -16
- package/dist/cli/index.mjs +1 -1
- package/dist/command-string-CALMTnwN.mjs +42 -0
- package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
- package/dist/{handle-CBaOcFZq.mjs → handle-C43gA-Pr.mjs} +701 -134
- package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
- package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
- package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/init/index.d.mts +1 -1
- package/dist/init/index.mjs +1 -1
- package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
- package/dist/normalize-BjG6unTj.mjs +317 -0
- package/dist/policy/index.d.mts +1 -1
- package/dist/policy/index.mjs +2 -2
- package/dist/prompt/index.d.mts +1 -1
- package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
- package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
- package/dist/runtime/index.d.mts +27 -16
- package/dist/runtime/index.mjs +2 -2
- package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
- package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
- package/package.json +3 -2
- package/src/adapters/claude/index.ts +92 -0
- package/src/adapters/cline/index.ts +36 -0
- package/src/adapters/codex/apply-patch.ts +94 -0
- package/src/adapters/codex/index.ts +84 -0
- package/src/adapters/codex/permission-mode.ts +16 -0
- package/src/adapters/cursor/context.ts +46 -0
- package/src/adapters/cursor/events.ts +38 -0
- package/src/adapters/cursor/index.ts +43 -0
- package/src/adapters/cursor/interfaces/types.ts +88 -0
- package/src/adapters/cursor/native-response.ts +158 -0
- package/src/adapters/cursor/normalize.ts +123 -0
- package/src/adapters/cursor/respond.ts +123 -0
- package/src/adapters/gemini/index.ts +36 -0
- package/src/adapters/hermes/index.ts +55 -0
- package/src/adapters/hermes/interfaces/types.ts +26 -0
- package/src/adapters/kimi/index.ts +96 -0
- package/src/adapters/kimi/interfaces/types.ts +55 -0
- package/src/cache/compact.ts +55 -0
- package/src/cache/index.ts +5 -0
- package/src/cache/io.ts +38 -0
- package/src/cache/mcp-response.ts +39 -0
- package/src/cache/mcp-store.ts +70 -0
- package/src/cache/store.ts +93 -0
- package/src/changelog/fetch.ts +79 -0
- package/src/cli/bin.ts +100 -0
- package/src/cli/cursor-event-scanner.ts +175 -0
- package/src/cli/cursor-stdin-reader.ts +65 -0
- package/src/cli/doctor.ts +96 -0
- package/src/cli/hook-io.ts +130 -0
- package/src/cli/hook-sound.ts +49 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/json-primitive-scanner.ts +83 -0
- package/src/cli/run.ts +36 -0
- package/src/cli/scope.ts +31 -0
- package/src/codex-rules/build.ts +63 -0
- package/src/codex-rules/index.ts +8 -0
- package/src/codex-rules/mapping.ts +42 -0
- package/src/codex-rules/rules/absolute-paths.ts +52 -0
- package/src/codex-rules/rules/ask.ts +44 -0
- package/src/codex-rules/rules/chmod-ask.ts +57 -0
- package/src/codex-rules/rules/dd.ts +23 -0
- package/src/codex-rules/rules/diskutil.ts +33 -0
- package/src/codex-rules/rules/mkfs.ts +42 -0
- package/src/codex-rules/rules/privilege.ts +49 -0
- package/src/codex-rules/rules/remote-fetch.ts +31 -0
- package/src/codex-rules/rules/rm-variants.ts +58 -0
- package/src/codex-rules/rules/single-commands.ts +57 -0
- package/src/codex-rules/skip-list.ts +25 -0
- package/src/codex-rules/starlark.ts +46 -0
- package/src/codex-rules/types.ts +36 -0
- package/src/config/dotenv.ts +55 -0
- package/src/config/env.ts +10 -0
- package/src/config/home-dir.ts +36 -0
- package/src/config/index.ts +5 -0
- package/src/config/layout.ts +52 -0
- package/src/config/limits.ts +56 -0
- package/src/config/ttl.ts +30 -0
- package/src/detect/harness.ts +73 -0
- package/src/detect/index.ts +1 -0
- package/src/detect/interfaces/types.ts +18 -0
- package/src/freshness/agent-evidence-record.ts +95 -0
- package/src/freshness/agent-evidence.ts +95 -0
- package/src/freshness/codex-spawn-evidence.ts +91 -0
- package/src/freshness/doc-helpers.ts +90 -0
- package/src/freshness/evidence-harvest-io.ts +72 -0
- package/src/freshness/evidence-harvest.ts +61 -0
- package/src/freshness/explore-tools.ts +95 -0
- package/src/freshness/index.ts +2 -0
- package/src/freshness/query-framework.ts +73 -0
- package/src/freshness/ref-evidence.ts +49 -0
- package/src/freshness/ref-journal.ts +98 -0
- package/src/freshness/trivial-edit-counter.ts +30 -0
- package/src/index.ts +11 -0
- package/src/init/index.ts +2 -0
- package/src/init/run.ts +32 -0
- package/src/init/templates.ts +75 -0
- package/src/memory/gitignore.ts +14 -0
- package/src/memory/index.ts +3 -0
- package/src/memory/registry.ts +39 -0
- package/src/memory/session-roots.ts +99 -0
- package/src/memory/state.ts +58 -0
- package/src/policy/apex-authorization.ts +100 -0
- package/src/policy/apex-gates.ts +91 -0
- package/src/policy/apex-target-steps.ts +73 -0
- package/src/policy/apex-target.ts +49 -0
- package/src/policy/apex-task-context.ts +81 -0
- package/src/policy/apex.ts +94 -0
- package/src/policy/cartographer/build-tree.ts +69 -0
- package/src/policy/cartographer/describe.ts +53 -0
- package/src/policy/cartographer/entry.ts +38 -0
- package/src/policy/cartographer/frontmatter.ts +48 -0
- package/src/policy/cartographer/indicators.ts +34 -0
- package/src/policy/claude-md-context.ts +66 -0
- package/src/policy/conventions/index.ts +11 -0
- package/src/policy/conventions/interfaces.ts +71 -0
- package/src/policy/conventions/langs.ts +53 -0
- package/src/policy/conventions/query.ts +49 -0
- package/src/policy/conventions/react-hooks.ts +18 -0
- package/src/policy/conventions/store-or-query.ts +21 -0
- package/src/policy/conventions/stores.ts +56 -0
- package/src/policy/conventions/strip.ts +87 -0
- package/src/policy/conventions/verdict.ts +38 -0
- package/src/policy/creation-intent.ts +11 -0
- package/src/policy/deny-loop.ts +88 -0
- package/src/policy/design/allowed-write.ts +28 -0
- package/src/policy/design/content-checks.ts +45 -0
- package/src/policy/design/corpus-fs.ts +21 -0
- package/src/policy/design/corpus-resolve-cache.ts +49 -0
- package/src/policy/design/corpus-resolve.ts +99 -0
- package/src/policy/design/corpus.ts +92 -0
- package/src/policy/design/design-system-rules.ts +45 -0
- package/src/policy/design/flag.ts +30 -0
- package/src/policy/design/gates-pipeline.ts +105 -0
- package/src/policy/design/gates.ts +87 -0
- package/src/policy/design/screenshot-tools.ts +24 -0
- package/src/policy/design/skill-evidence.ts +34 -0
- package/src/policy/design/skill-gate.ts +81 -0
- package/src/policy/design/skill-triggers.ts +51 -0
- package/src/policy/design/state.ts +92 -0
- package/src/policy/design/template-urls.ts +54 -0
- package/src/policy/design/transitions.ts +68 -0
- package/src/policy/detect-claude-md-project-type.ts +32 -0
- package/src/policy/detect-framework.ts +99 -0
- package/src/policy/detect-primitive-checks.ts +100 -0
- package/src/policy/detect-primitive-lib.ts +55 -0
- package/src/policy/detect-project.ts +99 -0
- package/src/policy/edit-outcome.ts +53 -0
- package/src/policy/evaluate.ts +81 -0
- package/src/policy/expert-agents.ts +60 -0
- package/src/policy/file-size-scope.ts +32 -0
- package/src/policy/file-size.ts +99 -0
- package/src/policy/framework-solid-exclude.ts +52 -0
- package/src/policy/framework-solid-extended.ts +93 -0
- package/src/policy/framework-solid-gates-systems.ts +99 -0
- package/src/policy/framework-solid-gates.ts +98 -0
- package/src/policy/framework-solid.ts +88 -0
- package/src/policy/gemini-mcp-gate.ts +83 -0
- package/src/policy/git-gates.ts +40 -0
- package/src/policy/guards/bash-command-anchor.ts +40 -0
- package/src/policy/guards/bash-write-patterns.ts +140 -0
- package/src/policy/guards/bash-write-redirects.ts +201 -0
- package/src/policy/guards/bash-write-safe-paths.ts +62 -0
- package/src/policy/guards/bash-write.ts +76 -0
- package/src/policy/guards/context.ts +12 -0
- package/src/policy/guards/index.ts +61 -0
- package/src/policy/guards/install.ts +22 -0
- package/src/policy/guards/interface-separation-ext.ts +41 -0
- package/src/policy/guards/interface-separation.ts +100 -0
- package/src/policy/guards/protected-path.ts +100 -0
- package/src/policy/guards/security.ts +97 -0
- package/src/policy/index.ts +19 -0
- package/src/policy/interfaces/types.ts +46 -0
- package/src/policy/js-gate-route.ts +51 -0
- package/src/policy/lessons/lesson-gate.ts +0 -0
- package/src/policy/lessons/trigger-index.ts +84 -0
- package/src/policy/lessons/types.ts +31 -0
- package/src/policy/module-layout.ts +31 -0
- package/src/policy/nearest-manifest.ts +75 -0
- package/src/policy/never-approval.ts +95 -0
- package/src/policy/patterns.ts +80 -0
- package/src/policy/seo/validate.ts +51 -0
- package/src/policy/shadcn-project.ts +22 -0
- package/src/policy/shadcn-skill-gate.ts +97 -0
- package/src/policy/shell-read-refs.ts +96 -0
- package/src/policy/skill-path.ts +46 -0
- package/src/policy/skill-patterns/laravel.ts +40 -0
- package/src/policy/skill-patterns/nextjs.ts +29 -0
- package/src/policy/skill-patterns/react.ts +27 -0
- package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
- package/src/policy/skill-patterns/shadcn.ts +66 -0
- package/src/policy/skill-patterns/swift.ts +30 -0
- package/src/policy/skill-patterns/tailwind.ts +40 -0
- package/src/policy/skill-source.ts +52 -0
- package/src/policy/skill-trigger-patterns.ts +33 -0
- package/src/policy/skill-triggers.ts +108 -0
- package/src/policy/tailwind-skill-gate.ts +63 -0
- package/src/policy/trivial-edits.ts +52 -0
- package/src/policy/verbosity.ts +39 -0
- package/src/prompt/index.ts +1 -0
- package/src/prompt/types.ts +36 -0
- package/src/refs/discover.ts +69 -0
- package/src/refs/frontmatter.ts +24 -0
- package/src/refs/index.ts +4 -0
- package/src/refs/loader.ts +59 -0
- package/src/refs/ref-key.ts +41 -0
- package/src/refs/router.ts +63 -0
- package/src/refs/types.ts +25 -0
- package/src/runtime/activity.ts +99 -0
- package/src/runtime/apply-patch-gate.ts +41 -0
- package/src/runtime/burst-window.ts +21 -0
- package/src/runtime/codex-shell-tool.ts +37 -0
- package/src/runtime/command-string.ts +37 -0
- package/src/runtime/confirm/codex-confirm.ts +126 -0
- package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
- package/src/runtime/confirm/confirm-code.ts +33 -0
- package/src/runtime/confirm/confirm-gate.ts +65 -0
- package/src/runtime/confirm/confirm-irreversible.ts +32 -0
- package/src/runtime/confirm/confirm-pending.ts +34 -0
- package/src/runtime/confirm/confirm-state.ts +56 -0
- package/src/runtime/confirm/confirm-subagent.ts +112 -0
- package/src/runtime/confirm/confirm-submit.ts +38 -0
- package/src/runtime/core-guards-active.ts +84 -0
- package/src/runtime/core-guards-wired.ts +47 -0
- package/src/runtime/deny-loop-store.ts +94 -0
- package/src/runtime/deny-notice.ts +52 -0
- package/src/runtime/design-cache-resolve.ts +51 -0
- package/src/runtime/design-content-gate.ts +95 -0
- package/src/runtime/design-files-gate.ts +114 -0
- package/src/runtime/design-helpers.ts +93 -0
- package/src/runtime/design-lifecycle.ts +39 -0
- package/src/runtime/design-read-credit.ts +28 -0
- package/src/runtime/design.ts +116 -0
- package/src/runtime/dev-context.ts +43 -0
- package/src/runtime/dry-patterns.ts +32 -0
- package/src/runtime/dry.ts +96 -0
- package/src/runtime/fragment-registry.ts +33 -0
- package/src/runtime/framework-skill-gate.ts +79 -0
- package/src/runtime/fs-cleanup.ts +71 -0
- package/src/runtime/gate-apex.ts +81 -0
- package/src/runtime/gate-helpers.ts +52 -0
- package/src/runtime/gate-input.ts +26 -0
- package/src/runtime/gate.ts +128 -0
- package/src/runtime/handle-post.ts +104 -0
- package/src/runtime/handle-pre.ts +137 -0
- package/src/runtime/handle-scope-async.ts +29 -0
- package/src/runtime/handle-types.ts +27 -0
- package/src/runtime/handle.ts +116 -0
- package/src/runtime/home-state.ts +55 -0
- package/src/runtime/index.ts +18 -0
- package/src/runtime/inform.ts +28 -0
- package/src/runtime/inject-budget-recap.ts +47 -0
- package/src/runtime/inject-budget.ts +81 -0
- package/src/runtime/inject-context.ts +85 -0
- package/src/runtime/inject-dedup-exclusive.ts +98 -0
- package/src/runtime/inject-dedup.ts +89 -0
- package/src/runtime/is-agent-tool.ts +19 -0
- package/src/runtime/lifecycle/agent-files.ts +49 -0
- package/src/runtime/lifecycle/agent-memory.ts +82 -0
- package/src/runtime/lifecycle/agent-transcript.ts +80 -0
- package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
- package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
- package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
- package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
- package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
- package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
- package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
- package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
- package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
- package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
- package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
- package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
- package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
- package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
- package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
- package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
- package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
- package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
- package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
- package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
- package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
- package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
- package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
- package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
- package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
- package/src/runtime/lifecycle/aipilot/types.ts +72 -0
- package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
- package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
- package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
- package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
- package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
- package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
- package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
- package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
- package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
- package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
- package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
- package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
- package/src/runtime/lifecycle/changelog-research.ts +43 -0
- package/src/runtime/lifecycle/check-file-size.ts +61 -0
- package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
- package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
- package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
- package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
- package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
- package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
- package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
- package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
- package/src/runtime/lifecycle/dispatch.ts +109 -0
- package/src/runtime/lifecycle/failure-lesson.ts +51 -0
- package/src/runtime/lifecycle/index.ts +34 -0
- package/src/runtime/lifecycle/inject-rules.ts +50 -0
- package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
- package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
- package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
- package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
- package/src/runtime/lifecycle/lessons/state.ts +23 -0
- package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
- package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
- package/src/runtime/lifecycle/memory/client.ts +65 -0
- package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
- package/src/runtime/lifecycle/memory/recall.ts +30 -0
- package/src/runtime/lifecycle/memory/salience.ts +31 -0
- package/src/runtime/lifecycle/memory/state.ts +49 -0
- package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
- package/src/runtime/lifecycle/post-compact.ts +38 -0
- package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
- package/src/runtime/lifecycle/post-tracking.ts +40 -0
- package/src/runtime/lifecycle/pre-compact.ts +38 -0
- package/src/runtime/lifecycle/rules-root.ts +83 -0
- package/src/runtime/lifecycle/security/check-skill.ts +63 -0
- package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
- package/src/runtime/lifecycle/security/scan.ts +76 -0
- package/src/runtime/lifecycle/security/skill-state.ts +47 -0
- package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
- package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
- package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
- package/src/runtime/lifecycle/session-end.ts +17 -0
- package/src/runtime/lifecycle/session-start.ts +57 -0
- package/src/runtime/lifecycle/snapshot/board.ts +27 -0
- package/src/runtime/lifecycle/snapshot/format.ts +60 -0
- package/src/runtime/lifecycle/snapshot/git.ts +71 -0
- package/src/runtime/lifecycle/snapshot/index.ts +52 -0
- package/src/runtime/lifecycle/snapshot/version.ts +34 -0
- package/src/runtime/lifecycle/solid-detect.ts +56 -0
- package/src/runtime/lifecycle/stop-core.ts +35 -0
- package/src/runtime/lifecycle/subagent-cache.ts +61 -0
- package/src/runtime/lifecycle/task-completed.ts +94 -0
- package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
- package/src/runtime/lifecycle/teammate-idle.ts +28 -0
- package/src/runtime/lifecycle/tool-failure.ts +24 -0
- package/src/runtime/lifecycle/track-changes.ts +61 -0
- package/src/runtime/lifecycle/validate-solid.ts +70 -0
- package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
- package/src/runtime/lifecycle-bridge.ts +74 -0
- package/src/runtime/mcp-key.ts +44 -0
- package/src/runtime/mcp-tool-name.ts +92 -0
- package/src/runtime/mcp.ts +91 -0
- package/src/runtime/modular.ts +77 -0
- package/src/runtime/normalize.ts +107 -0
- package/src/runtime/notices.ts +91 -0
- package/src/runtime/notification-sound.ts +49 -0
- package/src/runtime/notifications.ts +77 -0
- package/src/runtime/paths.ts +59 -0
- package/src/runtime/post-fanout.ts +41 -0
- package/src/runtime/post-outcome.ts +61 -0
- package/src/runtime/pre-allow.ts +97 -0
- package/src/runtime/precommit.ts +60 -0
- package/src/runtime/prompt-text.ts +38 -0
- package/src/runtime/receipt-capture.ts +34 -0
- package/src/runtime/record.ts +26 -0
- package/src/runtime/respond.ts +92 -0
- package/src/runtime/solid-file-size-gate.ts +58 -0
- package/src/runtime/solid-pre.ts +53 -0
- package/src/runtime/storage.ts +9 -0
- package/src/statusline/colors.ts +31 -0
- package/src/statusline/constants.ts +17 -0
- package/src/statusline/formatters.ts +54 -0
- package/src/statusline/index.ts +4 -0
- package/src/statusline/progress-bar.ts +46 -0
- package/src/tracking/codex-post-failure.ts +73 -0
- package/src/tracking/index.ts +2 -0
- package/src/tracking/integrity.ts +99 -0
- package/src/tracking/one-shot-dedup.ts +34 -0
- package/src/tracking/one-shot-failure.ts +42 -0
- package/src/tracking/one-shot-store.ts +95 -0
- package/src/tracking/one-shot.ts +93 -0
- package/src/tracking/receipts.ts +95 -0
- package/src/tracking/session-state.ts +94 -0
- package/src/tracking/store.ts +96 -0
- package/src/tracking/track-compact.ts +100 -0
- package/src/tracking/track-diff.ts +50 -0
- package/src/tracking/track-journal.ts +97 -0
- package/src/tracking/track-lock-sync.ts +83 -0
- package/src/tracking/track-lock.ts +67 -0
- package/src/util/compact-json.ts +23 -0
- package/src/util/index.ts +2 -0
- package/src/util/json-io.ts +47 -0
- package/src/util/project-root.ts +41 -0
- package/src/util/runtime-io.ts +85 -0
- package/src/util/semver.ts +30 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React skill-trigger patterns, ported verbatim from `react_skill_triggers.py`.
|
|
3
|
+
* Matched case-insensitively (source `re.IGNORECASE`).
|
|
4
|
+
*/
|
|
5
|
+
import { SHADCN } from "./shadcn";
|
|
6
|
+
|
|
7
|
+
/** Map of React sub-skill name → triggering code patterns. */
|
|
8
|
+
export const REACT_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
9
|
+
"react-19": ["\\buse\\b\\s*\\(", "useOptimistic\\b", "useActionState\\b",
|
|
10
|
+
"useEffectEvent\\b", "<Activity\\b", "from\\s+['\"]react['\"]"],
|
|
11
|
+
"react-tanstack-router": ["(createRouter|createRoute|createRootRoute)\\b",
|
|
12
|
+
"(useNavigate|useParams|useSearch|useLoaderData)\\b",
|
|
13
|
+
"from\\s+['\"]@tanstack/(react-router|router)",
|
|
14
|
+
"(routeTree|createFileRoute|createLazyFileRoute)\\b"],
|
|
15
|
+
"react-forms": ["(useForm|useAppForm|createFormHook|formOptions)\\b",
|
|
16
|
+
"(mergeForm|formApi|FieldApi|FormApi)\\b",
|
|
17
|
+
"form\\.(Field|Subscribe|handleSubmit)\\b",
|
|
18
|
+
"from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"],
|
|
19
|
+
"react-state": ["(create|createStore)\\(\\s*\\(\\s*set", "from\\s+['\"]zustand(/\\w+)?\"",
|
|
20
|
+
"(useShallow|useStore|skipHydration)\\b", "(persist|devtools|immer)\\("],
|
|
21
|
+
"react-testing": ["(render|screen|fireEvent|waitFor)\\b",
|
|
22
|
+
"from\\s+['\"]@testing-library/react",
|
|
23
|
+
"(describe|it|expect|vi\\.|jest\\.)\\b", "from\\s+['\"]vitest"],
|
|
24
|
+
"react-shadcn": SHADCN,
|
|
25
|
+
"react-i18n": ["(useTranslation|Trans)\\b", "from\\s+['\"]react-i18next",
|
|
26
|
+
"\\bt\\(\\s*['\"]", "i18n\\.(language|changeLanguage)"],
|
|
27
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadcn/ui domain skill-trigger patterns, ported verbatim from
|
|
3
|
+
* `shadcn_skill_triggers.py` (the standalone shadcn-expert plugin, distinct
|
|
4
|
+
* from the `SHADCN` HTML-detection patterns in `./shadcn.ts` consumed by
|
|
5
|
+
* react/nextjs). 5 sub-skills. Matched case-insensitively (source `re.IGNORECASE`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Map of shadcn/ui sub-skill name → triggering code patterns. */
|
|
9
|
+
export const SHADCN_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
10
|
+
"shadcn-detection": [
|
|
11
|
+
"components\\.json", "@radix-ui/", "@base-ui/",
|
|
12
|
+
"data-\\[state=", "data-\\[disabled\\]",
|
|
13
|
+
],
|
|
14
|
+
"shadcn-components": [
|
|
15
|
+
"from\\s+['\"].*components/ui/",
|
|
16
|
+
"<(Button|Input|Select|Dialog|Card|Table|Tabs|Badge)\\b",
|
|
17
|
+
"(Popover|Tooltip|Sheet|Drawer|Command|Accordion)\\b",
|
|
18
|
+
],
|
|
19
|
+
"shadcn-theming": [
|
|
20
|
+
"--(primary|secondary|muted|accent|destructive|foreground):",
|
|
21
|
+
"(cssVariables|themeConfig|globals\\.css)\\b",
|
|
22
|
+
":root\\s*\\{|\\.dark\\s*\\{",
|
|
23
|
+
],
|
|
24
|
+
"shadcn-registries": [
|
|
25
|
+
"mcp__shadcn__(search|view|get_add_command|list_items)",
|
|
26
|
+
"bunx.*shadcn@latest\\s+add\\b",
|
|
27
|
+
"(registries|@shadcn|@acme)\\b",
|
|
28
|
+
],
|
|
29
|
+
"shadcn-migration": [
|
|
30
|
+
"(@radix-ui.*@base-ui|@base-ui.*@radix-ui)",
|
|
31
|
+
"(migrat|convert|switch).*(radix|base.?ui)",
|
|
32
|
+
],
|
|
33
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadcn/ui HTML-to-component detection patterns, ported verbatim from the
|
|
3
|
+
* shared `shadcn_patterns.py` (FORM/OVERLAY/DATA/NAV/LAYOUT/FEEDBACK groups).
|
|
4
|
+
* 32 patterns total (3+6+8+5+5+5). Matched case-insensitively (source `re.IGNORECASE`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Forms: Button, Input, Textarea, Select, Checkbox, Radio, Switch, etc. */
|
|
8
|
+
const FORM: ReadonlyArray<string> = [
|
|
9
|
+
"<(button|input|select|textarea|label|option|optgroup)\\b",
|
|
10
|
+
"type=\"(checkbox|radio|range|file)\"",
|
|
11
|
+
"<input[^>]*maxLength=\"[1-2]\"",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Overlay: Dialog, AlertDialog, Sheet, Popover, Tooltip, ContextMenu, etc. */
|
|
15
|
+
const OVERLAY: ReadonlyArray<string> = [
|
|
16
|
+
"<dialog\\b",
|
|
17
|
+
"role=\"(dialog|alertdialog)\"",
|
|
18
|
+
"(aria-haspopup|aria-expanded|aria-pressed)=\"",
|
|
19
|
+
"(onContextMenu|onMouseEnter.*onMouseLeave)\\b",
|
|
20
|
+
"\\b(confirm|window\\.confirm)\\(",
|
|
21
|
+
"title=\"[^\"]{2,}\"",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/** Data: Table, Card, Badge, Avatar, Calendar, Chart, Carousel, Pagination. */
|
|
25
|
+
const DATA: ReadonlyArray<string> = [
|
|
26
|
+
"<(table|thead|tbody|tfoot|th|td|tr|caption|colgroup)\\b",
|
|
27
|
+
"<(article|section)\\b[^>]*className",
|
|
28
|
+
"rounded-full[^>]*className|className[^>]*rounded-full",
|
|
29
|
+
"<img\\b[^>]*rounded",
|
|
30
|
+
"(new Date|\\.toLocaleDateString|date-fns|dayjs)\\b",
|
|
31
|
+
"(recharts|chart\\.js|<svg[^>]*viewBox)",
|
|
32
|
+
"(scroll-snap|embla-carousel|useEmbla)\\b",
|
|
33
|
+
"(page=|currentPage|totalPages|pageSize)\\b",
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/** Navigation: Breadcrumb, NavigationMenu, Menubar, Sidebar, Tabs. */
|
|
37
|
+
const NAV: ReadonlyArray<string> = [
|
|
38
|
+
"<(nav|aside)\\b",
|
|
39
|
+
"<(menu|menuitem)\\b",
|
|
40
|
+
"role=\"(menubar|menu|menuitem|tablist|tab|tabpanel)\"",
|
|
41
|
+
"aria-label=\"(breadcrumb|navigation|sidebar)\"",
|
|
42
|
+
"aria-current=\"(page|step)\"",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Layout: Accordion, Collapsible, Separator, ScrollArea, AspectRatio. */
|
|
46
|
+
const LAYOUT: ReadonlyArray<string> = [
|
|
47
|
+
"<(hr|details|summary)\\b",
|
|
48
|
+
"role=\"separator\"",
|
|
49
|
+
"overflow-(auto|scroll|y-auto|x-auto)",
|
|
50
|
+
"(aspect-ratio|aspect-video|aspect-square)\\b",
|
|
51
|
+
"(resize|cursor-(col|row)-resize)\\b",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/** Feedback: Alert, Toast, Progress, Skeleton, Spinner. */
|
|
55
|
+
const FEEDBACK: ReadonlyArray<string> = [
|
|
56
|
+
"role=\"(alert|status|progressbar)\"",
|
|
57
|
+
"aria-live=\"(polite|assertive)\"",
|
|
58
|
+
"<progress\\b",
|
|
59
|
+
"(animate-pulse|animate-spin)\\b",
|
|
60
|
+
"(sonner|react-hot-toast|\\.toast\\()\\b",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** All 32 shadcn detection patterns combined. */
|
|
64
|
+
export const SHADCN: ReadonlyArray<string> = [
|
|
65
|
+
...FORM, ...OVERLAY, ...DATA, ...NAV, ...LAYOUT, ...FEEDBACK,
|
|
66
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift / Apple skill-trigger patterns, ported verbatim from
|
|
3
|
+
* `swift_skill_triggers.py`. 9 skills.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: Swift uses case-SENSITIVE matching (source `re.search` WITHOUT
|
|
6
|
+
* `re.IGNORECASE`). The framework name is registered in
|
|
7
|
+
* `CASE_SENSITIVE_FRAMEWORKS` so the gate compiles these without the `i` flag.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Map of Swift sub-skill name → triggering code patterns (case-sensitive). */
|
|
11
|
+
export const SWIFT_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
12
|
+
"swiftui-core": ["\\bstruct\\s+\\w+\\s*:\\s*View\\b", "@State\\b", "@Binding\\b",
|
|
13
|
+
"@Observable\\b", "@Environment\\b", "NavigationStack\\b",
|
|
14
|
+
"\\.sheet\\b", "\\.toolbar\\b", "\\.task\\b"],
|
|
15
|
+
"swift-core": ["\\bactor\\b", "\\basync\\s+(let|func|throws)\\b",
|
|
16
|
+
"\\bawait\\b", "Task\\s*\\{", "TaskGroup\\b", "Sendable\\b", "@MainActor\\b"],
|
|
17
|
+
"ios": ["UIKit|UIViewController|UIView\\b", "UIApplication\\b",
|
|
18
|
+
"\\.simulatorId\\b", "import\\s+UIKit\\b"],
|
|
19
|
+
"macos": ["AppKit|NSViewController|NSWindow\\b", "NSApplication\\b",
|
|
20
|
+
"\\.menuBar\\b", "import\\s+AppKit\\b"],
|
|
21
|
+
"watchos": ["WatchKit|WKInterface|WKExtension\\b", "HealthKit|HKWorkout\\b",
|
|
22
|
+
"WatchConnectivity\\b"],
|
|
23
|
+
"visionos": ["RealityKit|RealityView|ImmersiveSpace\\b",
|
|
24
|
+
"\\.volumeBaseplateVisibility\\b", "SpatialTapGesture\\b"],
|
|
25
|
+
"ipados": ["UISplitViewController|UIKeyCommand\\b", "\\.horizontalSizeClass\\b",
|
|
26
|
+
"pencilInteraction\\b"],
|
|
27
|
+
"tvos": ["TVUIKit|focusable\\b", "\\.focusSection\\b", "TVMonogram\\b"],
|
|
28
|
+
"build-distribution": ["TestFlight|AppStore\\b", "\\.entitlements\\b",
|
|
29
|
+
"codesign|notarize|archive\\b"],
|
|
30
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS skill-trigger patterns, ported verbatim from
|
|
3
|
+
* `tailwind_skill_triggers.py`. 12 skills.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: Tailwind uses case-SENSITIVE matching (source `re.search` WITHOUT
|
|
6
|
+
* `re.IGNORECASE`). The framework name is registered in
|
|
7
|
+
* `CASE_SENSITIVE_FRAMEWORKS` so the gate compiles these without the `i` flag.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Map of Tailwind sub-skill name → triggering utility-class patterns (case-sensitive). */
|
|
11
|
+
export const TAILWIND_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
12
|
+
"tailwindcss-v4": ["@theme\\b", "@source\\b", "@utility\\b", "@variant\\b",
|
|
13
|
+
"@import\\s+['\"]tailwindcss", "@config\\b"],
|
|
14
|
+
"tailwindcss-layout": ["\\b(flex|grid|inline-flex|inline-grid)\\b",
|
|
15
|
+
"(justify|items|place)-(start|end|center|between)",
|
|
16
|
+
"(grid-cols|grid-rows|col-span|row-span)-",
|
|
17
|
+
"(absolute|relative|fixed|sticky)\\b"],
|
|
18
|
+
"tailwindcss-typography": ["(font-sans|font-serif|font-mono|font-bold|font-semibold)\\b",
|
|
19
|
+
"(text-xs|text-sm|text-base|text-lg|text-xl|text-2xl)\\b",
|
|
20
|
+
"(tracking-|leading-|line-clamp-)\\b"],
|
|
21
|
+
"tailwindcss-backgrounds": ["(bg-gradient|bg-linear|bg-radial|bg-conic)\\b",
|
|
22
|
+
"(from-|via-|to-)\\w+", "bg-\\[url\\b"],
|
|
23
|
+
"tailwindcss-borders": ["(rounded-|border-|ring-|outline-|divide-)\\w+",
|
|
24
|
+
"(border-dashed|border-dotted|border-double)\\b"],
|
|
25
|
+
"tailwindcss-effects": ["(shadow-|opacity-|blur-|brightness-|contrast-)\\w+",
|
|
26
|
+
"(backdrop-blur|backdrop-brightness|backdrop-contrast)\\b",
|
|
27
|
+
"(inset-shadow-|mask-)\\w+"],
|
|
28
|
+
"tailwindcss-transforms": ["(scale-|rotate-|translate-|skew-)\\w+",
|
|
29
|
+
"(transition-|duration-|ease-|delay-)\\w+",
|
|
30
|
+
"(animate-spin|animate-pulse|animate-bounce)\\b"],
|
|
31
|
+
"tailwindcss-responsive": ["(sm:|md:|lg:|xl:|2xl:)\\w+",
|
|
32
|
+
"(@container|container-type)\\b", "(min-\\[|max-\\[)\\d+"],
|
|
33
|
+
"tailwindcss-spacing": ["\\b[pm][xytblr]?-\\d+\\b", "(space-x-|space-y-|gap-)\\d+"],
|
|
34
|
+
"tailwindcss-sizing": ["\\b[wh]-(full|screen|auto|min|max|fit)\\b",
|
|
35
|
+
"(min-w-|max-w-|min-h-|max-h-)\\w+", "(aspect-video|aspect-square)\\b"],
|
|
36
|
+
"tailwindcss-interactivity": ["(cursor-|select-|pointer-events-|scroll-)\\w+",
|
|
37
|
+
"(snap-|overscroll-|touch-)\\w+"],
|
|
38
|
+
"tailwindcss-custom-styles": ["@apply\\b", "@utility\\s+\\w+",
|
|
39
|
+
"@variant\\s+\\w+", "theme\\(\\s*['\"]"],
|
|
40
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework -> SKILL.md doc source for the APEX Check-1 non-routed deny —
|
|
3
|
+
* parity with the Python plugin's enforce-helpers.ts `getSkillSource()`
|
|
4
|
+
* (10-entry map, fallback "mcp__context7__query-docs"). The map is ported
|
|
5
|
+
* faithfully as skill NAMES only: disk paths are resolved DYNAMICALLY via
|
|
6
|
+
* {@link resolveSkillPath} (proprietary rule — no static path table; installed
|
|
7
|
+
* plugin dir names differ from upstream's `react-expert/...`, and a hardcoded
|
|
8
|
+
* table drifts from what is actually installed).
|
|
9
|
+
*/
|
|
10
|
+
import { resolveSkillPath } from "./skill-path";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fallback doc source when no SKILL.md can be promised: the consigne to query
|
|
14
|
+
* context7 (a TOOL name, not a path) — parity enforce-helpers.ts:51.
|
|
15
|
+
*/
|
|
16
|
+
export const CONTEXT7_SOURCE = "mcp__context7__query-docs";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Framework -> sub-skill dir name, faithful to the Python
|
|
20
|
+
* `getSkillSource` map (enforce-helpers.ts:39-50, all 10 entries).
|
|
21
|
+
*/
|
|
22
|
+
const SKILL_NAMES: Record<string, string> = {
|
|
23
|
+
react: "react-19",
|
|
24
|
+
nextjs: "nextjs-16",
|
|
25
|
+
swift: "swiftui-components",
|
|
26
|
+
laravel: "laravel-eloquent",
|
|
27
|
+
tailwind: "tailwindcss-v4",
|
|
28
|
+
generic: "solid-generic",
|
|
29
|
+
java: "solid-java",
|
|
30
|
+
go: "solid-go",
|
|
31
|
+
ruby: "solid-ruby",
|
|
32
|
+
rust: "solid-rust",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The doc source a Check-1 deny may promise for `framework` (parity
|
|
37
|
+
* `getSkillSource`): the REAL installed SKILL.md absolute path when it exists
|
|
38
|
+
* on disk, else {@link CONTEXT7_SOURCE}. `resolveSkillPath` returns a
|
|
39
|
+
* `/SKILL.md`-suffixed path ONLY on an `existsSync` hit; its
|
|
40
|
+
* `${PLUGINS_DIR}/<name>` not-found fallback (no marketplace installed, or a
|
|
41
|
+
* mapped skill absent on disk — e.g. upstream's `swiftui-components`) is
|
|
42
|
+
* converted to the context7 consigne here, because a deny must never promise
|
|
43
|
+
* a nonexistent path (every option a deny offers must actually unblock).
|
|
44
|
+
* @param framework - Detected framework (the `detectFramework()` domain).
|
|
45
|
+
* @param pluginsDirOverride - Marketplace plugins dir override (tests).
|
|
46
|
+
*/
|
|
47
|
+
export function getSkillSource(framework: string, pluginsDirOverride?: string): string {
|
|
48
|
+
const name = SKILL_NAMES[framework];
|
|
49
|
+
if (!name) return CONTEXT7_SOURCE;
|
|
50
|
+
const path = resolveSkillPath(name, pluginsDirOverride);
|
|
51
|
+
return path.endsWith("/SKILL.md") ? path : CONTEXT7_SOURCE;
|
|
52
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-framework code-pattern → required sub-skill data, ported verbatim from the
|
|
3
|
+
* fusengine `*_skill_triggers.py` + `*_patterns.py`
|
|
4
|
+
* (react/nextjs/laravel/swift) and the shared `shadcn_patterns.py`.
|
|
5
|
+
*
|
|
6
|
+
* Pattern groups live in `./skill-patterns/*`. Most frameworks match
|
|
7
|
+
* case-insensitively (source `re.IGNORECASE`); `swift` matches case-SENSITIVELY
|
|
8
|
+
* (source `re.search` without the flag) — see {@link CASE_SENSITIVE_FRAMEWORKS}.
|
|
9
|
+
*/
|
|
10
|
+
import { REACT_TRIGGERS } from "./skill-patterns/react";
|
|
11
|
+
import { NEXTJS_TRIGGERS } from "./skill-patterns/nextjs";
|
|
12
|
+
import { LARAVEL_TRIGGERS } from "./skill-patterns/laravel";
|
|
13
|
+
import { SWIFT_TRIGGERS } from "./skill-patterns/swift";
|
|
14
|
+
import { TAILWIND_TRIGGERS } from "./skill-patterns/tailwind";
|
|
15
|
+
import { SHADCN_TRIGGERS } from "./skill-patterns/shadcn-subskills";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Frameworks whose Python source omits `re.IGNORECASE`, so their regexes must
|
|
19
|
+
* be compiled WITHOUT the `i` flag to stay faithful.
|
|
20
|
+
*/
|
|
21
|
+
export const CASE_SENSITIVE_FRAMEWORKS: ReadonlySet<string> = new Set(["swift", "tailwind"]);
|
|
22
|
+
|
|
23
|
+
/** Map of required sub-skill name → triggering code patterns, keyed by framework. */
|
|
24
|
+
export const SKILL_TRIGGERS: Readonly<
|
|
25
|
+
Record<string, Readonly<Record<string, ReadonlyArray<string>>>>
|
|
26
|
+
> = {
|
|
27
|
+
react: REACT_TRIGGERS,
|
|
28
|
+
nextjs: NEXTJS_TRIGGERS,
|
|
29
|
+
laravel: LARAVEL_TRIGGERS,
|
|
30
|
+
swift: SWIFT_TRIGGERS,
|
|
31
|
+
tailwind: TAILWIND_TRIGGERS,
|
|
32
|
+
shadcn: SHADCN_TRIGGERS,
|
|
33
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content → sub-skill consultation gate, ported from the fusengine Python
|
|
3
|
+
* `*_skill_triggers.py` (react/nextjs/laravel) + `check_skill_common.py`
|
|
4
|
+
* `specific_skill_consulted`. Maps written-code API patterns to a required
|
|
5
|
+
* sub-skill and blocks when its `skills/<name>/` path was not read in-session.
|
|
6
|
+
*/
|
|
7
|
+
import type { Prompt } from "../prompt/types";
|
|
8
|
+
import { CASE_SENSITIVE_FRAMEWORKS, SKILL_TRIGGERS } from "./skill-trigger-patterns";
|
|
9
|
+
import { isShadcnProject } from "./shadcn-project";
|
|
10
|
+
import { resolveSkillPath } from "./skill-path";
|
|
11
|
+
|
|
12
|
+
export { SKILL_TRIGGERS } from "./skill-trigger-patterns";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect which sub-skills the written `content` requires for a `framework`.
|
|
16
|
+
* Faithful to the Python `detect_required_skills`: first matching pattern per
|
|
17
|
+
* skill wins. Most frameworks match case-insensitively (source `re.IGNORECASE`);
|
|
18
|
+
* `swift` matches case-sensitively (see {@link CASE_SENSITIVE_FRAMEWORKS}).
|
|
19
|
+
* @param framework - "react" | "nextjs" | "laravel" | "swift".
|
|
20
|
+
* @param content - the code being written.
|
|
21
|
+
* @returns required sub-skill names (empty when framework unknown / no match).
|
|
22
|
+
*/
|
|
23
|
+
export function detectRequiredSkills(framework: string, content: string): string[] {
|
|
24
|
+
const groups = SKILL_TRIGGERS[framework];
|
|
25
|
+
if (!groups) return [];
|
|
26
|
+
const flags = CASE_SENSITIVE_FRAMEWORKS.has(framework) ? "" : "i";
|
|
27
|
+
const required: string[] = [];
|
|
28
|
+
for (const [skill, patterns] of Object.entries(groups)) {
|
|
29
|
+
if (patterns.some((p) => new RegExp(p, flags).test(content))) required.push(skill);
|
|
30
|
+
}
|
|
31
|
+
return required;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `.tsx`/`.jsx` — the only extensions `check-tailwind-skill.py` actually gates
|
|
36
|
+
* (its regex also lists `.css`/`.html`, but both hit an early return right
|
|
37
|
+
* after, in the Python source).
|
|
38
|
+
*/
|
|
39
|
+
const TAILWIND_FILE = /\.(tsx|jsx)$/;
|
|
40
|
+
|
|
41
|
+
/** Ported verbatim from `check-tailwind-skill.py`'s `TW_PATTERN`. */
|
|
42
|
+
const TAILWIND_CONTENT = /(className|class).*['"].*\b(flex|grid|p-|m-|w-|h-|text-|bg-|border-)/;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* True when `filePath`/`content` match the Python Tailwind gate's trigger
|
|
46
|
+
* condition. React/Next.js components embed Tailwind utility classes in
|
|
47
|
+
* `className` — this check fires IN ADDITION TO the primary framework gate,
|
|
48
|
+
* never instead of it: {@link detectFramework} keeps returning "react"/
|
|
49
|
+
* "nextjs" for these files (framework SOLID rules stay correct).
|
|
50
|
+
*/
|
|
51
|
+
export function usesTailwindUtilities(filePath: string, content: string): boolean {
|
|
52
|
+
return TAILWIND_FILE.test(filePath) && TAILWIND_CONTENT.test(content);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Block when a required sub-skill's `skills/<name>/` path is absent from
|
|
57
|
+
* `refsRead`. Mirrors `specific_skill_consulted`, which confirms a skill was
|
|
58
|
+
* read by checking the tracking file contains `skills/<name>/`.
|
|
59
|
+
* @param framework - "react" | "nextjs" | "laravel".
|
|
60
|
+
* @param content - the code being written.
|
|
61
|
+
* @param refsRead - in-session read reference paths.
|
|
62
|
+
* @param forcedSkill - a skill the detected modular architecture forces (optional).
|
|
63
|
+
* @param cwd - project root; when set and not a shadcn project, `*-shadcn`
|
|
64
|
+
* requirements are skipped (ports the Python `is_shadcn_project` filter).
|
|
65
|
+
* @param filePath - the file being written; when it's a `.tsx`/`.jsx` file
|
|
66
|
+
* with Tailwind utility classes in `className`, the "tailwind" domain
|
|
67
|
+
* skills are merged in alongside `framework`'s own (ports the separate
|
|
68
|
+
* `check-tailwind-skill.py` gate, independent of react/nextjs).
|
|
69
|
+
* @returns a `block` Prompt naming the missing sub-skills, or `null` when satisfied.
|
|
70
|
+
*/
|
|
71
|
+
export function skillTriggerGate(
|
|
72
|
+
framework: string,
|
|
73
|
+
content: string,
|
|
74
|
+
refsRead: readonly string[],
|
|
75
|
+
forcedSkill?: string | null,
|
|
76
|
+
cwd?: string,
|
|
77
|
+
filePath?: string,
|
|
78
|
+
): Prompt | null {
|
|
79
|
+
// Phase 1.5 parity (check-laravel-skill.py:57-62): a forced FuseCore skill is
|
|
80
|
+
// denied on its own, BEFORE domain sub-skills — never merged into their message.
|
|
81
|
+
if (forcedSkill === "fusecore" && !refsRead.some((r) => r.includes("skills/fusecore/"))) {
|
|
82
|
+
return {
|
|
83
|
+
kind: "block",
|
|
84
|
+
title: "FuseCore skill not consulted",
|
|
85
|
+
reason:
|
|
86
|
+
"BLOCKED: FuseCore project detected. Read the FuseCore skill before writing code in FuseCore modules.",
|
|
87
|
+
actions: [`Read ${resolveSkillPath("fusecore")}`],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
let required = detectRequiredSkills(framework, content);
|
|
91
|
+
if (framework !== "tailwind" && filePath && usesTailwindUtilities(filePath, content)) {
|
|
92
|
+
required = [...required, ...detectRequiredSkills("tailwind", content)];
|
|
93
|
+
}
|
|
94
|
+
if (forcedSkill && !required.includes(forcedSkill)) required.push(forcedSkill);
|
|
95
|
+
if (cwd && !isShadcnProject(cwd)) required = required.filter((s) => !s.endsWith("-shadcn"));
|
|
96
|
+
const missing = required.filter(
|
|
97
|
+
(s) => !refsRead.some((r) => r.includes(`skills/${s}/`)),
|
|
98
|
+
);
|
|
99
|
+
if (missing.length === 0) return null;
|
|
100
|
+
return {
|
|
101
|
+
kind: "block",
|
|
102
|
+
title: "Required sub-skill not consulted",
|
|
103
|
+
reason:
|
|
104
|
+
`${framework}: code uses APIs covered by ${missing.join(", ")} ` +
|
|
105
|
+
"but its skill reference was not read this session.",
|
|
106
|
+
actions: missing.map((s) => `Read ${resolveSkillPath(s)}`),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module tailwind-skill-gate
|
|
3
|
+
* Standalone Tailwind base-skill gate — ports Phase 1 of the tailwindcss plugin's
|
|
4
|
+
* `check-tailwind-skill.py`. A `.tsx`/`.jsx` write carrying Tailwind utility
|
|
5
|
+
* classes must have consulted a base Tailwind skill (`tailwindcss-v4` or
|
|
6
|
+
* `tailwindcss-utilities`) this session. Phase 2 (domain sub-skills) and Phase 3
|
|
7
|
+
* (MCP research) are already enforced by {@link skillTriggerGate} (via
|
|
8
|
+
* `usesTailwindUtilities`) and `docConsultedGate`, so this gate covers only the
|
|
9
|
+
* base-skill gap, independent of the detected framework.
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import type { Prompt } from "../prompt/types";
|
|
13
|
+
import { usesTailwindUtilities } from "./skill-triggers";
|
|
14
|
+
import { resolveSkillPath } from "./skill-path";
|
|
15
|
+
|
|
16
|
+
/** Vendored/build paths exempt (parity check-tailwind-skill.py `/(node_modules|dist|build)/`). */
|
|
17
|
+
const TW_EXCLUDE_RE = /(^|\/)(node_modules|dist|build)\//;
|
|
18
|
+
/** A read reference proving a base Tailwind skill was consulted (Phase 1). */
|
|
19
|
+
const TW_BASE_SKILL_RE = /skills\/(tailwindcss-v4|tailwindcss-utilities)\//;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True when this Write/Edit is a `.tsx`/`.jsx` file carrying Tailwind utility
|
|
23
|
+
* classes, outside vendored dirs — the trigger condition of Phase 1.
|
|
24
|
+
* @param tool - the tool name.
|
|
25
|
+
* @param filePath - the file being written.
|
|
26
|
+
* @param content - the written content.
|
|
27
|
+
*/
|
|
28
|
+
export function isTailwindWrite(tool: string, filePath: string, content: string): boolean {
|
|
29
|
+
if (tool !== "Write" && tool !== "Edit") return false;
|
|
30
|
+
if (TW_EXCLUDE_RE.test(filePath)) return false;
|
|
31
|
+
return usesTailwindUtilities(filePath, content);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** True when `tailwindcss-v4` or `tailwindcss-utilities` was read this session (Phase 1 base skill). */
|
|
35
|
+
export function tailwindBaseSkillRead(refsRead: readonly string[]): boolean {
|
|
36
|
+
return refsRead.some((p) => TW_BASE_SKILL_RE.test(p));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Gate a Tailwind-scoped write: block until a base Tailwind skill is read
|
|
41
|
+
* (parity check-tailwind-skill.py Phase 1). Returns `null` when out of scope
|
|
42
|
+
* or already satisfied.
|
|
43
|
+
* @param tool - the tool name.
|
|
44
|
+
* @param filePath - the file being written.
|
|
45
|
+
* @param content - the written content.
|
|
46
|
+
* @param refsRead - in-session read reference paths.
|
|
47
|
+
*/
|
|
48
|
+
export function tailwindSkillGate(
|
|
49
|
+
tool: string,
|
|
50
|
+
filePath: string,
|
|
51
|
+
content: string,
|
|
52
|
+
refsRead: readonly string[],
|
|
53
|
+
): Prompt | null {
|
|
54
|
+
if (!isTailwindWrite(tool, filePath, content)) return null;
|
|
55
|
+
if (tailwindBaseSkillRead(refsRead)) return null;
|
|
56
|
+
return {
|
|
57
|
+
kind: "block",
|
|
58
|
+
title: "Tailwind CSS skill",
|
|
59
|
+
reason:
|
|
60
|
+
"BLOCKED: Tailwind skill not consulted. Read tailwindcss-v4/SKILL.md or tailwindcss-utilities/SKILL.md, or use mcp__context7__query-docs (topic: tailwindcss), then retry.",
|
|
61
|
+
actions: [`Read ${resolveSkillPath("tailwindcss-v4")} or ${resolveSkillPath("tailwindcss-utilities")}`],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Prompt } from "../prompt/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-filters of the ported enforce-apex-phases.ts hook, in Python order
|
|
5
|
+
* (lines 48-65): protected-path guard first, then the dependency/build-dir
|
|
6
|
+
* skip, then the trivial-edit fast path. The trivial COUNTER itself lives in
|
|
7
|
+
* the SessionTrack (`trivialEdits`, see recordTrivialEdit/trivialCount) — no
|
|
8
|
+
* separate state file.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Hook-managed state paths — Write/Edit always denied (parity enforce-apex-phases.ts PROTECTED_PATHS). */
|
|
12
|
+
export const PROTECTED_PATHS: RegExp = /\.claude\/(plugins\/(marketplaces|cache)|logs\/00-apex|fusengine-cache\/skill-tracking)/;
|
|
13
|
+
|
|
14
|
+
/** Dependency/build dirs the APEX gates skip entirely (parity enforce-apex-phases.ts SKIP_DIRS). */
|
|
15
|
+
export const SKIP_DIRS: RegExp = /(node_modules|vendor|dist|build|\.next|DerivedData)/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Absolute deny on Write/Edit of hook-managed state paths, with the Python
|
|
19
|
+
* wording byte-for-byte. Runs BEFORE the code-ext/skip-dirs filters (parity
|
|
20
|
+
* enforce-apex-phases.ts:48-54), so non-code files under the protected roots
|
|
21
|
+
* are covered too.
|
|
22
|
+
* @param tool - The harness tool name (only Write/Edit are policed).
|
|
23
|
+
* @param filePath - Target path of the edit (or undefined).
|
|
24
|
+
* @returns A blocking prompt, or null to fall through.
|
|
25
|
+
*/
|
|
26
|
+
export function protectedPathGate(tool: string, filePath: string | undefined): Prompt | null {
|
|
27
|
+
if ((tool !== "Write" && tool !== "Edit") || !filePath || !PROTECTED_PATHS.test(filePath)) return null;
|
|
28
|
+
return {
|
|
29
|
+
kind: "block",
|
|
30
|
+
title: "APEX Hook Guard: protected path",
|
|
31
|
+
reason:
|
|
32
|
+
"[APEX Hook Guard] Write blocked — this path is managed automatically by APEX hooks. Manual edits are forbidden and would corrupt tracked state.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** An Edit whose new_string spans fewer lines than this is "trivial" (parity: < 5). */
|
|
37
|
+
export const TRIVIAL_MAX_LINES = 5;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when this tool-use qualifies for the trivial fast path (parity
|
|
41
|
+
* enforce-apex-phases.ts:58-65): only an Edit (a Write always creates or
|
|
42
|
+
* replaces a file wholesale), NEVER `replace_all`, and a new_string under
|
|
43
|
+
* {@link TRIVIAL_MAX_LINES} lines. Absent content is NOT trivial (fail closed
|
|
44
|
+
* — the full gates run).
|
|
45
|
+
* @param tool - The harness tool name.
|
|
46
|
+
* @param content - The Edit's new_string (adapter-normalized).
|
|
47
|
+
* @param isReplaceAll - The Edit's replace_all flag.
|
|
48
|
+
*/
|
|
49
|
+
export function isTrivialEdit(tool: string, content: string | undefined, isReplaceAll?: boolean): boolean {
|
|
50
|
+
if (tool !== "Edit" || isReplaceAll === true || content === undefined) return false;
|
|
51
|
+
return content.split("\n").length < TRIVIAL_MAX_LINES;
|
|
52
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Exa search tool — result count force-capped + token budget capped. */
|
|
2
|
+
const EXA_SEARCH = /exa__web_search|exa_web_search/i;
|
|
3
|
+
/** Exa code-context tool — token budget capped; `numResults` capped only when
|
|
4
|
+
* already present (parity: limit-mcp-verbosity.py never injects it). */
|
|
5
|
+
const EXA_CODE = /exa__get_code_context|exa_get_code_context/i;
|
|
6
|
+
/** Context7 doc tool whose token budget is capped. */
|
|
7
|
+
const CONTEXT7_TOOLS = /context7__query-docs|context7_query-docs|query-docs/i;
|
|
8
|
+
|
|
9
|
+
/** Max results an exa MCP call may request. */
|
|
10
|
+
export const MAX_EXA_RESULTS = 3;
|
|
11
|
+
/** Max token budget for exa `tokensNum` / context7 `tokens`. */
|
|
12
|
+
export const MAX_TOKENS = 2000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Cap an MCP call's verbosity — exa `numResults` ≤ 3 (+ `tokensNum` ≤ 2000),
|
|
16
|
+
* Context7 `tokens` ≤ 2000. Returns the capped input (a mutation for the harness
|
|
17
|
+
* to apply) when a change is needed, else null.
|
|
18
|
+
*/
|
|
19
|
+
export function capVerbosity(tool: string, input: Record<string, unknown>): Record<string, unknown> | null {
|
|
20
|
+
const out: Record<string, unknown> = { ...input };
|
|
21
|
+
let changed = false;
|
|
22
|
+
const cap = (key: string, max: number, force: boolean): void => {
|
|
23
|
+
const v = out[key];
|
|
24
|
+
if ((typeof v === "number" && v > max) || (force && typeof v !== "number")) {
|
|
25
|
+
out[key] = max;
|
|
26
|
+
changed = true;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (EXA_SEARCH.test(tool)) {
|
|
30
|
+
cap("numResults", MAX_EXA_RESULTS, true);
|
|
31
|
+
cap("tokensNum", MAX_TOKENS, false);
|
|
32
|
+
} else if (EXA_CODE.test(tool)) {
|
|
33
|
+
cap("numResults", MAX_EXA_RESULTS, false);
|
|
34
|
+
cap("tokensNum", MAX_TOKENS, false);
|
|
35
|
+
} else if (CONTEXT7_TOOLS.test(tool)) {
|
|
36
|
+
cap("tokens", MAX_TOKENS, false);
|
|
37
|
+
}
|
|
38
|
+
return changed ? out : null;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** How a policy outcome should surface in a harness. */
|
|
2
|
+
export type PromptKind = "ask" | "block" | "inform";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Portable, harness-agnostic representation of a policy outcome as a prompt.
|
|
6
|
+
* Adapters render this into their harness's native shape (a Claude
|
|
7
|
+
* `permissionDecision`, a Cursor block, a CLI exit + message, ...).
|
|
8
|
+
*/
|
|
9
|
+
export interface Prompt {
|
|
10
|
+
kind: PromptKind;
|
|
11
|
+
/** Stable policy identifier for confirmation diagnostics. */
|
|
12
|
+
ruleId?: string;
|
|
13
|
+
/** Short title, e.g. "SOLID file-size limit". */
|
|
14
|
+
title: string;
|
|
15
|
+
/** Why this fired. */
|
|
16
|
+
reason: string;
|
|
17
|
+
/** Concrete next actions to proceed. */
|
|
18
|
+
actions?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* User-facing notice (Python `hook_output.allow_pass`/`post_pass` parity):
|
|
21
|
+
* shown to the HUMAN — Claude Code's `systemMessage` — never to the agent,
|
|
22
|
+
* unlike `reason`/additionalContext. Excluded from {@link formatPrompt}.
|
|
23
|
+
*/
|
|
24
|
+
userMessage?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Render a {@link Prompt} as a consistent, agent-readable memo block. */
|
|
28
|
+
export function formatPrompt(p: Prompt): string {
|
|
29
|
+
const tag = p.kind === "block" ? "BLOCKED" : p.kind === "ask" ? "CONFIRM" : "NOTE";
|
|
30
|
+
const lines = [`[${tag}] ${p.title}`, p.reason];
|
|
31
|
+
if (p.actions?.length) {
|
|
32
|
+
lines.push("Next:");
|
|
33
|
+
p.actions.forEach((a, i) => lines.push(` ${i + 1}. ${a}`));
|
|
34
|
+
}
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|