@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,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { detectProjectType } from "./detect-project";
|
|
4
|
+
|
|
5
|
+
/** Real project capabilities inferred from a manifest's deps + config files. */
|
|
6
|
+
export type Cap =
|
|
7
|
+
| "react" | "nextjs" | "vue" | "svelte" | "angular" | "nuxt"
|
|
8
|
+
| "laravel" | "swift" | "tailwind"
|
|
9
|
+
| "tanstack-start" | "tanstack-router" | "tanstack-query" | "vue-query"
|
|
10
|
+
| "zustand" | "pinia";
|
|
11
|
+
|
|
12
|
+
/** Manifest filenames that anchor a project root (any one marks the dir). */
|
|
13
|
+
const MANIFESTS = ["package.json", "composer.json", "Package.swift", "Cargo.toml", "go.mod", "pyproject.toml"];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Walk up from `startDir` to the NEAREST ancestor holding any manifest file.
|
|
17
|
+
* Unlike {@link projectRootOrNull} (which prefers the `.git` boundary and would
|
|
18
|
+
* skip a nested `scripts/package.json`), this returns the closest manifest dir,
|
|
19
|
+
* so an inner monorepo package wins over the repo root.
|
|
20
|
+
* @param startDir - Directory to begin the walk-up from.
|
|
21
|
+
* @returns The nearest manifest directory, or `null` at the filesystem root.
|
|
22
|
+
*/
|
|
23
|
+
export function nearestManifestDir(startDir: string): string | null {
|
|
24
|
+
let current = resolve(startDir);
|
|
25
|
+
for (;;) {
|
|
26
|
+
if (MANIFESTS.some((m) => existsSync(join(current, m)))) return current;
|
|
27
|
+
const parent = dirname(current);
|
|
28
|
+
if (parent === current) return null;
|
|
29
|
+
current = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read the REAL capabilities of the project rooted at `dir`: JS-framework deps
|
|
35
|
+
* from `package.json` (which {@link detectProjectType} does NOT scan), plus the
|
|
36
|
+
* non-JS/config signals detectProjectType already covers (laravel/swift/
|
|
37
|
+
* tailwind) — reused, not duplicated. Fail-open: any parse error yields whatever
|
|
38
|
+
* was gathered so far, never throws.
|
|
39
|
+
* @param dir - A manifest directory (from {@link nearestManifestDir}), or null.
|
|
40
|
+
* @returns The set of detected capabilities (empty when `dir` is null).
|
|
41
|
+
*/
|
|
42
|
+
export function projectCaps(dir: string | null): Set<Cap> {
|
|
43
|
+
const caps = new Set<Cap>();
|
|
44
|
+
if (!dir) return caps;
|
|
45
|
+
try {
|
|
46
|
+
const pkgPath = join(dir, "package.json");
|
|
47
|
+
if (existsSync(pkgPath)) {
|
|
48
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
|
|
49
|
+
dependencies?: Record<string, string>;
|
|
50
|
+
devDependencies?: Record<string, string>;
|
|
51
|
+
};
|
|
52
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
53
|
+
if ("react" in deps || "react-dom" in deps) caps.add("react");
|
|
54
|
+
if ("next" in deps) caps.add("nextjs");
|
|
55
|
+
if ("vue" in deps) caps.add("vue");
|
|
56
|
+
if ("svelte" in deps) caps.add("svelte");
|
|
57
|
+
if ("@angular/core" in deps) caps.add("angular");
|
|
58
|
+
if ("nuxt" in deps) caps.add("nuxt");
|
|
59
|
+
if ("tailwindcss" in deps) caps.add("tailwind");
|
|
60
|
+
if ("@tanstack/react-start" in deps) caps.add("tanstack-start");
|
|
61
|
+
if ("@tanstack/react-router" in deps) caps.add("tanstack-router");
|
|
62
|
+
if ("@tanstack/react-query" in deps) caps.add("tanstack-query");
|
|
63
|
+
if ("@tanstack/vue-query" in deps) caps.add("vue-query");
|
|
64
|
+
if ("zustand" in deps) caps.add("zustand");
|
|
65
|
+
if ("pinia" in deps) caps.add("pinia");
|
|
66
|
+
}
|
|
67
|
+
const type = detectProjectType(dir);
|
|
68
|
+
if (type === "laravel") caps.add("laravel");
|
|
69
|
+
if (type === "swift") caps.add("swift");
|
|
70
|
+
if (type === "tailwind") caps.add("tailwind");
|
|
71
|
+
} catch {
|
|
72
|
+
// fail-open: return what we have
|
|
73
|
+
}
|
|
74
|
+
return caps;
|
|
75
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Prompt } from "../prompt/types";
|
|
2
|
+
import { RALPH_SAFE } from "./patterns";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shell metacharacters that turn a "simple" command into a chained/composite
|
|
6
|
+
* one: `&&`, `||`, `;`, `|`, a lone background `&` (POSIX control operator —
|
|
7
|
+
* `cmd1 & cmd2` runs BOTH; `&(?!>)` excludes bash's `&>`/`&>>` redirects, which
|
|
8
|
+
* cannot introduce a second command), a backtick, `$(`, or a literal newline.
|
|
9
|
+
* Bounded alternation, no nested quantifiers — no ReDoS. Quote-unaware by
|
|
10
|
+
* design: a quoted `"R&D"` gates as ask (fail-closed).
|
|
11
|
+
*/
|
|
12
|
+
const CHAIN_RE = /&&|\|\||[;|`]|&(?!>)|\$\(|\n/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* True when `cmd` has no chaining operator. evaluate.ts's neverApproval
|
|
16
|
+
* exemption must NEVER fire on a composite command — `git commit -m x &&
|
|
17
|
+
* git push --force` would otherwise slip a blocked op past the RALPH_SAFE
|
|
18
|
+
* subset check, since that check is a `startsWith` on the leading verb only.
|
|
19
|
+
*/
|
|
20
|
+
export function isSingleCommand(cmd: string): boolean {
|
|
21
|
+
return !CHAIN_RE.test(cmd);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Commands exempt from `ask` under Codex `approval_policy=never` — a superset
|
|
26
|
+
* of {@link RALPH_SAFE} plus `git push`. Kept as its OWN list, never merged
|
|
27
|
+
* into RALPH_SAFE: the RALPH_MODE path (evaluate.ts's `ralphSafe`) has no
|
|
28
|
+
* equivalent to {@link isSafePushForm}, only the shared GIT_BLOCKED +
|
|
29
|
+
* isSingleCommand guards — adding push to RALPH_SAFE would auto-allow
|
|
30
|
+
* `git push origin --delete branch` under RALPH_MODE, since GIT_BLOCKED only
|
|
31
|
+
* covers force-push, not remote deletion. Only the neverApproval path in
|
|
32
|
+
* evaluate.ts consults NEVER_SAFE, and it always pairs it with
|
|
33
|
+
* isSafePushForm for any push entry.
|
|
34
|
+
*/
|
|
35
|
+
export const NEVER_SAFE: ReadonlyArray<string> = [...RALPH_SAFE, "git push"];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `--delete`/`-d` (git-push(1): "deleted from the remote repository", no
|
|
39
|
+
* `--force` needed), `--mirror` (force-updates AND deletes remote-only refs),
|
|
40
|
+
* `--prune` (removes remote refs with no local counterpart) — all delete or
|
|
41
|
+
* force-overwrite a remote ref without going through the `--force` flag
|
|
42
|
+
* GIT_BLOCKED already checks. Boundary-anchored like patterns.ts's FORCE_FLAG
|
|
43
|
+
* so `-d` never matches inside a longer token (e.g. a branch name).
|
|
44
|
+
*/
|
|
45
|
+
const PUSH_DESTRUCTIVE_FLAG = /\s(?:--delete|--mirror|--prune|-d)(?:\s|=|$)/;
|
|
46
|
+
/** A refspec colon (e.g. `git push origin :branch`) deletes the remote ref — the legacy form of `--delete` (git-push(1): "prefixing all refs with a colon"). */
|
|
47
|
+
const REFSPEC_COLON = /\s:\S/;
|
|
48
|
+
/**
|
|
49
|
+
* A leading `+` on a pushed refspec force-updates the remote ref on a
|
|
50
|
+
* non-fast-forward — git-push(1): "The `+` is optional and does the same thing
|
|
51
|
+
* as `--force`". It carries NO `--force`/`-f` flag, so GIT_BLOCKED's FORCE_FLAG
|
|
52
|
+
* misses it; this guard is the `--force` twin. The `\s` anchor makes `+` open
|
|
53
|
+
* its token (`+main`, `+src:dst`), never a mid-token `feature+x` ref name.
|
|
54
|
+
*/
|
|
55
|
+
const REFSPEC_FORCE = /\s\+\S/;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* True unless `cmd` is a `git push` carrying an irreversible remote-mutation
|
|
59
|
+
* form GIT_BLOCKED doesn't already cover: `--delete`/`-d`/`--mirror`/`--prune`,
|
|
60
|
+
* a `:refspec` remote-delete, or a `+refspec` force-update (the `--force` twin
|
|
61
|
+
* FORCE_FLAG misses). Plain `--force`/`-f` stays GIT_BLOCKED's job, checked
|
|
62
|
+
* upstream in evaluate.ts. Non-push commands always pass — this guard exists
|
|
63
|
+
* solely to narrow the `git push` entry of {@link NEVER_SAFE}.
|
|
64
|
+
*/
|
|
65
|
+
export function isSafePushForm(cmd: string): boolean {
|
|
66
|
+
if (!cmd.startsWith("git push")) return true;
|
|
67
|
+
return !PUSH_DESTRUCTIVE_FLAG.test(cmd) && !REFSPEC_COLON.test(cmd) && !REFSPEC_FORCE.test(cmd);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Next steps offered on the auto-approve notice — the only channel left once `ask` is unavailable. */
|
|
71
|
+
const ACTIONS: readonly string[] = [
|
|
72
|
+
"Set approval_policy=on-request for interactive confirmation",
|
|
73
|
+
"Or set RALPH_MODE=1 to auto-approve silently (no notice)",
|
|
74
|
+
"Or run the command manually outside the agent loop",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Builds the auto-approve `inform` prompt for a command evaluate.ts has
|
|
79
|
+
* already proven safe (RALPH_SAFE subset, not GIT_BLOCKED, not chained —
|
|
80
|
+
* see {@link isSingleCommand}). Pure formatting only; the 3-condition
|
|
81
|
+
* decision itself lives in evaluate.ts, where the anti-chaining guarantee
|
|
82
|
+
* must stay visible and auditable.
|
|
83
|
+
* @param cmd - The already-vetted command.
|
|
84
|
+
*/
|
|
85
|
+
export function buildNeverApprovalPrompt(cmd: string): Prompt {
|
|
86
|
+
const trimmed = cmd.trim();
|
|
87
|
+
const reason = `Auto-approved "${trimmed}" under approval_policy=never.`;
|
|
88
|
+
return {
|
|
89
|
+
kind: "inform",
|
|
90
|
+
title: "Auto-approved (approval_policy=never)",
|
|
91
|
+
reason,
|
|
92
|
+
actions: [...ACTIONS],
|
|
93
|
+
userMessage: `[fuse-harness] Auto-approved "${trimmed}" — approval_policy=never has no ask channel. Destructive git commands still deny. Set approval_policy=on-request, or RALPH_MODE=1, to keep asking.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard pattern data, ported verbatim from the fusengine git/install guards.
|
|
3
|
+
* Note (faithful): `--force` also matches `--force-with-lease` — preserved
|
|
4
|
+
* from the source guard, now as an explicit alternation instead of an
|
|
5
|
+
* accidental substring match (see {@link GIT_BLOCKED}).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Token-boundary suffix for a destructive flag: only whitespace, `=`, or
|
|
10
|
+
* end-of-string may follow — never another word character. Without this, a
|
|
11
|
+
* flag regex matches as a plain substring of any argument (a branch/file name
|
|
12
|
+
* like `fix/guard-false-positives` contains `-f`, `feature-force-refactor`
|
|
13
|
+
* contains `-force`), turning a rename into a false-positive hard block
|
|
14
|
+
* (proven live: `git push -u origin fix/api-keys-fossil` denied as
|
|
15
|
+
* "Destructive git command"). No leading-boundary group is needed on the flag
|
|
16
|
+
* side — every pattern below prefixes the flag with a literal `\s`, and the
|
|
17
|
+
* flag can never be the first character of the whole command since a git
|
|
18
|
+
* verb (`git push`, `git reset`, ...) always precedes it.
|
|
19
|
+
*/
|
|
20
|
+
const FLAG_END = "(?:\\s|=|$)";
|
|
21
|
+
/** `-f` / `--force` / `--force-with-lease`, boundary-anchored on both flag forms. */
|
|
22
|
+
const FORCE_FLAG = `\\s(?:--force(?:-with-lease)?|-f)${FLAG_END}`;
|
|
23
|
+
/**
|
|
24
|
+
* `git clean` short-flag cluster containing both `f` and `d` in any order
|
|
25
|
+
* (`-fd`, `-df`, `-fdx`, `-fdX`, `-xfd`…): a MORE destructive superset than a
|
|
26
|
+
* bare `-fd` — must stay blocked, never downgrade to `git clean`'s GIT_ASK.
|
|
27
|
+
*/
|
|
28
|
+
const CLEAN_FD_FLAG = `\\s-(?=[a-zA-Z]*f)(?=[a-zA-Z]*d)[a-zA-Z]+${FLAG_END}`;
|
|
29
|
+
|
|
30
|
+
/** Destructive git operations to block outright. */
|
|
31
|
+
export const GIT_BLOCKED: ReadonlyArray<RegExp> = [
|
|
32
|
+
new RegExp(`git push.*${FORCE_FLAG}`),
|
|
33
|
+
new RegExp(`git reset.*\\s--hard${FLAG_END}`),
|
|
34
|
+
new RegExp(`git clean.*${CLEAN_FD_FLAG}`),
|
|
35
|
+
new RegExp(`git branch.*\\s-D${FLAG_END}`),
|
|
36
|
+
new RegExp(`git rebase.*${FORCE_FLAG}`),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** Git operations that warrant a confirmation prompt. */
|
|
40
|
+
export const GIT_ASK: ReadonlyArray<RegExp> = [
|
|
41
|
+
/git push/, /git checkout/, /git reset/, /git rebase/, /git merge/,
|
|
42
|
+
/git stash/, /git clean/, /git rm/, /git mv/, /git restore/,
|
|
43
|
+
/git revert/, /git cherry-pick/, /git commit/, /git add/, /git branch -d/,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/** System-level package installs (need confirmation). */
|
|
47
|
+
export const SYSTEM_INSTALL: ReadonlyArray<RegExp> = [
|
|
48
|
+
/brew install/, /brew upgrade/, /brew cask/, /apt install/, /apt-get install/,
|
|
49
|
+
/dnf install/, /pacman -S/,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/** Project-level package installs. */
|
|
53
|
+
export const PROJECT_INSTALL: ReadonlyArray<RegExp> = [
|
|
54
|
+
/npm install/, /npm i /, /yarn add/, /pnpm add/, /pip install/, /pip3 install/,
|
|
55
|
+
/composer require/, /bun add/, /bun install/, /cargo install/, /go install/,
|
|
56
|
+
/gem install/, /pipx install/,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/** True when `cmd` matches any pattern in `patterns`. */
|
|
60
|
+
export function matchPatterns(cmd: string, patterns: ReadonlyArray<RegExp>): boolean {
|
|
61
|
+
return patterns.some((re) => re.test(cmd));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Git commands exempt from confirmation when Ralph mode is on (parity git-guard.py RALPH_SAFE). */
|
|
65
|
+
export const RALPH_SAFE: ReadonlyArray<string> = [
|
|
66
|
+
"git add", "git commit", "git checkout -b", "git branch --show-current",
|
|
67
|
+
"git status", "git diff", "git log",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* OPT-IN autonomous "Ralph" mode: OFF unless `RALPH_MODE` is `1`/`true`, read
|
|
72
|
+
* fresh on each call. Only the env var activates it — the Python source also
|
|
73
|
+
* auto-enabled on a `.claude/ralph/prd.json` file or a `feature/*` branch, but
|
|
74
|
+
* those silent activations are dropped here: a mode that removes git/install
|
|
75
|
+
* confirmations must never turn on implicitly (owner: default OFF, env opt-in).
|
|
76
|
+
*/
|
|
77
|
+
export function isRalphMode(): boolean {
|
|
78
|
+
const v = process.env.RALPH_MODE;
|
|
79
|
+
return v === "1" || v === "true";
|
|
80
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO completeness checks — pure text in, missing-items out (no fs). Ports the
|
|
3
|
+
* `validate()` of `seo/hooks/validate-seo.ts` using regex instead of cheerio so
|
|
4
|
+
* the harness keeps zero runtime HTML-parser dependency (presence-only gate).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Extensions the SEO hook validates (HTML-like rendered output). */
|
|
8
|
+
const HTML_LIKE = /\.(html|astro|tsx|vue|blade\.php)$/;
|
|
9
|
+
|
|
10
|
+
/** True when `path` is an HTML-like file the SEO hook should validate. */
|
|
11
|
+
export function isHtmlLike(path: string): boolean {
|
|
12
|
+
return HTML_LIKE.test(path);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** True when a `<tag …>…</tag>` carries non-empty inner text. */
|
|
16
|
+
function hasNonEmptyTag(html: string, tag: string): boolean {
|
|
17
|
+
const m = new RegExp(`<${tag}[\\s>][\\s\\S]*?</${tag}>`, "i").exec(html);
|
|
18
|
+
return !!m && m[0].replace(/<[^>]*>/g, "").trim().length > 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** True when a `<meta …>` whose `selAttr=selVal` has a non-empty `content`. */
|
|
22
|
+
function hasMetaContent(html: string, selAttr: string, selVal: string): boolean {
|
|
23
|
+
const re = new RegExp(`<meta\\b[^>]*\\b${selAttr}\\s*=\\s*["']${selVal}["'][^>]*>`, "i");
|
|
24
|
+
const tag = re.exec(html)?.[0] ?? "";
|
|
25
|
+
const c = /\bcontent\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1] ?? "";
|
|
26
|
+
return c.trim().length > 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** True when a `<link rel="canonical">` carries a non-empty `href`. */
|
|
30
|
+
function hasCanonical(html: string): boolean {
|
|
31
|
+
const tag = /<link\b[^>]*\brel\s*=\s*["']canonical["'][^>]*>/i.exec(html)?.[0] ?? "";
|
|
32
|
+
return (/\bhref\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1] ?? "").trim().length > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Report the SEO elements missing from HTML-like content (title, meta
|
|
37
|
+
* description, OG title/description/image, canonical, JSON-LD schema).
|
|
38
|
+
* @param html - The file content.
|
|
39
|
+
* @returns The missing element labels (empty when complete).
|
|
40
|
+
*/
|
|
41
|
+
export function missingSeoElements(html: string): string[] {
|
|
42
|
+
const missing: string[] = [];
|
|
43
|
+
if (!hasNonEmptyTag(html, "title")) missing.push("<title>");
|
|
44
|
+
if (!hasMetaContent(html, "name", "description")) missing.push("<meta name='description'>");
|
|
45
|
+
if (!hasMetaContent(html, "property", "og:title")) missing.push("og:title");
|
|
46
|
+
if (!hasMetaContent(html, "property", "og:description")) missing.push("og:description");
|
|
47
|
+
if (!hasMetaContent(html, "property", "og:image")) missing.push("og:image");
|
|
48
|
+
if (!hasCanonical(html)) missing.push("canonical");
|
|
49
|
+
if (!/<script\b[^>]*type\s*=\s*["']application\/ld\+json["']/i.test(html)) missing.push("JSON-LD schema");
|
|
50
|
+
return missing;
|
|
51
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Directories whose presence indicates shadcn/ui is installed. */
|
|
5
|
+
const UI_DIRS: ReadonlyArray<string> = [
|
|
6
|
+
"src/components/ui",
|
|
7
|
+
"components/ui",
|
|
8
|
+
"src/modules/cores/shadcn/components/ui",
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether `cwd` is a shadcn/ui project, ported from the shared Python
|
|
13
|
+
* `is_shadcn_project` (`shadcn_patterns.py`): true when a `components.json`
|
|
14
|
+
* file exists, or any known `components/ui` directory exists under the root.
|
|
15
|
+
* Used to skip `*-shadcn` sub-skill requirements when shadcn is not installed.
|
|
16
|
+
* @param cwd - project root directory to scan.
|
|
17
|
+
* @returns `true` when shadcn/ui is detected on disk.
|
|
18
|
+
*/
|
|
19
|
+
export function isShadcnProject(cwd: string): boolean {
|
|
20
|
+
if (existsSync(join(cwd, "components.json"))) return true;
|
|
21
|
+
return UI_DIRS.some((dir) => existsSync(join(cwd, dir)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module shadcn-skill-gate
|
|
3
|
+
* Standalone shadcn/ui skill gate, ported from the shadcn-expert plugin's
|
|
4
|
+
* `check-skill-loaded.py`. Distinct from the `*-shadcn` sub-skill entries
|
|
5
|
+
* consumed by react/nextjs (`./skill-patterns/shadcn.ts`, the HTML-detection
|
|
6
|
+
* patterns): this polices ANY Write/Edit in a shadcn-scoped path
|
|
7
|
+
* (components|ui|shadcn|components.json), independent of the detected
|
|
8
|
+
* `framework` — mirroring the plugin's own PreToolUse hook, which runs
|
|
9
|
+
* alongside (not instead of) the react/nextjs framework gate.
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import type { Prompt } from "../prompt/types";
|
|
13
|
+
import type { AuthEntry } from "../freshness/doc-helpers";
|
|
14
|
+
import { skillTriggerGate } from "./skill-triggers";
|
|
15
|
+
import { docConsultedGate } from "./apex";
|
|
16
|
+
|
|
17
|
+
/** File extensions the shadcn gate polices (source: `\.(tsx|jsx|css|scss|json)$`). */
|
|
18
|
+
const SHADCN_FILE_RE = /\.(tsx|jsx|css|scss|json)$/;
|
|
19
|
+
/**
|
|
20
|
+
* Vendored/build paths exempt from the gate. Uses `(^|\/)` (not just a
|
|
21
|
+
* leading `/`) so a relative root-start path like `node_modules/x` is also
|
|
22
|
+
* exempt — mirrors the same deliberate widening already applied in
|
|
23
|
+
* `design/skill-gate.ts`'s `EXEMPT_RE`, not the Python's stricter `/x/`-only match.
|
|
24
|
+
*/
|
|
25
|
+
const EXEMPT_RE = /(^|\/)(node_modules|dist|build)\//;
|
|
26
|
+
/** Path fragments that put a write in shadcn scope (source: `(components|ui|shadcn|components\.json)`). */
|
|
27
|
+
const SHADCN_PATH_RE = /(components|ui|shadcn|components\.json)/;
|
|
28
|
+
/** A read reference proving one of the two named base skills was consulted (Phase 1). */
|
|
29
|
+
const SHADCN_BASE_SKILL_RE = /skills\/(shadcn-detection|shadcn-components)\//;
|
|
30
|
+
/** Session `authorizations` source proving a live `mcp__shadcn__*` call was made this session (Phase 1, option 3). */
|
|
31
|
+
const SHADCN_MCP_SOURCE_RE = /shadcn-mcp/i;
|
|
32
|
+
|
|
33
|
+
/** Session evidence the gate consumes: read references + doc authorizations. */
|
|
34
|
+
export interface ShadcnEvidence {
|
|
35
|
+
refsRead: readonly string[];
|
|
36
|
+
authorizations?: Record<string, AuthEntry>;
|
|
37
|
+
sessionId: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True when this Write/Edit is shadcn-scoped: a `.tsx/.jsx/.css/.scss/.json`
|
|
42
|
+
* file under a `components|ui|shadcn|components.json` path, outside vendored
|
|
43
|
+
* dirs. Ports `check-skill-loaded.py`'s file-path filter (lines 35-42).
|
|
44
|
+
*/
|
|
45
|
+
export function isShadcnWrite(tool: string, filePath: string): boolean {
|
|
46
|
+
if (tool !== "Write" && tool !== "Edit") return false;
|
|
47
|
+
if (!SHADCN_FILE_RE.test(filePath) || EXEMPT_RE.test(filePath)) return false;
|
|
48
|
+
return SHADCN_PATH_RE.test(filePath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when `shadcn-detection/` or `shadcn-components/` was read this session (Phase 1 base skill). */
|
|
52
|
+
export function shadcnBaseSkillRead(refsRead: readonly string[]): boolean {
|
|
53
|
+
return refsRead.some((p) => SHADCN_BASE_SKILL_RE.test(p));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** True when a `mcp__shadcn__*` tool call was recorded this session (Phase 1's 3rd unblock option). */
|
|
57
|
+
export function shadcnMcpConsulted(authorizations: Record<string, AuthEntry> | undefined, sessionId: string): boolean {
|
|
58
|
+
if (!authorizations) return false;
|
|
59
|
+
return Object.values(authorizations).some(
|
|
60
|
+
(a) => a.doc_sessions?.includes(sessionId) && (a.sources ?? (a.source ? [a.source] : [])).some((s) => SHADCN_MCP_SOURCE_RE.test(s)),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gate a shadcn-scoped write. Phase 1 requires a base skill read
|
|
66
|
+
* (shadcn-detection or shadcn-components); Phase 2 requires the domain
|
|
67
|
+
* sub-skill(s) matching the written content, via {@link skillTriggerGate}
|
|
68
|
+
* keyed on the "shadcn" framework (registered in `SKILL_TRIGGERS`); Phase 3
|
|
69
|
+
* requires doc research, delegated to the existing {@link docConsultedGate}
|
|
70
|
+
* (Context7 AND Exa, or a web fallback alone — not the Python's strict AND).
|
|
71
|
+
* Returns a blocking {@link Prompt}, or `null` to allow.
|
|
72
|
+
*/
|
|
73
|
+
export function shadcnSkillGate(
|
|
74
|
+
tool: string,
|
|
75
|
+
filePath: string,
|
|
76
|
+
content: string,
|
|
77
|
+
ev: ShadcnEvidence,
|
|
78
|
+
): Prompt | null {
|
|
79
|
+
if (!isShadcnWrite(tool, filePath)) return null;
|
|
80
|
+
if (!shadcnBaseSkillRead(ev.refsRead) && !shadcnMcpConsulted(ev.authorizations, ev.sessionId)) {
|
|
81
|
+
return {
|
|
82
|
+
kind: "block",
|
|
83
|
+
title: "shadcn/ui skill",
|
|
84
|
+
reason: "BLOCKED: shadcn skill not consulted. Read shadcn-detection/SKILL.md or shadcn-components/SKILL.md, or use mcp__shadcn__search_items_in_registries, then retry.",
|
|
85
|
+
actions: ["Read skills/shadcn-detection/ or skills/shadcn-components/, or call mcp__shadcn__search_items_in_registries, then retry"],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const domainBlock = skillTriggerGate("shadcn", content, ev.refsRead);
|
|
89
|
+
if (domainBlock) return domainBlock;
|
|
90
|
+
return docConsultedGate({
|
|
91
|
+
sessionId: ev.sessionId,
|
|
92
|
+
framework: "shadcn",
|
|
93
|
+
filePath,
|
|
94
|
+
content,
|
|
95
|
+
authorizations: ev.authorizations,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module shell-read-refs
|
|
3
|
+
* Codex teammates frequently read skill/SOLID reference `.md` files through a
|
|
4
|
+
* shell Bash call (`cat`, `head`, …) instead of a native `Read` — the
|
|
5
|
+
* refsRead/ref-journal freshness gates only ever credited a native `Read`
|
|
6
|
+
* (`src/runtime/activity.ts`'s `READ_TOOLS` branch), so a shell-read skill
|
|
7
|
+
* consultation was invisible to every SOLID/skill gate. This detects the
|
|
8
|
+
* `.md` paths a KNOWN read-only command targets in a Bash `command`, for a
|
|
9
|
+
* caller to fold into the SAME `{kind:"ref", path, ts}` activity
|
|
10
|
+
* `src/runtime/record.ts` already persists (no new store, no new gate).
|
|
11
|
+
*
|
|
12
|
+
* Fail-open by construction: only a whitelisted read-only command name
|
|
13
|
+
* credits its `.md` arguments — a non-read command (`echo`, `mv`, `tee`, …)
|
|
14
|
+
* sharing a chained/piped segment is never credited, and `sed -i`/
|
|
15
|
+
* `--in-place` (a MUTATION despite the `sed` name) is explicitly excluded.
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
import { commandToString } from "../runtime/command-string";
|
|
19
|
+
import { shellOutputRedirects } from "./guards/bash-write-redirects";
|
|
20
|
+
|
|
21
|
+
/** Read-only shell commands that can target a `.md` reference by path. */
|
|
22
|
+
const READ_COMMANDS = new Set(["cat", "head", "tail", "sed", "rg", "ripgrep", "less", "more", "bat"]);
|
|
23
|
+
|
|
24
|
+
/** POSIX shells whose `-c`-style argv (or inline string) wraps a real script. */
|
|
25
|
+
const SHELL_BINS = new Set(["bash", "sh", "zsh", "dash"]);
|
|
26
|
+
|
|
27
|
+
/** `sed -i` / `sed --in-place` mutates the file in place — never a read. */
|
|
28
|
+
const SED_INPLACE = /(^|\s)(-i\b|--in-place\b)/;
|
|
29
|
+
|
|
30
|
+
/** Split a command string on `&&`, `||`, `;`, `|`, and newlines — each side scanned independently. */
|
|
31
|
+
function segments(command: string): string[] {
|
|
32
|
+
return command.split(/&&|\|\||[;|\n]/);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Strip a redirection (`>`, `>>`, `<`, `2>`, `&>`, …) and everything after — its target is WRITTEN, not read. */
|
|
36
|
+
function beforeRedirect(segment: string): string {
|
|
37
|
+
const first = shellOutputRedirects(segment)[0];
|
|
38
|
+
return first ? segment.slice(0, first.start) : segment;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Naive shell tokenizer: whitespace-split, stripping one matching layer of quotes per token. */
|
|
42
|
+
function tokenize(segment: string): string[] {
|
|
43
|
+
const tokens = segment.match(/(?:"[^"]*"|'[^']*'|\S+)/g) ?? [];
|
|
44
|
+
return tokens.map((t) => (/^(['"]).*\1$/.test(t) ? t.slice(1, -1) : t));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Index of the first token that isn't an env-assignment prefix (`KEY=VAL cmd …`). */
|
|
48
|
+
function firstCommandIndex(tokens: string[]): number {
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i] ?? "")) i++;
|
|
51
|
+
return i;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The inner script of a `sh|bash|zsh|dash -c <script>` wrapper (as ONE token, since {@link tokenize} keeps a quoted phrase whole), or undefined when `tokens` isn't that shape. */
|
|
55
|
+
function unwrapShellC(tokens: string[]): string | undefined {
|
|
56
|
+
const i = firstCommandIndex(tokens);
|
|
57
|
+
const bin = tokens[i]?.slice(tokens[i].lastIndexOf("/") + 1);
|
|
58
|
+
if (!bin || !SHELL_BINS.has(bin)) return undefined;
|
|
59
|
+
const flag = tokens[i + 1];
|
|
60
|
+
if (!flag || !/^-[a-z]*c$/.test(flag)) return undefined;
|
|
61
|
+
return tokens[i + 2];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Credit the `.md` arguments of one segment if its command is a whitelisted read; recurses once into a `sh -c` wrapper. */
|
|
65
|
+
function scanSegment(raw: string, out: string[]): void {
|
|
66
|
+
const segment = beforeRedirect(raw);
|
|
67
|
+
const tokens = tokenize(segment);
|
|
68
|
+
const inner = unwrapShellC(tokens);
|
|
69
|
+
if (inner !== undefined) {
|
|
70
|
+
for (const s of segments(inner)) scanSegment(s, out);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const i = firstCommandIndex(tokens);
|
|
74
|
+
const name = tokens[i]?.slice(tokens[i].lastIndexOf("/") + 1);
|
|
75
|
+
if (!name || !READ_COMMANDS.has(name)) return;
|
|
76
|
+
if (name === "sed" && SED_INPLACE.test(segment)) return;
|
|
77
|
+
for (const t of tokens.slice(i + 1)) {
|
|
78
|
+
if (t.endsWith(".md") && !t.startsWith("-")) out.push(t);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The `.md` paths a read-only shell command reads in `command` (a Bash
|
|
84
|
+
* `tool_input.command` — plain string or Codex's argv-array form, both
|
|
85
|
+
* normalized via {@link commandToString}). Empty for a non-read command,
|
|
86
|
+
* `sed -i` in-place, or an unparseable/absent command — fail-open, never a
|
|
87
|
+
* false credit.
|
|
88
|
+
* @param command - Raw `tool_input.command` value (string | string[] | unknown).
|
|
89
|
+
*/
|
|
90
|
+
export function shellReadRefPaths(command: unknown): string[] {
|
|
91
|
+
const str = commandToString(command);
|
|
92
|
+
if (!str) return [];
|
|
93
|
+
const out: string[] = [];
|
|
94
|
+
for (const seg of segments(str)) scanSegment(seg, out);
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic skill-path resolution (fs). Replaces a hardcoded `PLUGIN_DIR`
|
|
3
|
+
* table (framework -> plugin dir name), which can drift from what's actually
|
|
4
|
+
* installed: scans installed marketplace plugins for a `skills/<name>/SKILL.md`
|
|
5
|
+
* that exists ON DISK, returning the first real absolute path found — never a
|
|
6
|
+
* guessed one.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately lighter than the cartographer's own `scanSkills` (which
|
|
9
|
+
* frontmatter-parses every skill in a plugin): this runs once per missing
|
|
10
|
+
* sub-skill on every gated Write/Edit, so it does a single flat `readdirSync`
|
|
11
|
+
* of the plugins dir + one `existsSync` per candidate — no file reads, no
|
|
12
|
+
* recursion, no `statSync`.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { findMarketplacePlugins } from "../runtime/lifecycle/cartographer/detect";
|
|
17
|
+
import { PLUGINS_DIR } from "./file-size";
|
|
18
|
+
|
|
19
|
+
/** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
|
|
20
|
+
function pluginDirNames(dir: string): string[] {
|
|
21
|
+
try {
|
|
22
|
+
return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the real, installed `SKILL.md` path for `skillName`.
|
|
30
|
+
* @param skillName - the sub-skill directory name (e.g. "solid-react",
|
|
31
|
+
* "shadcn-detection", "react-shadcn").
|
|
32
|
+
* @param pluginsDirOverride - override for the marketplace plugins dir
|
|
33
|
+
* (tests); defaults to the auto-detected marketplace root.
|
|
34
|
+
* @returns the absolute `<pluginDir>/skills/<skillName>/SKILL.md` of the
|
|
35
|
+
* first installed plugin that actually has it, or the generic
|
|
36
|
+
* `${PLUGINS_DIR}/<skillName>` fallback when none is found (CI/test
|
|
37
|
+
* environments without a marketplace).
|
|
38
|
+
*/
|
|
39
|
+
export function resolveSkillPath(skillName: string, pluginsDirOverride?: string): string {
|
|
40
|
+
const pluginsDir = pluginsDirOverride ?? findMarketplacePlugins();
|
|
41
|
+
for (const dir of pluginDirNames(pluginsDir)) {
|
|
42
|
+
const candidate = join(pluginsDir, dir, "skills", skillName, "SKILL.md");
|
|
43
|
+
if (existsSync(candidate)) return candidate;
|
|
44
|
+
}
|
|
45
|
+
return `${PLUGINS_DIR}/${skillName}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laravel skill-trigger patterns, ported verbatim from `laravel_patterns.py`
|
|
3
|
+
* (consumed by `laravel_skill_triggers.py`). Matched case-insensitively.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Map of Laravel sub-skill name → triggering PHP code patterns. */
|
|
7
|
+
export const LARAVEL_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
8
|
+
"fusecore": ["FuseCore\\\\[A-Za-z]+\\\\App\\\\", "use HasModule\\b",
|
|
9
|
+
"ModuleServiceProvider\\b", "ModuleInterface\\b"],
|
|
10
|
+
"laravel-eloquent": ["(extends Model|HasFactory|belongsTo|hasMany|hasOne|morphTo)\\b",
|
|
11
|
+
"\\$this->belongsToMany|->with\\(|->whereHas\\(",
|
|
12
|
+
"(Eloquent|Model)::(find|where|create|update|all)\\b"],
|
|
13
|
+
"laravel-api": ["(JsonResource|ResourceCollection|apiResource)\\b",
|
|
14
|
+
"Route::(get|post|put|delete|apiResource)\\(",
|
|
15
|
+
"(response\\(\\)->json|Request \\$request)\\b"],
|
|
16
|
+
"laravel-auth": ["(Auth::|auth\\(\\)|Sanctum|Passport|Socialite)\\b",
|
|
17
|
+
"(Gate::|Policy|can\\(|authorize)\\b",
|
|
18
|
+
"(middleware\\(['\"]auth|LoginController|RegisterController)\\b"],
|
|
19
|
+
"laravel-livewire": ["(extends Component|Livewire|wire:|#\\[On)\\b",
|
|
20
|
+
"(mount|render|emit|dispatch)\\(\\)", "@livewire|<livewire:"],
|
|
21
|
+
"laravel-queues": ["(implements ShouldQueue|dispatch\\(|Bus::)\\b",
|
|
22
|
+
"(Queue::|Job|Batch|Chain)\\b", "(onQueue|onConnection|tries|backoff)\\b"],
|
|
23
|
+
"laravel-billing": ["(Billable|subscription|Cashier)\\b",
|
|
24
|
+
"(createSubscription|newSubscription|charge)\\("],
|
|
25
|
+
"laravel-stripe-connect": ["(StripeConnect|connectAccount|onboardingUrl)\\b",
|
|
26
|
+
"(paymentIntent|transfer|payout|splitPayment)\\b",
|
|
27
|
+
"Stripe\\\\\\\\(Account|Transfer|PaymentIntent)\\b"],
|
|
28
|
+
"laravel-testing": ["(extends TestCase|RefreshDatabase|WithFaker)\\b",
|
|
29
|
+
"(assertStatus|assertJson|assertSee|assertRedirect)\\(",
|
|
30
|
+
"(factory\\(|Pest|it\\(|test\\(|expect\\()\\b"],
|
|
31
|
+
"laravel-migrations": ["(Schema::|Blueprint|->table|->create)\\b",
|
|
32
|
+
"(->string|->integer|->boolean|->foreignId|->index)\\(", "extends Migration\\b"],
|
|
33
|
+
"laravel-blade": ["(@extends|@section|@yield|@component|@slot)\\b",
|
|
34
|
+
"(@if|@foreach|@include|@push|@stack)\\b", "(Blade::|x-[a-z])\\b"],
|
|
35
|
+
"laravel-permission": ["(hasRole|givePermissionTo|assignRole|spatie)\\b",
|
|
36
|
+
"(Permission|Role)::(create|findByName)\\b", "@can\\b|@role\\b|middleware.*role:"],
|
|
37
|
+
"laravel-i18n": ["(__\\(|trans\\(|trans_choice\\(|@lang)\\b",
|
|
38
|
+
"Lang::|->locale\\(|setLocale\\b"],
|
|
39
|
+
"laravel-vite": ["(@vite|@viteReactRefresh|Vite::)\\b"],
|
|
40
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js skill-trigger patterns, ported verbatim from `nextjs_skill_triggers.py`.
|
|
3
|
+
* Matched case-insensitively (source `re.IGNORECASE`).
|
|
4
|
+
*/
|
|
5
|
+
import { SHADCN } from "./shadcn";
|
|
6
|
+
|
|
7
|
+
/** Map of Next.js sub-skill name → triggering code patterns. */
|
|
8
|
+
export const NEXTJS_TRIGGERS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
9
|
+
"better-auth": ["(authClient|betterAuth|createAuthClient)\\b",
|
|
10
|
+
"(signIn|signUp|signOut|useSession|getSession)\\b", "auth\\.(api|handler)\\b",
|
|
11
|
+
"(prismaAdapter|drizzleAdapter|mongodbAdapter)\\b",
|
|
12
|
+
"(twoFactor|passkey|magicLink|emailOtp|organization)\\b",
|
|
13
|
+
"(apiKey|bearer|jwt|sso|scim|captcha|anonymous)\\b", "from\\s+['\"].*better-auth"],
|
|
14
|
+
"nextjs-tanstack-form": ["(useForm|useAppForm|createFormHook|formOptions)\\b",
|
|
15
|
+
"(mergeForm|formApi|FieldApi|FormApi)\\b",
|
|
16
|
+
"form\\.(Field|Subscribe|handleSubmit)\\b", "(zodValidator|onServerValidate)\\b",
|
|
17
|
+
"from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"],
|
|
18
|
+
"prisma-7": ["(PrismaClient|prismaAdapter)\\b", "prisma\\.(\\w+\\.\\w+|\\$\\w+)",
|
|
19
|
+
"(globalForPrisma|\\$transaction|\\$queryRaw|\\$executeRaw)\\b",
|
|
20
|
+
"from\\s+['\"](@prisma|\\..*generated.*prisma)"],
|
|
21
|
+
"nextjs-shadcn": SHADCN,
|
|
22
|
+
"nextjs-zustand": ["(create|createStore)\\(\\s*\\(\\s*set", "from\\s+['\"]zustand(/\\w+)?\"",
|
|
23
|
+
"(useShallow|useStore|skipHydration)\\b", "\\.(getState|setState|subscribe)\\(\\)",
|
|
24
|
+
"(persist|devtools|immer)\\("],
|
|
25
|
+
"nextjs-i18n": ["(useTranslations|useLocale|useMessages|useFormatter)\\b",
|
|
26
|
+
"(getTranslations|getLocale|getMessages|getFormatter)\\b",
|
|
27
|
+
"(NextIntlClientProvider|defineRouting)\\b", "from\\s+['\"]next-intl(/\\w+)?\"",
|
|
28
|
+
"\\bt\\(\\s*['\"]"],
|
|
29
|
+
};
|