@fusengine/harness 0.1.89 → 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 +5 -4
- 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-DHhukRdh.mjs → handle-C43gA-Pr.mjs} +737 -150
- 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/permission-mode-B-SFyR0X.mjs +226 -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
- package/dist/permission-mode-B0MUl7FT.mjs +0 -136
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveMaxLines } from "../config/limits";
|
|
4
|
+
import { harnessHomeSegment } from "./apex-target";
|
|
5
|
+
import { apexTaskSteps } from "./apex-target-steps";
|
|
6
|
+
|
|
7
|
+
/** Parsed task state injected into a Task sub-agent prompt. */
|
|
8
|
+
export interface ApexTaskState {
|
|
9
|
+
/** Current task id (defaults to "1"). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Task subject (defaults to ""). */
|
|
12
|
+
subject: string;
|
|
13
|
+
/** Current phase (defaults to "analyze"). */
|
|
14
|
+
phase: string;
|
|
15
|
+
/** Comma-joined consulted doc keys, or "none". */
|
|
16
|
+
docs: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read the current task state from `task.json`, reproducing the legacy Python
|
|
21
|
+
* logic. Any read/parse error falls back to `("1", "", "analyze", "none")`.
|
|
22
|
+
* @param taskFile - Absolute path to `.claude/apex/task.json`.
|
|
23
|
+
* @returns The parsed {@link ApexTaskState}.
|
|
24
|
+
*/
|
|
25
|
+
export function loadApexTaskState(taskFile: string): ApexTaskState {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(readFileSync(taskFile, "utf-8")) as Record<string, unknown>;
|
|
28
|
+
const id = String(data.current_task ?? "1");
|
|
29
|
+
const tasks = (data.tasks as Record<string, unknown> | undefined) ?? {};
|
|
30
|
+
const task = (tasks[id] as Record<string, unknown> | undefined) ?? {};
|
|
31
|
+
const subject = typeof task.subject === "string" ? task.subject : "";
|
|
32
|
+
const phase = typeof task.phase === "string" ? task.phase : "analyze";
|
|
33
|
+
const consultedMap = (task.doc_consulted as Record<string, unknown> | undefined) ?? {};
|
|
34
|
+
const consulted = Object.entries(consultedMap)
|
|
35
|
+
.filter(([, v]) => typeof v === "object" && v !== null && (v as Record<string, unknown>).consulted === true)
|
|
36
|
+
.map(([k]) => k);
|
|
37
|
+
return { id, subject, phase, docs: consulted.join(", ") || "none" };
|
|
38
|
+
} catch {
|
|
39
|
+
return { id: "1", subject: "", phase: "analyze", docs: "none" };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the APEX context string injected into a Task sub-agent prompt.
|
|
45
|
+
* @param state - The parsed task state.
|
|
46
|
+
* @param maxLines - SOLID per-file line ceiling.
|
|
47
|
+
* @param id - Harness target id (defaults to "claude-code" — zero-regression default).
|
|
48
|
+
* @returns The injection text.
|
|
49
|
+
*/
|
|
50
|
+
export function buildApexTaskContext(state: ApexTaskState, maxLines: number, id: string = "claude-code"): string {
|
|
51
|
+
const seg = harnessHomeSegment(id);
|
|
52
|
+
const { step3, step4, step7 } = apexTaskSteps(id);
|
|
53
|
+
return (
|
|
54
|
+
`⚠️ APEX MODE - Read ${seg}/apex/AGENTS.md for rules\n\n` +
|
|
55
|
+
`Current: Task #${state.id} - ${state.subject} (Phase: ${state.phase})\n` +
|
|
56
|
+
`Docs consulted: ${state.docs}\n\n` +
|
|
57
|
+
`Agent must:\n` +
|
|
58
|
+
`1. Read task.json → find last 3 completed tasks\n` +
|
|
59
|
+
`2. Read their notes in docs/ (task-{ID}-{subject}.md)\n` +
|
|
60
|
+
`3. ${step3}\n` +
|
|
61
|
+
`4. ${step4}\n` +
|
|
62
|
+
`5. Apply SOLID (files < ${maxLines} lines)\n` +
|
|
63
|
+
`6. Write notes to docs/task-{ID}-{subject}.md\n` +
|
|
64
|
+
`7. ${step7}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the PreToolUse Task injection, gated on the existence of the
|
|
70
|
+
* project's target apex dir (`.claude/apex/`, `.codex/apex/`, ...). Returns
|
|
71
|
+
* `null` when APEX is not active (no dir).
|
|
72
|
+
* @param projectRoot - `CLAUDE_PROJECT_DIR` or cwd.
|
|
73
|
+
* @param id - Harness target id (defaults to "claude-code" — zero-regression default).
|
|
74
|
+
* @returns The injection text, or `null` to emit nothing.
|
|
75
|
+
*/
|
|
76
|
+
export function buildApexTaskInjection(projectRoot: string, id: string = "claude-code"): string | null {
|
|
77
|
+
const apexDir = join(projectRoot, harnessHomeSegment(id), "apex");
|
|
78
|
+
if (!existsSync(apexDir)) return null;
|
|
79
|
+
const state = loadApexTaskState(join(apexDir, "task.json"));
|
|
80
|
+
return buildApexTaskContext(state, resolveMaxLines(), id);
|
|
81
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { formatDocDeny, isDocConsulted, type AuthEntry } from "../freshness/doc-helpers";
|
|
2
|
+
import type { RefMeta } from "../refs/types";
|
|
3
|
+
import type { Prompt } from "../prompt/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Session context for the stateful APEX gates. The harness adapter supplies this
|
|
7
|
+
* (the package owns the gate LOGIC; recording the session activity is the
|
|
8
|
+
* adapter's tracking layer).
|
|
9
|
+
*/
|
|
10
|
+
export interface ApexContext {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
framework: string;
|
|
13
|
+
filePath: string;
|
|
14
|
+
content: string;
|
|
15
|
+
/** Doc-consultation authorizations from session state (Context7/Exa). */
|
|
16
|
+
authorizations?: Record<string, AuthEntry>;
|
|
17
|
+
/** Available SOLID references for the framework's skill. */
|
|
18
|
+
refs?: RefMeta[];
|
|
19
|
+
/** Absolute paths of SOLID refs already read this session. */
|
|
20
|
+
refsRead?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Epoch-ms read timestamp per `refsRead` path (parity track-solid-reads.py
|
|
23
|
+
* `solid_reads[].timestamp`). PARITY: the TTL applies ONLY to solidReadGate —
|
|
24
|
+
* Python TTL-izes SOLID reads exclusively (require-solid-read.py); the
|
|
25
|
+
* skill-trigger/design/shadcn gates stay session-scoped (no TTL). A path in
|
|
26
|
+
* `refsRead` with no stamp here (tracks recorded before this field existed)
|
|
27
|
+
* counts as read — backward compat.
|
|
28
|
+
*/
|
|
29
|
+
refsReadAt?: Record<string, number>;
|
|
30
|
+
/** Current epoch ms for the SOLID-read TTL check; absent → reads never expire. */
|
|
31
|
+
now?: number;
|
|
32
|
+
/** Whether the required prior agents (explore + research) ran within the freshness window. */
|
|
33
|
+
agentsFresh?: boolean;
|
|
34
|
+
/** Names of REQUIRED_AGENTS that have NOT run fresh (subset), for a precise freshnessGate message. Absent → generic wording. */
|
|
35
|
+
missingAgents?: string[];
|
|
36
|
+
/** Freshness window in ms: labels the block messages' TTL (e.g. "2min") and bounds the SOLID-read TTL in solidReadGate. */
|
|
37
|
+
windowMs?: number;
|
|
38
|
+
/** Whether brainstorming is required for this edit (creation intent on a new file). */
|
|
39
|
+
brainstormRequired?: boolean;
|
|
40
|
+
/** Whether the brainstorming agent ran within the window. */
|
|
41
|
+
brainstormFresh?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A single APEX gate: returns a blocking {@link Prompt}, or null to pass. */
|
|
45
|
+
export type ApexGate = (ctx: ApexContext) => Prompt | null;
|
|
46
|
+
|
|
47
|
+
/** Gate: BOTH Context7 AND Exa (or a web fallback alone) must have been consulted this session. */
|
|
48
|
+
export const docConsultedGate: ApexGate = (ctx) =>
|
|
49
|
+
isDocConsulted(ctx.authorizations, ctx.sessionId)
|
|
50
|
+
? null
|
|
51
|
+
: {
|
|
52
|
+
kind: "block",
|
|
53
|
+
title: "APEX: documentation not consulted",
|
|
54
|
+
reason: formatDocDeny(ctx.framework),
|
|
55
|
+
actions: ["Use BOTH mcp__context7__query-docs AND mcp__exa__web_search_exa, or a web fallback alone (WebSearch/WebFetch)"],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Gate: brainstorming must precede creating new files when flagged. */
|
|
59
|
+
export const brainstormGate: ApexGate = (ctx) =>
|
|
60
|
+
ctx.brainstormRequired && ctx.brainstormFresh === false
|
|
61
|
+
? {
|
|
62
|
+
kind: "block",
|
|
63
|
+
title: "APEX: brainstorm first",
|
|
64
|
+
reason: `Creation intent detected — brainstorm before creating new ${ctx.framework} files.`,
|
|
65
|
+
actions: ["Launch the brainstorming agent"],
|
|
66
|
+
}
|
|
67
|
+
: null;
|
|
68
|
+
|
|
69
|
+
// solidReadGate/freshnessGate live in ./apex-gates (kept out of this file for
|
|
70
|
+
// the SOLID file-size ceiling — they import ApexContext/ApexGate from here).
|
|
71
|
+
export { solidReadGate, freshnessGate } from "./apex-gates";
|
|
72
|
+
import { solidReadGate, freshnessGate } from "./apex-gates";
|
|
73
|
+
|
|
74
|
+
/** Gates that run BEFORE Check 1 (harness-specific brainstorm + agent freshness, pre-dating enforce-apex-phases). */
|
|
75
|
+
export const PRE_AUTH_GATES: ReadonlyArray<ApexGate> = [brainstormGate, freshnessGate];
|
|
76
|
+
|
|
77
|
+
/** Gates that run AFTER Check 1: Check 2 (doc consulted once per session) then the SOLID refs. */
|
|
78
|
+
export const POST_AUTH_GATES: ReadonlyArray<ApexGate> = [docConsultedGate, solidReadGate];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Default APEX gate chain (brainstorm, freshness, docs, SOLID refs). Check 1
|
|
82
|
+
* (`apexAuthorizationGate`, policy/apex-authorization.ts) is wired BETWEEN the
|
|
83
|
+
* PRE and POST sub-chains by the runtime (gate-apex.ts), which owns its
|
|
84
|
+
* target-write side effect — parity enforce-apex-phases.ts Check1 -> Check2.
|
|
85
|
+
*/
|
|
86
|
+
export const APEX_GATES: ReadonlyArray<ApexGate> = [...PRE_AUTH_GATES, ...POST_AUTH_GATES];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Run the APEX gates (chain-of-responsibility): the first failing gate's prompt
|
|
90
|
+
* wins; null means every gate passed (allow).
|
|
91
|
+
*/
|
|
92
|
+
export function evaluateApex(ctx: ApexContext, gates: ReadonlyArray<ApexGate> = APEX_GATES): Prompt | null {
|
|
93
|
+
return gates.reduce<Prompt | null>((hit, gate) => hit ?? gate(ctx), null);
|
|
94
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indented Unicode tree from scanned plugin items — pure text in, markdown out
|
|
3
|
+
* (no fs). Ports `build_tree.py`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A scanned `[type, name, desc]` plugin item (agent/skill/command/hooks). */
|
|
7
|
+
export type ScanRow = readonly [type: string, name: string, desc: string];
|
|
8
|
+
|
|
9
|
+
const SECTION_ORDER: readonly string[] = ["agent", "skill", "command"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format grouped items with tree connectors and optional markdown links.
|
|
13
|
+
* Skill sections link to `./skills/<name>/index.md`; other sections to
|
|
14
|
+
* `./<folder>/<name>.md`; unlinked sections render the bare name.
|
|
15
|
+
* @param prefix - The line prefix (indent + branch glyphs).
|
|
16
|
+
* @param items - The `[name, desc]` pairs to render.
|
|
17
|
+
* @param folder - The link folder ("" disables linking).
|
|
18
|
+
* @param asDirs - Whether items link to a subdirectory `index.md`.
|
|
19
|
+
* @returns The rendered lines.
|
|
20
|
+
*/
|
|
21
|
+
function printItems(prefix: string, items: ReadonlyArray<[string, string]>, folder: string, asDirs: boolean): string[] {
|
|
22
|
+
return items.map(([name, desc], i) => {
|
|
23
|
+
const connector = i === items.length - 1 ? "└──" : "├──";
|
|
24
|
+
const safe = name.replace(/^\/+/, "");
|
|
25
|
+
let label = name;
|
|
26
|
+
if (folder && asDirs) label = `[${name}](./${folder}/${safe}/index.md)`;
|
|
27
|
+
else if (folder) label = `[${name}](./${folder}/${safe}.md)`;
|
|
28
|
+
const short = desc && desc !== "(no description)" ? ` — ${desc.slice(0, 80)}` : "";
|
|
29
|
+
return `${prefix}${connector} ${label}${short}`;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build an indented tree from scanned items. The `hooks` row renders as a single
|
|
35
|
+
* trailing `└── hooks: …` line; agents/skills/commands render as folder sections.
|
|
36
|
+
* @param items - The scanned `[type, name, desc]` rows.
|
|
37
|
+
* @param linked - When true, leaf names become markdown links.
|
|
38
|
+
* @returns The joined tree text.
|
|
39
|
+
*/
|
|
40
|
+
export function buildTree(items: ReadonlyArray<ScanRow>, linked: boolean = false): string {
|
|
41
|
+
const groups: Record<string, [string, string][]> = {};
|
|
42
|
+
let hooksLine = "";
|
|
43
|
+
for (const [typ, name, desc] of items) {
|
|
44
|
+
if (typ === "hooks") hooksLine = name;
|
|
45
|
+
else (groups[typ] ??= []).push([name, desc]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sections = SECTION_ORDER.filter((s) => s in groups);
|
|
49
|
+
if (hooksLine) sections.push("hooks");
|
|
50
|
+
|
|
51
|
+
const lines: string[] = [];
|
|
52
|
+
const total = sections.length;
|
|
53
|
+
for (let idx = 0; idx < total; idx++) {
|
|
54
|
+
const section = sections[idx] ?? "";
|
|
55
|
+
if (section === "hooks") {
|
|
56
|
+
lines.push(`└── hooks: ${hooksLine}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const isLast = idx === total - 1;
|
|
60
|
+
const folder = `${section}s`;
|
|
61
|
+
const prefix = isLast ? "└──" : "├──";
|
|
62
|
+
const subPrefix = isLast ? " " : "│ ";
|
|
63
|
+
lines.push(`${prefix} ${folder}/`);
|
|
64
|
+
const linkFolder = linked ? folder : "";
|
|
65
|
+
const isDirSection = section === "skill";
|
|
66
|
+
lines.push(...printItems(subPrefix, groups[section] ?? [], linkFolder, linked && isDirSection));
|
|
67
|
+
}
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-description heuristics — pure text in, description out (no fs). Ports
|
|
3
|
+
* `describe.py`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const SOURCE_SUFFIXES: ReadonlySet<string> = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".swift"]);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* First `# ` Markdown heading text (sans hashes), sliced to 60. "" when none.
|
|
10
|
+
* @param text - The document text.
|
|
11
|
+
* @returns The heading text, or "".
|
|
12
|
+
*/
|
|
13
|
+
export function firstHeading(text: string): string {
|
|
14
|
+
for (const line of text.split("\n")) {
|
|
15
|
+
if (line.startsWith("# ")) return line.replace(/^#+/, "").trim().slice(0, 60);
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* First leading comment among the first 10 lines (`//`, `#` but not `#!`, or a
|
|
22
|
+
* `"""`/`'''` docstring), sliced to 60. "" when none.
|
|
23
|
+
* @param text - The source text.
|
|
24
|
+
* @returns The comment text, or "".
|
|
25
|
+
*/
|
|
26
|
+
export function firstComment(text: string): string {
|
|
27
|
+
const lines = text.split("\n").slice(0, 10);
|
|
28
|
+
for (const raw of lines) {
|
|
29
|
+
const line = raw.trim();
|
|
30
|
+
if ((line.startsWith("//") || line.startsWith("#")) && !line.startsWith("#!")) {
|
|
31
|
+
return line.replace(/^[/#! ]+/, "").slice(0, 60);
|
|
32
|
+
}
|
|
33
|
+
if (line.startsWith('"""') || line.startsWith("'''")) {
|
|
34
|
+
return line.replace(/^['"\s]+|['"\s]+$/g, "").slice(0, 60);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Derive a description from a file's suffix + text. For `.md`, the supplied
|
|
42
|
+
* frontmatter `description` (truncated) wins over the first heading; for known
|
|
43
|
+
* source suffixes, the first comment; else "".
|
|
44
|
+
* @param suffix - The file extension (with dot).
|
|
45
|
+
* @param text - The file text.
|
|
46
|
+
* @param mdField - The pre-parsed frontmatter `description` (md only).
|
|
47
|
+
* @returns The derived description, or "".
|
|
48
|
+
*/
|
|
49
|
+
export function descFromText(suffix: string, text: string, mdField: string): string {
|
|
50
|
+
if (suffix === ".md") return mdField.slice(0, 60) || firstHeading(text);
|
|
51
|
+
if (SOURCE_SUFFIXES.has(suffix)) return firstComment(text);
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-entry parsing — pure line regexes. Ports the line parsers of
|
|
3
|
+
* `merge_index.py` and `track-enrichment.py`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A parsed `prefix[name](path) — desc` tree line. */
|
|
7
|
+
export interface TreeEntry {
|
|
8
|
+
prefix: string;
|
|
9
|
+
name: string;
|
|
10
|
+
path: string;
|
|
11
|
+
desc: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ENTRY_RE = /^(.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.*)$/;
|
|
15
|
+
const ENRICH_RE = /^(?:.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.+)$/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse a `merge_index` tree line into its parts. Returns null on no match.
|
|
19
|
+
* @param line - The raw tree line.
|
|
20
|
+
* @returns The parsed entry, or null.
|
|
21
|
+
*/
|
|
22
|
+
export function parseEntry(line: string): TreeEntry | null {
|
|
23
|
+
const m = ENTRY_RE.exec(line);
|
|
24
|
+
if (!m || m[1] === undefined || m[2] === undefined || m[3] === undefined || m[4] === undefined) return null;
|
|
25
|
+
return { prefix: m[1], name: m[2], path: m[3], desc: m[4] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse an enrichment line into `[path, desc]`, requiring a non-empty desc.
|
|
30
|
+
* @param line - The raw index line.
|
|
31
|
+
* @returns The `[path, desc]` pair, or null.
|
|
32
|
+
*/
|
|
33
|
+
export function parseEnrichment(line: string): [string, string] | null {
|
|
34
|
+
const m = ENRICH_RE.exec(line);
|
|
35
|
+
if (!m || m[2] === undefined || m[3] === undefined) return null;
|
|
36
|
+
const desc = m[3].trim();
|
|
37
|
+
return desc ? [m[2], desc] : null;
|
|
38
|
+
}
|
|
@@ -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/");
|