@fusengine/harness 0.1.90 → 0.1.92
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-BF1dZFjY.mjs} +1194 -144
- 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-Dy8g9Ybl.mjs +397 -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 +37 -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-budget.ts +144 -0
- package/src/adapters/cursor/context-limit.ts +115 -0
- package/src/adapters/cursor/context.ts +65 -0
- package/src/adapters/cursor/events.ts +38 -0
- package/src/adapters/cursor/index.ts +43 -0
- package/src/adapters/cursor/interfaces/context-budget.ts +25 -0
- package/src/adapters/cursor/interfaces/types.ts +88 -0
- package/src/adapters/cursor/native-response.ts +39 -0
- package/src/adapters/cursor/native-schemas.ts +161 -0
- package/src/adapters/cursor/normalize.ts +188 -0
- package/src/adapters/cursor/plugin-root.ts +103 -0
- package/src/adapters/cursor/respond.ts +170 -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 +197 -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 +106 -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 +55 -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 +99 -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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter parsing — pure text helpers (no fs). Ports `parse_frontmatter.py`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const BLOCK_SCALARS: ReadonlySet<string> = new Set(["|", ">", "|+", "|-", ">+", ">-"]);
|
|
6
|
+
|
|
7
|
+
/** Escape regex metacharacters in an arbitrary field name. */
|
|
8
|
+
function escapeRe(s: string): string {
|
|
9
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract a single frontmatter field's value from `text`. Strips surrounding
|
|
14
|
+
* quotes; skips YAML block-scalar markers. Returns "" when absent.
|
|
15
|
+
* @param text - The full document text.
|
|
16
|
+
* @param field - The frontmatter key to read.
|
|
17
|
+
* @returns The field value, or "".
|
|
18
|
+
*/
|
|
19
|
+
export function parseField(text: string, field: string): string {
|
|
20
|
+
const fm = /^---\s*\n([\s\S]*?)\n---/.exec(text);
|
|
21
|
+
if (!fm || fm[1] === undefined) return "";
|
|
22
|
+
const lineRe = new RegExp(`^${escapeRe(field)}\\s*:\\s*(.+)$`);
|
|
23
|
+
for (const line of fm[1].split("\n")) {
|
|
24
|
+
const m = lineRe.exec(line);
|
|
25
|
+
if (!m || m[1] === undefined) continue;
|
|
26
|
+
const val = m[1].trim().replace(/^["']|["']$/g, "");
|
|
27
|
+
if (BLOCK_SCALARS.has(val)) continue;
|
|
28
|
+
return val;
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Derive a short description from the body following the frontmatter: the first
|
|
35
|
+
* non-empty trimmed line, sliced to `maxLen`. Returns "" when none.
|
|
36
|
+
* @param text - The full document text.
|
|
37
|
+
* @param maxLen - Maximum length of the returned description.
|
|
38
|
+
* @returns The body-derived description, or "".
|
|
39
|
+
*/
|
|
40
|
+
export function parseBodyDesc(text: string, maxLen: number = 60): string {
|
|
41
|
+
const m = /^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/.exec(text);
|
|
42
|
+
if (!m || m[1] === undefined) return "";
|
|
43
|
+
for (const raw of m[1].split("\n")) {
|
|
44
|
+
const line = raw.trim();
|
|
45
|
+
if (line) return line.slice(0, maxLen);
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cartographer indicators — pure data sets used to detect a project root and to
|
|
3
|
+
* exclude noise directories when walking a tree. Ports the constant tables from
|
|
4
|
+
* `generate_project_map.py` / `write_recursive.py`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Filenames whose presence marks a directory as a project root. */
|
|
8
|
+
export const PROJECT_INDICATORS: ReadonlySet<string> = new Set([
|
|
9
|
+
"package.json", "deno.json", "bun.lockb", "bun.lock", "tsconfig.json",
|
|
10
|
+
"package-lock.json", "yarn.lock", "pnpm-lock.yaml", "pnpm-workspace.yaml",
|
|
11
|
+
"composer.json", "artisan", "Cargo.toml", "rust-toolchain.toml", "go.mod",
|
|
12
|
+
"pyproject.toml", "setup.py", "setup.cfg", "Pipfile", "requirements.txt",
|
|
13
|
+
"environment.yml", "Gemfile", "Package.swift", "Podfile", "pubspec.yaml",
|
|
14
|
+
"pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "build.sbt",
|
|
15
|
+
"Makefile", "CMakeLists.txt", "meson.build", "configure.ac",
|
|
16
|
+
"Directory.Build.props", "global.json", "mix.exs", "rebar.config",
|
|
17
|
+
"project.clj", "deps.edn", "stack.yaml", "cabal.project", "dune-project",
|
|
18
|
+
"build.zig", "gleam.toml", "v.mod", "Project.toml", "DESCRIPTION", "cpanfile",
|
|
19
|
+
"Makefile.PL", ".luacheckrc", "astro.config.mjs", "next.config.js",
|
|
20
|
+
"next.config.mjs", "nuxt.config.ts", "vite.config.ts", "next.config.ts",
|
|
21
|
+
"angular.json", "svelte.config.js", "svelte.config.ts", "main.tf",
|
|
22
|
+
"ansible.cfg", "pulumi.yaml", "cdk.json", "Chart.yaml", "wrangler.toml",
|
|
23
|
+
"fly.toml", "turbo.json", "nx.json", "BUILD", "WORKSPACE", "Justfile",
|
|
24
|
+
"Taskfile.yml", "docker-compose.yml", "docker-compose.yaml", "compose.yml",
|
|
25
|
+
"compose.yaml", "Dockerfile", ".git",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/** Directory names skipped entirely during the tree walk. */
|
|
29
|
+
export const EXCLUDE_DIRS: ReadonlySet<string> = new Set([
|
|
30
|
+
"node_modules", ".git", ".next", ".nuxt", "dist", "build", ".output",
|
|
31
|
+
"vendor", "__pycache__", ".venv", "venv", ".cartographer", ".claude",
|
|
32
|
+
".ruff_cache", ".DS_Store", "coverage", ".turbo", ".vercel", ".netlify",
|
|
33
|
+
"Pods", "DerivedData", ".build", ".swiftpm",
|
|
34
|
+
]);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { resolveMaxLines } from "../config/limits";
|
|
5
|
+
import { DEV_KEYWORDS, type ProjectType } from "./detect-project";
|
|
6
|
+
import { getExpertAgent } from "./expert-agents";
|
|
7
|
+
import { apexDocName, apexPlanTool, harnessHomeSegment } from "./apex-target";
|
|
8
|
+
import { detectClaudeMdProjectType } from "./detect-claude-md-project-type";
|
|
9
|
+
|
|
10
|
+
export { detectClaudeMdProjectType };
|
|
11
|
+
|
|
12
|
+
/** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
|
|
13
|
+
export const DEV_VERBS: RegExp =
|
|
14
|
+
/(cr[ée]er|impl[ée]menter|ajouter|d[ée]velopper|construire|build|refactor|migrer|implement|create|add|develop)/i;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build the APEX instruction preamble for a development task.
|
|
18
|
+
* @param projectType - Detected project type label.
|
|
19
|
+
* @param maxLines - SOLID per-file line ceiling.
|
|
20
|
+
* @param id - Harness target id (defaults to "claude-code" — zero-regression default).
|
|
21
|
+
* @returns The APEX instruction text.
|
|
22
|
+
*/
|
|
23
|
+
export function buildApexInstruction(projectType: ProjectType, maxLines: number, id: string = "claude-code"): string {
|
|
24
|
+
const expertAgent = getExpertAgent(projectType);
|
|
25
|
+
const seg = harnessHomeSegment(id);
|
|
26
|
+
const planTool = apexPlanTool(id);
|
|
27
|
+
return (
|
|
28
|
+
`INSTRUCTION: This is a development task. Use APEX methodology:\n\n` +
|
|
29
|
+
`**TRACKING FILE**: [project]/${seg}/apex/task.json — create it yourself via apex-methodology Step 0 (init-tracking) if missing\n\n` +
|
|
30
|
+
`1. **ANALYZE** (MANDATORY - 3 AGENTS IN PARALLEL):\n` +
|
|
31
|
+
` - explore-codebase + research-expert + ${expertAgent} (framework expertise)\n` +
|
|
32
|
+
` - Project type detected: ${projectType}\n\n` +
|
|
33
|
+
`2. **PLAN**: Use ${planTool} to break down tasks (<${maxLines} lines per file)\n\n` +
|
|
34
|
+
`3. **EXECUTE**: ${expertAgent}, follow SOLID principles, split at ${maxLines - 10} lines\n\n` +
|
|
35
|
+
`4. **eLICIT**: self-review with NAMED elicitation techniques (apex ref 03.5-elicit) — fix findings BEFORE validation\n\n` +
|
|
36
|
+
`5. **VERIFY**: functional check — run it, confirm references⇔declarations consistency\n\n` +
|
|
37
|
+
`6. **eXAMINE**: Run sniper agent after ANY modification\n\n` +
|
|
38
|
+
`**GATE**: eLicit + Verify BEFORE sniper — NEVER skip.\n\n` +
|
|
39
|
+
`**IMPORTANT**: Read ${seg}/apex/task.json to check documentation status before writing code.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the UserPromptSubmit injection text: read the target's root
|
|
45
|
+
* instructions doc (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, ...) and,
|
|
46
|
+
* when the prompt matches a dev verb, prepend the APEX instruction. Returns
|
|
47
|
+
* `null` when the doc is absent/unreadable (the hook then emits nothing).
|
|
48
|
+
* @param prompt - The raw user prompt.
|
|
49
|
+
* @param cwd - Project root (for project-type detection).
|
|
50
|
+
* @param id - Harness target id (defaults to "claude-code" — zero-regression default).
|
|
51
|
+
* @returns The injection text, or `null` to emit nothing.
|
|
52
|
+
*/
|
|
53
|
+
export function buildClaudeMdContext(prompt: string, cwd: string, id: string = "claude-code"): string | null {
|
|
54
|
+
const docName = apexDocName(id);
|
|
55
|
+
const claudeMd = join(homedir(), harnessHomeSegment(id), docName);
|
|
56
|
+
if (!existsSync(claudeMd)) return null;
|
|
57
|
+
let claudeContent: string;
|
|
58
|
+
try {
|
|
59
|
+
claudeContent = readFileSync(claudeMd, "utf-8");
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (!DEV_VERBS.test(prompt) && !DEV_KEYWORDS.test(prompt)) return `# ${docName}\n${claudeContent}`;
|
|
64
|
+
const apex = buildApexInstruction(detectClaudeMdProjectType(cwd), resolveMaxLines(), id);
|
|
65
|
+
return `${apex}\n\n# ${docName}\n${claudeContent}`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convention detectors (canonical module structure: interfaces, types,
|
|
3
|
+
* hooks, stores, query) — masked-content, per-language, single source for
|
|
4
|
+
* every convention gate.
|
|
5
|
+
*/
|
|
6
|
+
export * from "./strip";
|
|
7
|
+
export * from "./langs";
|
|
8
|
+
export * from "./interfaces";
|
|
9
|
+
export * from "./react-hooks";
|
|
10
|
+
export * from "./stores";
|
|
11
|
+
export * from "./query";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface/type-contract declaration detection, per language, over MASKED
|
|
3
|
+
* content (see `strip.ts` — comments and strings never match). Single source
|
|
4
|
+
* consumed by every interface gate; the four historical layers each carried
|
|
5
|
+
* their own regexes (alias `type X =` false positives, unanchored
|
|
6
|
+
* `class.*ABC`, missed `public protocol`/`sealed interface`/generics).
|
|
7
|
+
*
|
|
8
|
+
* Rule symmetry (owner spec): an EXPORTED contract belongs to its directory
|
|
9
|
+
* (interfaces/ resp. types/); a non-exported local declaration is allowed.
|
|
10
|
+
*
|
|
11
|
+
* Each detection carries a LEVEL: `"legacy"` (the exact historical semantics,
|
|
12
|
+
* hard deny byte-identical) vs `"extended"` (syntax widenings — Go
|
|
13
|
+
* `interface{` without the space, Python `Protocol` — advisory-first via
|
|
14
|
+
* `FUSE_CONVENTIONS_MODE`, owner Amendment 5). Go anchors at column 0: an
|
|
15
|
+
* interface declared INSIDE a function (indented, a true false positive — a
|
|
16
|
+
* local type cannot move to internal/interfaces/) never matches.
|
|
17
|
+
*/
|
|
18
|
+
import { langOfPath, lexProfileOf, type LangFamily } from "./langs";
|
|
19
|
+
import { maskCommentsAndStrings } from "./strip";
|
|
20
|
+
|
|
21
|
+
/** Detection level: historical semantics (hard deny) vs advisory widening. */
|
|
22
|
+
export type InterfaceDeclLevel = "legacy" | "extended";
|
|
23
|
+
|
|
24
|
+
const LEGACY_RE: Partial<Record<LangFamily, RegExp>> = {
|
|
25
|
+
ts: /^\s*export\s+(?:declare\s+)?interface\s+\w+/m,
|
|
26
|
+
py: /^\s*class\s+\w+\s*\([^)]*\b(?:ABC|ABCMeta)\b/m,
|
|
27
|
+
go: /^type\s+\w+\s+interface\s+\{/m,
|
|
28
|
+
rs: /^\s*(?:pub\s+)?trait\s+\w+/m,
|
|
29
|
+
java: /^\s*(?:(?:public|sealed|final|abstract)\s+)*(?:interface|@FunctionalInterface)\s*\w*|^\s*fun\s+interface\s+\w+/m,
|
|
30
|
+
php: /^\s*(?:abstract\s+class|interface)\s+\w+/m,
|
|
31
|
+
swift: /^\s*(?:(?:public|open|internal|private|fileprivate)\s+)?protocol\s+\w+/m,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const EXTENDED_RE: Partial<Record<LangFamily, RegExp>> = {
|
|
35
|
+
py: /^\s*class\s+\w+\s*\([^)]*\bProtocol\b/m,
|
|
36
|
+
go: /^type\s+\w+\s+interface\b/m,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Exported TS type alias (`export type X = …`) — belongs in `src/types/`. */
|
|
40
|
+
const TYPE_ALIAS_RE = /^\s*export\s+type\s+\w+\s*[=<]/m;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Classify the interface declaration level of a file (masked scan), or null.
|
|
44
|
+
* @param filePath - File path (extension selects the language).
|
|
45
|
+
* @param content - Raw file content (masked internally).
|
|
46
|
+
*/
|
|
47
|
+
export function interfaceDeclLevel(filePath: string, content: string): InterfaceDeclLevel | null {
|
|
48
|
+
const lang = langOfPath(filePath);
|
|
49
|
+
if (!lang) return null;
|
|
50
|
+
const masked = maskCommentsAndStrings(content, lexProfileOf(lang));
|
|
51
|
+
if (LEGACY_RE[lang]?.test(masked)) return "legacy";
|
|
52
|
+
if (EXTENDED_RE[lang]?.test(masked)) return "extended";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* True when the file declares an interface-like contract at any level.
|
|
58
|
+
* @param filePath - File path (extension selects the language).
|
|
59
|
+
* @param content - Raw file content (masked internally).
|
|
60
|
+
*/
|
|
61
|
+
export function declaresInterface(filePath: string, content: string): boolean {
|
|
62
|
+
return interfaceDeclLevel(filePath, content) !== null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* True when a TS-family file declares an EXPORTED type alias (masked scan).
|
|
67
|
+
* @param content - Raw file content.
|
|
68
|
+
*/
|
|
69
|
+
export function declaresExportedTypeAlias(content: string): boolean {
|
|
70
|
+
return TYPE_ALIAS_RE.test(maskCommentsAndStrings(content, "c"));
|
|
71
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language detection and canonical convention paths. Single source for
|
|
3
|
+
* "which family does this extension belong to" and "is this path already in
|
|
4
|
+
* the right convention directory" — every convention gate (interfaces,
|
|
5
|
+
* types, hooks, stores, query) consumes these predicates instead of
|
|
6
|
+
* re-deriving its own path rules.
|
|
7
|
+
*
|
|
8
|
+
* Canonical module structure (owner spec, 2026-07):
|
|
9
|
+
* modules/<feature>/components/ reusable module components
|
|
10
|
+
* modules/<feature>/src/interfaces/ interface Foo { } contracts
|
|
11
|
+
* modules/<feature>/src/types/ exported type aliases
|
|
12
|
+
* modules/<feature>/src/hooks/ custom use* hooks
|
|
13
|
+
* modules/<feature>/src/stores/ *.store.ts (Zustand, Pinia)
|
|
14
|
+
* modules/<feature>/src/query/ TanStack Query definitions
|
|
15
|
+
* (+ modules/cores/… for cross-feature code)
|
|
16
|
+
*/
|
|
17
|
+
import type { LexProfile } from "./strip";
|
|
18
|
+
|
|
19
|
+
/** Language family a convention rule applies to, or null when out of scope. */
|
|
20
|
+
export type LangFamily = "ts" | "py" | "go" | "rs" | "java" | "php" | "swift" | "rb" | "vue";
|
|
21
|
+
|
|
22
|
+
const EXT_LANG: Record<string, LangFamily> = {
|
|
23
|
+
ts: "ts", tsx: "ts", js: "ts", jsx: "ts", mts: "ts", mjs: "ts", astro: "ts", svelte: "ts",
|
|
24
|
+
py: "py", go: "go", rs: "rs", java: "java", kt: "java",
|
|
25
|
+
php: "php", swift: "swift", rb: "rb", vue: "vue",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Language family for a file path (by extension), or null when unsupported. */
|
|
29
|
+
export function langOfPath(filePath: string): LangFamily | null {
|
|
30
|
+
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
31
|
+
return EXT_LANG[ext] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Lexical profile for masking, derived from the language family. */
|
|
35
|
+
export function lexProfileOf(lang: LangFamily): LexProfile {
|
|
36
|
+
if (lang === "py") return "py";
|
|
37
|
+
if (lang === "rb") return "rb";
|
|
38
|
+
if (lang === "php") return "php";
|
|
39
|
+
return "c";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True for dotfiles/dependency dirs never subject to conventions. */
|
|
43
|
+
export function isVendorPath(filePath: string): boolean {
|
|
44
|
+
return /(^|\/)(node_modules|vendor|dist|build|\.next|coverage)\//.test(filePath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Path predicates — canonical convention directories. */
|
|
48
|
+
export const isInterfacesPath = (p: string): boolean => /\/(interfaces|Contracts|Protocols|traits)\//.test(p) || p.endsWith("traits.rs");
|
|
49
|
+
export const isTypesPath = (p: string): boolean => p.includes("/types/") || p.endsWith(".d.ts");
|
|
50
|
+
export const isHooksPath = (p: string): boolean => p.includes("/hooks/") || p.includes("/composables/");
|
|
51
|
+
export const isStoresPath = (p: string): boolean => p.includes("/stores/");
|
|
52
|
+
export const isQueryPath = (p: string): boolean => p.includes("/query/");
|
|
53
|
+
export const isComponentsPath = (p: string): boolean => p.includes("/components/");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack Query definition detection over MASKED content (React and Vue
|
|
3
|
+
* variants share the call names; the family scoping is the gate's job, via
|
|
4
|
+
* project deps). Canonical home: `src/query/` — one file per domain. Only
|
|
5
|
+
* DEFINITIONS are gated; inline consumption of a hook defined in `query/`
|
|
6
|
+
* stays allowed.
|
|
7
|
+
*/
|
|
8
|
+
import { maskCommentsAndStrings } from "./strip";
|
|
9
|
+
import { nearestManifestDir, projectCaps } from "../nearest-manifest";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
/** Import line of a TanStack Query package (raw scan: masking blanks the module string). */
|
|
13
|
+
const QUERY_IMPORT_RE = /^\s*import\s+[^;]*@tanstack\/[\w-]*query[\w-]*/m;
|
|
14
|
+
|
|
15
|
+
/** A TanStack Query definition call. */
|
|
16
|
+
const QUERY_CALL_RE = /\b(?:useQuery|useMutation|useInfiniteQuery|queryOptions)\s*(?:<[^()]*>)?\s*\(/;
|
|
17
|
+
|
|
18
|
+
/** Exported declaration (`export const|function`) — a defined query hook. */
|
|
19
|
+
const EXPORT_DECL_RE = /^\s*export\s+(?:const|function)\s+\w+/m;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True when the file imports a TanStack Query package (raw scan, anchored to
|
|
23
|
+
* real import lines so `// import …` comments cannot match).
|
|
24
|
+
* @param content - Raw file content.
|
|
25
|
+
*/
|
|
26
|
+
export function importsTanstackQuery(content: string): boolean {
|
|
27
|
+
return QUERY_IMPORT_RE.test(content);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* True when the file DEFINES an exported query/mutation hook (export +
|
|
32
|
+
* query call, masked scan) — as opposed to merely consuming one inline.
|
|
33
|
+
* @param content - Raw file content.
|
|
34
|
+
*/
|
|
35
|
+
export function declaresQueryHook(content: string): boolean {
|
|
36
|
+
const masked = maskCommentsAndStrings(content, "c");
|
|
37
|
+
return QUERY_CALL_RE.test(masked) && EXPORT_DECL_RE.test(masked);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True when the nearest manifest of `filePath` declares a TanStack Query
|
|
42
|
+
* dependency (`@tanstack/react-query` or `@tanstack/vue-query`) — the
|
|
43
|
+
* cap gate of every query convention (owner: no cap, no query rule).
|
|
44
|
+
* @param filePath - The file being judged (its nearest manifest wins).
|
|
45
|
+
*/
|
|
46
|
+
export function queryCapActive(filePath: string): boolean {
|
|
47
|
+
const caps = projectCaps(nearestManifestDir(dirname(resolve(filePath))));
|
|
48
|
+
return caps.has("tanstack-query") || caps.has("vue-query");
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom React hook declaration detection over MASKED content. Historical
|
|
3
|
+
* rule (`framework-solid-gates.ts`): an exported custom hook belongs in
|
|
4
|
+
* `hooks/` — the regex was naive (`^export (function|const) use[A-Z]`,
|
|
5
|
+
* missing `export default`, `async`, and matching inside comments/strings).
|
|
6
|
+
*/
|
|
7
|
+
import { maskCommentsAndStrings } from "./strip";
|
|
8
|
+
|
|
9
|
+
/** Exported custom hook: `export [default] [async] function|const useX`. */
|
|
10
|
+
const HOOK_DECL_RE = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:function|const)\s+use[A-Z]\w*/m;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* True when the file declares an exported custom `use*` hook (masked scan).
|
|
14
|
+
* @param content - Raw file content.
|
|
15
|
+
*/
|
|
16
|
+
export function declaresCustomHook(content: string): boolean {
|
|
17
|
+
return HOOK_DECL_RE.test(maskCommentsAndStrings(content, "c"));
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared store/query-definition exemption (owner F0.2): a file is governed
|
|
3
|
+
* by the store/query conventions — NOT by the hook-location rule — ONLY when
|
|
4
|
+
* its signature is validated AND its ecosystem is confirmed:
|
|
5
|
+
* - store: the real `zustand`/`pinia` import (or the v5 curried signature);
|
|
6
|
+
* - query: a TanStack Query definition AND the `@tanstack/*-query` cap in
|
|
7
|
+
* the nearest manifest. Without the cap, a fake `useQuery(` caller is just
|
|
8
|
+
* a hook — the legacy hook rule applies, byte-identical.
|
|
9
|
+
*/
|
|
10
|
+
import { declaresStore } from "./stores";
|
|
11
|
+
import { declaresQueryHook, queryCapActive } from "./query";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* True when the file is a store or query definition (exempt from hook rules).
|
|
15
|
+
* @param filePath - The file being judged (manifest resolution for the cap).
|
|
16
|
+
* @param content - Its RAW content (detectors mask internally).
|
|
17
|
+
*/
|
|
18
|
+
export function isStoreOrQueryDefinition(filePath: string, content: string): boolean {
|
|
19
|
+
if (declaresStore(content, "zustand") || declaresStore(content, "pinia")) return true;
|
|
20
|
+
return declaresQueryHook(content) && queryCapActive(filePath);
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State-store declaration detection over MASKED content — Zustand (React
|
|
3
|
+
* family) and Pinia (Vue family), per the owner's framework scoping: a rule
|
|
4
|
+
* only fires inside its own family (the gate checks the project deps, this
|
|
5
|
+
* module only reads content). Canonical home: `src/stores/`, `*.store.ts`,
|
|
6
|
+
* one store per domain, budget = ratio 0.4 of the global line limit.
|
|
7
|
+
*
|
|
8
|
+
* Signature-strict (audit lesson): `Object.create(` and home-made
|
|
9
|
+
* `create(` factories never match — Zustand requires the `zustand` import
|
|
10
|
+
* (or the v5 curried `create<T>()(...)` call), Pinia requires the `pinia`
|
|
11
|
+
* import alongside `defineStore(`.
|
|
12
|
+
*/
|
|
13
|
+
import { maskCommentsAndStrings, maskCommentsOnly } from "./strip";
|
|
14
|
+
|
|
15
|
+
/** Store library whose declarations are being scanned. */
|
|
16
|
+
export type StoreLib = "zustand" | "pinia";
|
|
17
|
+
|
|
18
|
+
/** Import line of the store package — comments masked (a `// import …` line
|
|
19
|
+
* never matches), strings KEPT (masking would blank the module literal). */
|
|
20
|
+
const STORE_IMPORT_RE: Record<StoreLib, RegExp> = {
|
|
21
|
+
zustand: /^\s*import\s+[^;]*from\s+['"]zustand/m,
|
|
22
|
+
pinia: /^\s*import\s+[^;]*from\s+['"]pinia/m,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Zustand v5 curried declaration: `export const useX = create<S>()(`. */
|
|
26
|
+
const ZUSTAND_CURRIED_RE = /^\s*export\s+const\s+\w+\s*=\s*create\s*<[^=]*>\s*\(\s*\)\s*\(/m;
|
|
27
|
+
/** Zustand declaration with import context: `export const useX = create(`. */
|
|
28
|
+
const ZUSTAND_DECL_RE = /^\s*export\s+const\s+\w+\s*=\s*create(?:Store)?\s*(?:<[^(]+>)?\s*\(/m;
|
|
29
|
+
/** Pinia declaration: `export const useX = defineStore(`. */
|
|
30
|
+
const PINIA_DECL_RE = /^\s*export\s+const\s+\w+\s*=\s*defineStore\s*\(/m;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* True when the file declares a store of the given library (masked scan,
|
|
34
|
+
* signature-strict).
|
|
35
|
+
* @param content - Raw file content.
|
|
36
|
+
* @param lib - Store library to detect.
|
|
37
|
+
*/
|
|
38
|
+
export function declaresStore(content: string, lib: StoreLib): boolean {
|
|
39
|
+
const masked = maskCommentsAndStrings(content, "c");
|
|
40
|
+
const importable = maskCommentsOnly(content, "c");
|
|
41
|
+
if (lib === "pinia") return STORE_IMPORT_RE.pinia.test(importable) && PINIA_DECL_RE.test(masked);
|
|
42
|
+
return ZUSTAND_CURRIED_RE.test(masked) || (STORE_IMPORT_RE.zustand.test(importable) && ZUSTAND_DECL_RE.test(masked));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Count store declarations in one file (multi-store advisory: one per domain).
|
|
47
|
+
* @param content - Raw file content.
|
|
48
|
+
* @param lib - Store library to count.
|
|
49
|
+
*/
|
|
50
|
+
export function countStores(content: string, lib: StoreLib): number {
|
|
51
|
+
const masked = maskCommentsAndStrings(content, "c");
|
|
52
|
+
const re = lib === "pinia" ? PINIA_DECL_RE : ZUSTAND_DECL_RE;
|
|
53
|
+
const curried = masked.match(new RegExp(ZUSTAND_CURRIED_RE.source, "gm"))?.length ?? 0;
|
|
54
|
+
const plain = masked.match(new RegExp(re.source, "gm"))?.length ?? 0;
|
|
55
|
+
return lib === "pinia" ? plain : Math.max(curried, plain);
|
|
56
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical comment/string masking for convention detectors. Every convention
|
|
3
|
+
* rule (interfaces, types, hooks, stores, query) matches against MASKED
|
|
4
|
+
* content so a declaration-looking line inside a comment, string, template
|
|
5
|
+
* literal, or heredoc can never trigger a verdict. Masking replaces the
|
|
6
|
+
* covered CHARACTERS with spaces but keeps every newline: line offsets and
|
|
7
|
+
* `^…/m` anchors stay valid, and the output has the same line count.
|
|
8
|
+
* Not a parser — a per-family state machine (c-like, python, ruby, php).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Lexical profile of a language family. */
|
|
12
|
+
export type LexProfile = "c" | "py" | "rb" | "php";
|
|
13
|
+
|
|
14
|
+
/** Mask PHP heredoc/nowdoc blocks (`<<<TAG` … terminator line), offsets kept. */
|
|
15
|
+
function maskHeredocs(content: string): string {
|
|
16
|
+
return content.replace(/<<<['"]?(\w+)['"]?\r?\n[\s\S]*?^\1;?$/gm, (block) => block.replace(/[^\n]/g, " "));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mask comments and strings of `content` according to its lexical profile.
|
|
21
|
+
* @param content - Source text.
|
|
22
|
+
* @param profile - Lexical family (see `langs.ts` `lexProfileOf`).
|
|
23
|
+
* @returns Same-length text with comment/string interiors blanked.
|
|
24
|
+
*/
|
|
25
|
+
export function maskCommentsAndStrings(content: string, profile: LexProfile): string {
|
|
26
|
+
return maskScan(content, profile, false);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mask COMMENTS only (strings kept) — for detectors whose signal IS a string
|
|
31
|
+
* literal (module imports like `from "zustand"`): a `// import …` comment
|
|
32
|
+
* cannot match, while the real import line stays readable.
|
|
33
|
+
* @param content - Source text.
|
|
34
|
+
* @param profile - Lexical family.
|
|
35
|
+
* @returns Same-length text with comment interiors blanked.
|
|
36
|
+
*/
|
|
37
|
+
export function maskCommentsOnly(content: string, profile: LexProfile): string {
|
|
38
|
+
return maskScan(content, profile, true);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Shared scanner; `keepStrings` skips the string-masking branch. */
|
|
42
|
+
function maskScan(content: string, profile: LexProfile, keepStrings: boolean): string {
|
|
43
|
+
if (profile === "php") content = maskHeredocs(content);
|
|
44
|
+
const out = content.split("");
|
|
45
|
+
const blank = (i: number) => { if (out[i] !== "\n") out[i] = " "; };
|
|
46
|
+
const isPy = profile === "py" || profile === "rb";
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < out.length) {
|
|
49
|
+
const two = content.slice(i, i + 2);
|
|
50
|
+
if (!isPy && two === "//") { while (i < out.length && out[i] !== "\n") blank(i++); continue; }
|
|
51
|
+
if (isPy && out[i] === "#") { while (i < out.length && out[i] !== "\n") blank(i++); continue; }
|
|
52
|
+
if (profile === "php" && out[i] === "#") { while (i < out.length && out[i] !== "\n") blank(i++); continue; }
|
|
53
|
+
if (!isPy && two === "/*") { const end = content.indexOf("*/", i + 2); const stop = end === -1 ? out.length : end + 2; while (i < stop) blank(i++); continue; }
|
|
54
|
+
if (isPy && (content.startsWith("'''", i) || content.startsWith('"""', i))) {
|
|
55
|
+
const q = content.slice(i, i + 3);
|
|
56
|
+
const end = content.indexOf(q, i + 3);
|
|
57
|
+
const stop = end === -1 ? out.length : end + 3;
|
|
58
|
+
while (i < stop) blank(i++);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (!keepStrings && (out[i] === "'" || out[i] === '"' || (profile === "c" && out[i] === "`"))) {
|
|
62
|
+
const q = out[i] as string;
|
|
63
|
+
// Unterminated-quote guard: a single/double quote never opens a string
|
|
64
|
+
// past its own line (JS/TS has no multiline plain strings — a JSX
|
|
65
|
+
// apostrophe like `Don't` must not swallow the rest of the file), and
|
|
66
|
+
// an unterminated template literal masks nothing (unpaired backtick).
|
|
67
|
+
let j = i + 1, closed = -1;
|
|
68
|
+
while (j < out.length && out[j] !== "\n") {
|
|
69
|
+
if (out[j] === "\\") { j += 2; continue; }
|
|
70
|
+
if (out[j] === q) { closed = j; break; }
|
|
71
|
+
j++;
|
|
72
|
+
}
|
|
73
|
+
if (q === "`") {
|
|
74
|
+
if (closed !== -1) { while (i <= closed) blank(i++); continue; }
|
|
75
|
+
const end = content.indexOf("`", i + 1);
|
|
76
|
+
if (end !== -1) { while (i <= end) blank(i++); continue; }
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (closed === -1) { i++; continue; }
|
|
81
|
+
while (i <= closed) blank(i++);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
return out.join("");
|
|
87
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollout control for NEW convention denies (owner decision, inverted
|
|
3
|
+
* 2026-07): new convention rules BLOCK by default — `FUSE_CONVENTIONS_MODE`
|
|
4
|
+
* is an opt-OUT. Set it to `advisory` to observe without blocking (`inform`
|
|
5
|
+
* output); any absent or invalid value means `deny`. Denies that already
|
|
6
|
+
* existed before the conventions module are unaffected (byte-parity); this
|
|
7
|
+
* module only ever MODULATES the new ones.
|
|
8
|
+
*/
|
|
9
|
+
import type { Prompt } from "../../prompt/types";
|
|
10
|
+
|
|
11
|
+
/** Rollout mode for new convention denies. */
|
|
12
|
+
export type ConventionsMode = "advisory" | "deny";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Current rollout mode: `advisory` ONLY on the exact explicit value —
|
|
16
|
+
* anything else (absent, typo, empty) is `deny` (owner decision: deny-first).
|
|
17
|
+
* @param env - Environment (defaults to `process.env`).
|
|
18
|
+
*/
|
|
19
|
+
export function conventionsMode(env: Record<string, string | undefined> = process.env): ConventionsMode {
|
|
20
|
+
return env.FUSE_CONVENTIONS_MODE === "advisory" ? "advisory" : "deny";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a convention verdict under the rollout flag: a NEW rule yields an
|
|
25
|
+
* `inform` prompt in advisory mode (explicit opt-out), the blocking prompt
|
|
26
|
+
* otherwise; an EXISTING rule always yields the blocking prompt unchanged.
|
|
27
|
+
* @param prompt - The blocking prompt the rule would emit.
|
|
28
|
+
* @param isNew - True when the rule is new (post-conventions module).
|
|
29
|
+
* @param env - Environment (defaults to `process.env`).
|
|
30
|
+
*/
|
|
31
|
+
export function rolloutVerdict(
|
|
32
|
+
prompt: Prompt,
|
|
33
|
+
isNew: boolean,
|
|
34
|
+
env: Record<string, string | undefined> = process.env,
|
|
35
|
+
): Prompt {
|
|
36
|
+
if (!isNew || conventionsMode(env) === "deny") return prompt;
|
|
37
|
+
return { kind: "inform", title: prompt.title, reason: prompt.reason, actions: prompt.actions };
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const CREATE_RE = /\b(?:create|implement|add|build|new|feature|component|generate|make|develop|scaffold)\b/i;
|
|
2
|
+
const SKIP_RE = /\b(?:fix|bug|debug|update|refactor|rename|move|delete|remove|commit|push|status|edit|modify|change|adjust|tweak|existing|current)\b/i;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* True when a prompt expresses creation intent (a new feature/component) and is
|
|
6
|
+
* not a fix/refactor — the signal that brainstorming should precede creation.
|
|
7
|
+
* The harness calls this on UserPromptSubmit, then `recordBrainstormRequired`.
|
|
8
|
+
*/
|
|
9
|
+
export function detectCreationIntent(prompt: string): boolean {
|
|
10
|
+
return CREATE_RE.test(prompt) && !SKIP_RE.test(prompt);
|
|
11
|
+
}
|