@fusengine/harness 0.1.90 → 0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude/index.d.mts +1 -1
- package/dist/adapters/claude/index.mjs +1 -1
- package/dist/adapters/cline/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +2 -2
- package/dist/adapters/codex/index.mjs +3 -2
- package/dist/adapters/cursor/index.d.mts +15 -28
- package/dist/adapters/cursor/index.mjs +35 -46
- package/dist/adapters/gemini/index.mjs +1 -1
- package/dist/adapters/hermes/index.d.mts +1 -1
- package/dist/adapters/hermes/index.mjs +1 -1
- package/dist/adapters/kimi/index.d.mts +1 -1
- package/dist/adapters/kimi/index.mjs +1 -1
- package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
- package/dist/cli/bin.mjs +429 -16
- package/dist/cli/index.mjs +1 -1
- package/dist/command-string-CALMTnwN.mjs +42 -0
- package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
- package/dist/{handle-CBaOcFZq.mjs → handle-C43gA-Pr.mjs} +701 -134
- package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
- package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
- package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/init/index.d.mts +1 -1
- package/dist/init/index.mjs +1 -1
- package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
- package/dist/normalize-BjG6unTj.mjs +317 -0
- package/dist/policy/index.d.mts +1 -1
- package/dist/policy/index.mjs +2 -2
- package/dist/prompt/index.d.mts +1 -1
- package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
- package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
- package/dist/runtime/index.d.mts +27 -16
- package/dist/runtime/index.mjs +2 -2
- package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
- package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
- package/package.json +3 -2
- package/src/adapters/claude/index.ts +92 -0
- package/src/adapters/cline/index.ts +36 -0
- package/src/adapters/codex/apply-patch.ts +94 -0
- package/src/adapters/codex/index.ts +84 -0
- package/src/adapters/codex/permission-mode.ts +16 -0
- package/src/adapters/cursor/context.ts +46 -0
- package/src/adapters/cursor/events.ts +38 -0
- package/src/adapters/cursor/index.ts +43 -0
- package/src/adapters/cursor/interfaces/types.ts +88 -0
- package/src/adapters/cursor/native-response.ts +158 -0
- package/src/adapters/cursor/normalize.ts +123 -0
- package/src/adapters/cursor/respond.ts +123 -0
- package/src/adapters/gemini/index.ts +36 -0
- package/src/adapters/hermes/index.ts +55 -0
- package/src/adapters/hermes/interfaces/types.ts +26 -0
- package/src/adapters/kimi/index.ts +96 -0
- package/src/adapters/kimi/interfaces/types.ts +55 -0
- package/src/cache/compact.ts +55 -0
- package/src/cache/index.ts +5 -0
- package/src/cache/io.ts +38 -0
- package/src/cache/mcp-response.ts +39 -0
- package/src/cache/mcp-store.ts +70 -0
- package/src/cache/store.ts +93 -0
- package/src/changelog/fetch.ts +79 -0
- package/src/cli/bin.ts +100 -0
- package/src/cli/cursor-event-scanner.ts +175 -0
- package/src/cli/cursor-stdin-reader.ts +65 -0
- package/src/cli/doctor.ts +96 -0
- package/src/cli/hook-io.ts +130 -0
- package/src/cli/hook-sound.ts +49 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/json-primitive-scanner.ts +83 -0
- package/src/cli/run.ts +36 -0
- package/src/cli/scope.ts +31 -0
- package/src/codex-rules/build.ts +63 -0
- package/src/codex-rules/index.ts +8 -0
- package/src/codex-rules/mapping.ts +42 -0
- package/src/codex-rules/rules/absolute-paths.ts +52 -0
- package/src/codex-rules/rules/ask.ts +44 -0
- package/src/codex-rules/rules/chmod-ask.ts +57 -0
- package/src/codex-rules/rules/dd.ts +23 -0
- package/src/codex-rules/rules/diskutil.ts +33 -0
- package/src/codex-rules/rules/mkfs.ts +42 -0
- package/src/codex-rules/rules/privilege.ts +49 -0
- package/src/codex-rules/rules/remote-fetch.ts +31 -0
- package/src/codex-rules/rules/rm-variants.ts +58 -0
- package/src/codex-rules/rules/single-commands.ts +57 -0
- package/src/codex-rules/skip-list.ts +25 -0
- package/src/codex-rules/starlark.ts +46 -0
- package/src/codex-rules/types.ts +36 -0
- package/src/config/dotenv.ts +55 -0
- package/src/config/env.ts +10 -0
- package/src/config/home-dir.ts +36 -0
- package/src/config/index.ts +5 -0
- package/src/config/layout.ts +52 -0
- package/src/config/limits.ts +56 -0
- package/src/config/ttl.ts +30 -0
- package/src/detect/harness.ts +73 -0
- package/src/detect/index.ts +1 -0
- package/src/detect/interfaces/types.ts +18 -0
- package/src/freshness/agent-evidence-record.ts +95 -0
- package/src/freshness/agent-evidence.ts +95 -0
- package/src/freshness/codex-spawn-evidence.ts +91 -0
- package/src/freshness/doc-helpers.ts +90 -0
- package/src/freshness/evidence-harvest-io.ts +72 -0
- package/src/freshness/evidence-harvest.ts +61 -0
- package/src/freshness/explore-tools.ts +95 -0
- package/src/freshness/index.ts +2 -0
- package/src/freshness/query-framework.ts +73 -0
- package/src/freshness/ref-evidence.ts +49 -0
- package/src/freshness/ref-journal.ts +98 -0
- package/src/freshness/trivial-edit-counter.ts +30 -0
- package/src/index.ts +11 -0
- package/src/init/index.ts +2 -0
- package/src/init/run.ts +32 -0
- package/src/init/templates.ts +75 -0
- package/src/memory/gitignore.ts +14 -0
- package/src/memory/index.ts +3 -0
- package/src/memory/registry.ts +39 -0
- package/src/memory/session-roots.ts +99 -0
- package/src/memory/state.ts +58 -0
- package/src/policy/apex-authorization.ts +100 -0
- package/src/policy/apex-gates.ts +91 -0
- package/src/policy/apex-target-steps.ts +73 -0
- package/src/policy/apex-target.ts +49 -0
- package/src/policy/apex-task-context.ts +81 -0
- package/src/policy/apex.ts +94 -0
- package/src/policy/cartographer/build-tree.ts +69 -0
- package/src/policy/cartographer/describe.ts +53 -0
- package/src/policy/cartographer/entry.ts +38 -0
- package/src/policy/cartographer/frontmatter.ts +48 -0
- package/src/policy/cartographer/indicators.ts +34 -0
- package/src/policy/claude-md-context.ts +66 -0
- package/src/policy/conventions/index.ts +11 -0
- package/src/policy/conventions/interfaces.ts +71 -0
- package/src/policy/conventions/langs.ts +53 -0
- package/src/policy/conventions/query.ts +49 -0
- package/src/policy/conventions/react-hooks.ts +18 -0
- package/src/policy/conventions/store-or-query.ts +21 -0
- package/src/policy/conventions/stores.ts +56 -0
- package/src/policy/conventions/strip.ts +87 -0
- package/src/policy/conventions/verdict.ts +38 -0
- package/src/policy/creation-intent.ts +11 -0
- package/src/policy/deny-loop.ts +88 -0
- package/src/policy/design/allowed-write.ts +28 -0
- package/src/policy/design/content-checks.ts +45 -0
- package/src/policy/design/corpus-fs.ts +21 -0
- package/src/policy/design/corpus-resolve-cache.ts +49 -0
- package/src/policy/design/corpus-resolve.ts +99 -0
- package/src/policy/design/corpus.ts +92 -0
- package/src/policy/design/design-system-rules.ts +45 -0
- package/src/policy/design/flag.ts +30 -0
- package/src/policy/design/gates-pipeline.ts +105 -0
- package/src/policy/design/gates.ts +87 -0
- package/src/policy/design/screenshot-tools.ts +24 -0
- package/src/policy/design/skill-evidence.ts +34 -0
- package/src/policy/design/skill-gate.ts +81 -0
- package/src/policy/design/skill-triggers.ts +51 -0
- package/src/policy/design/state.ts +92 -0
- package/src/policy/design/template-urls.ts +54 -0
- package/src/policy/design/transitions.ts +68 -0
- package/src/policy/detect-claude-md-project-type.ts +32 -0
- package/src/policy/detect-framework.ts +99 -0
- package/src/policy/detect-primitive-checks.ts +100 -0
- package/src/policy/detect-primitive-lib.ts +55 -0
- package/src/policy/detect-project.ts +99 -0
- package/src/policy/edit-outcome.ts +53 -0
- package/src/policy/evaluate.ts +81 -0
- package/src/policy/expert-agents.ts +60 -0
- package/src/policy/file-size-scope.ts +32 -0
- package/src/policy/file-size.ts +99 -0
- package/src/policy/framework-solid-exclude.ts +52 -0
- package/src/policy/framework-solid-extended.ts +93 -0
- package/src/policy/framework-solid-gates-systems.ts +99 -0
- package/src/policy/framework-solid-gates.ts +98 -0
- package/src/policy/framework-solid.ts +88 -0
- package/src/policy/gemini-mcp-gate.ts +83 -0
- package/src/policy/git-gates.ts +40 -0
- package/src/policy/guards/bash-command-anchor.ts +40 -0
- package/src/policy/guards/bash-write-patterns.ts +140 -0
- package/src/policy/guards/bash-write-redirects.ts +201 -0
- package/src/policy/guards/bash-write-safe-paths.ts +62 -0
- package/src/policy/guards/bash-write.ts +76 -0
- package/src/policy/guards/context.ts +12 -0
- package/src/policy/guards/index.ts +61 -0
- package/src/policy/guards/install.ts +22 -0
- package/src/policy/guards/interface-separation-ext.ts +41 -0
- package/src/policy/guards/interface-separation.ts +100 -0
- package/src/policy/guards/protected-path.ts +100 -0
- package/src/policy/guards/security.ts +97 -0
- package/src/policy/index.ts +19 -0
- package/src/policy/interfaces/types.ts +46 -0
- package/src/policy/js-gate-route.ts +51 -0
- package/src/policy/lessons/lesson-gate.ts +0 -0
- package/src/policy/lessons/trigger-index.ts +84 -0
- package/src/policy/lessons/types.ts +31 -0
- package/src/policy/module-layout.ts +31 -0
- package/src/policy/nearest-manifest.ts +75 -0
- package/src/policy/never-approval.ts +95 -0
- package/src/policy/patterns.ts +80 -0
- package/src/policy/seo/validate.ts +51 -0
- package/src/policy/shadcn-project.ts +22 -0
- package/src/policy/shadcn-skill-gate.ts +97 -0
- package/src/policy/shell-read-refs.ts +96 -0
- package/src/policy/skill-path.ts +46 -0
- package/src/policy/skill-patterns/laravel.ts +40 -0
- package/src/policy/skill-patterns/nextjs.ts +29 -0
- package/src/policy/skill-patterns/react.ts +27 -0
- package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
- package/src/policy/skill-patterns/shadcn.ts +66 -0
- package/src/policy/skill-patterns/swift.ts +30 -0
- package/src/policy/skill-patterns/tailwind.ts +40 -0
- package/src/policy/skill-source.ts +52 -0
- package/src/policy/skill-trigger-patterns.ts +33 -0
- package/src/policy/skill-triggers.ts +108 -0
- package/src/policy/tailwind-skill-gate.ts +63 -0
- package/src/policy/trivial-edits.ts +52 -0
- package/src/policy/verbosity.ts +39 -0
- package/src/prompt/index.ts +1 -0
- package/src/prompt/types.ts +36 -0
- package/src/refs/discover.ts +69 -0
- package/src/refs/frontmatter.ts +24 -0
- package/src/refs/index.ts +4 -0
- package/src/refs/loader.ts +59 -0
- package/src/refs/ref-key.ts +41 -0
- package/src/refs/router.ts +63 -0
- package/src/refs/types.ts +25 -0
- package/src/runtime/activity.ts +99 -0
- package/src/runtime/apply-patch-gate.ts +41 -0
- package/src/runtime/burst-window.ts +21 -0
- package/src/runtime/codex-shell-tool.ts +37 -0
- package/src/runtime/command-string.ts +37 -0
- package/src/runtime/confirm/codex-confirm.ts +126 -0
- package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
- package/src/runtime/confirm/confirm-code.ts +33 -0
- package/src/runtime/confirm/confirm-gate.ts +65 -0
- package/src/runtime/confirm/confirm-irreversible.ts +32 -0
- package/src/runtime/confirm/confirm-pending.ts +34 -0
- package/src/runtime/confirm/confirm-state.ts +56 -0
- package/src/runtime/confirm/confirm-subagent.ts +112 -0
- package/src/runtime/confirm/confirm-submit.ts +38 -0
- package/src/runtime/core-guards-active.ts +84 -0
- package/src/runtime/core-guards-wired.ts +47 -0
- package/src/runtime/deny-loop-store.ts +94 -0
- package/src/runtime/deny-notice.ts +52 -0
- package/src/runtime/design-cache-resolve.ts +51 -0
- package/src/runtime/design-content-gate.ts +95 -0
- package/src/runtime/design-files-gate.ts +114 -0
- package/src/runtime/design-helpers.ts +93 -0
- package/src/runtime/design-lifecycle.ts +39 -0
- package/src/runtime/design-read-credit.ts +28 -0
- package/src/runtime/design.ts +116 -0
- package/src/runtime/dev-context.ts +43 -0
- package/src/runtime/dry-patterns.ts +32 -0
- package/src/runtime/dry.ts +96 -0
- package/src/runtime/fragment-registry.ts +33 -0
- package/src/runtime/framework-skill-gate.ts +79 -0
- package/src/runtime/fs-cleanup.ts +71 -0
- package/src/runtime/gate-apex.ts +81 -0
- package/src/runtime/gate-helpers.ts +52 -0
- package/src/runtime/gate-input.ts +26 -0
- package/src/runtime/gate.ts +128 -0
- package/src/runtime/handle-post.ts +104 -0
- package/src/runtime/handle-pre.ts +137 -0
- package/src/runtime/handle-scope-async.ts +29 -0
- package/src/runtime/handle-types.ts +27 -0
- package/src/runtime/handle.ts +116 -0
- package/src/runtime/home-state.ts +55 -0
- package/src/runtime/index.ts +18 -0
- package/src/runtime/inform.ts +28 -0
- package/src/runtime/inject-budget-recap.ts +47 -0
- package/src/runtime/inject-budget.ts +81 -0
- package/src/runtime/inject-context.ts +85 -0
- package/src/runtime/inject-dedup-exclusive.ts +98 -0
- package/src/runtime/inject-dedup.ts +89 -0
- package/src/runtime/is-agent-tool.ts +19 -0
- package/src/runtime/lifecycle/agent-files.ts +49 -0
- package/src/runtime/lifecycle/agent-memory.ts +82 -0
- package/src/runtime/lifecycle/agent-transcript.ts +80 -0
- package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
- package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
- package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
- package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
- package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
- package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
- package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
- package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
- package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
- package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
- package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
- package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
- package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
- package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
- package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
- package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
- package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
- package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
- package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
- package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
- package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
- package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
- package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
- package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
- package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
- package/src/runtime/lifecycle/aipilot/types.ts +72 -0
- package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
- package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
- package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
- package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
- package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
- package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
- package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
- package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
- package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
- package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
- package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
- package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
- package/src/runtime/lifecycle/changelog-research.ts +43 -0
- package/src/runtime/lifecycle/check-file-size.ts +61 -0
- package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
- package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
- package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
- package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
- package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
- package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
- package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
- package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
- package/src/runtime/lifecycle/dispatch.ts +109 -0
- package/src/runtime/lifecycle/failure-lesson.ts +51 -0
- package/src/runtime/lifecycle/index.ts +34 -0
- package/src/runtime/lifecycle/inject-rules.ts +50 -0
- package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
- package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
- package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
- package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
- package/src/runtime/lifecycle/lessons/state.ts +23 -0
- package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
- package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
- package/src/runtime/lifecycle/memory/client.ts +65 -0
- package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
- package/src/runtime/lifecycle/memory/recall.ts +30 -0
- package/src/runtime/lifecycle/memory/salience.ts +31 -0
- package/src/runtime/lifecycle/memory/state.ts +49 -0
- package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
- package/src/runtime/lifecycle/post-compact.ts +38 -0
- package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
- package/src/runtime/lifecycle/post-tracking.ts +40 -0
- package/src/runtime/lifecycle/pre-compact.ts +38 -0
- package/src/runtime/lifecycle/rules-root.ts +83 -0
- package/src/runtime/lifecycle/security/check-skill.ts +63 -0
- package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
- package/src/runtime/lifecycle/security/scan.ts +76 -0
- package/src/runtime/lifecycle/security/skill-state.ts +47 -0
- package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
- package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
- package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
- package/src/runtime/lifecycle/session-end.ts +17 -0
- package/src/runtime/lifecycle/session-start.ts +57 -0
- package/src/runtime/lifecycle/snapshot/board.ts +27 -0
- package/src/runtime/lifecycle/snapshot/format.ts +60 -0
- package/src/runtime/lifecycle/snapshot/git.ts +71 -0
- package/src/runtime/lifecycle/snapshot/index.ts +52 -0
- package/src/runtime/lifecycle/snapshot/version.ts +34 -0
- package/src/runtime/lifecycle/solid-detect.ts +56 -0
- package/src/runtime/lifecycle/stop-core.ts +35 -0
- package/src/runtime/lifecycle/subagent-cache.ts +61 -0
- package/src/runtime/lifecycle/task-completed.ts +94 -0
- package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
- package/src/runtime/lifecycle/teammate-idle.ts +28 -0
- package/src/runtime/lifecycle/tool-failure.ts +24 -0
- package/src/runtime/lifecycle/track-changes.ts +61 -0
- package/src/runtime/lifecycle/validate-solid.ts +70 -0
- package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
- package/src/runtime/lifecycle-bridge.ts +74 -0
- package/src/runtime/mcp-key.ts +44 -0
- package/src/runtime/mcp-tool-name.ts +92 -0
- package/src/runtime/mcp.ts +91 -0
- package/src/runtime/modular.ts +77 -0
- package/src/runtime/normalize.ts +107 -0
- package/src/runtime/notices.ts +91 -0
- package/src/runtime/notification-sound.ts +49 -0
- package/src/runtime/notifications.ts +77 -0
- package/src/runtime/paths.ts +59 -0
- package/src/runtime/post-fanout.ts +41 -0
- package/src/runtime/post-outcome.ts +61 -0
- package/src/runtime/pre-allow.ts +97 -0
- package/src/runtime/precommit.ts +60 -0
- package/src/runtime/prompt-text.ts +38 -0
- package/src/runtime/receipt-capture.ts +34 -0
- package/src/runtime/record.ts +26 -0
- package/src/runtime/respond.ts +92 -0
- package/src/runtime/solid-file-size-gate.ts +58 -0
- package/src/runtime/solid-pre.ts +53 -0
- package/src/runtime/storage.ts +9 -0
- package/src/statusline/colors.ts +31 -0
- package/src/statusline/constants.ts +17 -0
- package/src/statusline/formatters.ts +54 -0
- package/src/statusline/index.ts +4 -0
- package/src/statusline/progress-bar.ts +46 -0
- package/src/tracking/codex-post-failure.ts +73 -0
- package/src/tracking/index.ts +2 -0
- package/src/tracking/integrity.ts +99 -0
- package/src/tracking/one-shot-dedup.ts +34 -0
- package/src/tracking/one-shot-failure.ts +42 -0
- package/src/tracking/one-shot-store.ts +95 -0
- package/src/tracking/one-shot.ts +93 -0
- package/src/tracking/receipts.ts +95 -0
- package/src/tracking/session-state.ts +94 -0
- package/src/tracking/store.ts +96 -0
- package/src/tracking/track-compact.ts +100 -0
- package/src/tracking/track-diff.ts +50 -0
- package/src/tracking/track-journal.ts +97 -0
- package/src/tracking/track-lock-sync.ts +83 -0
- package/src/tracking/track-lock.ts +67 -0
- package/src/util/compact-json.ts +23 -0
- package/src/util/index.ts +2 -0
- package/src/util/json-io.ts +47 -0
- package/src/util/project-root.ts +41 -0
- package/src/util/runtime-io.ts +85 -0
- package/src/util/semver.ts +30 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse-lessons scope dispatch (TS port of the 4 handler scripts). Routes by
|
|
3
|
+
* event: SessionStart/SubagentStart inject `MEMORY/LESSON.md`; Stop reminds the
|
|
4
|
+
* stopping session about ITS OWN projects with unsaved code edits; PostToolUse
|
|
5
|
+
* marks the write to arm/silence the throttle. The reminder + mark logic (incl.
|
|
6
|
+
* the per-`session_id` scoping that fixes the multi-session misdirection) lives
|
|
7
|
+
* in {@link module:runtime/lifecycle/lessons/reminder}; this module keeps the
|
|
8
|
+
* event router + lesson-file injection. Non-fatal by design.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { renderInform } from "../../inform";
|
|
12
|
+
import { projectRoot } from "../../../util/project-root";
|
|
13
|
+
import { atomicWrite } from "../../../util/json-io";
|
|
14
|
+
import { curateLessons } from "../aipilot/curate-lessons";
|
|
15
|
+
import { compressInjection } from "../aipilot/lesson-inject";
|
|
16
|
+
import { capFragment } from "../../inject-budget";
|
|
17
|
+
import { lessonsArchiveFileFor, lessonsFileFor } from "./state";
|
|
18
|
+
import { markWrite, remindWrite } from "./reminder";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Persist a curation ATOMICALLY and ARCHIVE-FIRST for zero-loss: prepend the
|
|
22
|
+
* moved bullets to LESSON-archive.md, THEN rewrite LESSON.md. On ANY write error
|
|
23
|
+
* the original file is left untouched (returns `original`) so a bullet is never
|
|
24
|
+
* lost — a rare archive-then-trim-fail leaves a duplicate (never a loss), which
|
|
25
|
+
* the next dedup pass reconciles.
|
|
26
|
+
*/
|
|
27
|
+
function persistCuration(file: string, root: string, curated: string, archive: string, original: string): string {
|
|
28
|
+
try {
|
|
29
|
+
if (archive) {
|
|
30
|
+
const af = lessonsArchiveFileFor(root);
|
|
31
|
+
const prev = existsSync(af) ? readFileSync(af, "utf-8") : "";
|
|
32
|
+
atomicWrite(af, prev ? `${archive}\n${prev}` : archive);
|
|
33
|
+
}
|
|
34
|
+
atomicWrite(file, curated);
|
|
35
|
+
return curated;
|
|
36
|
+
} catch { return original; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Inject `MEMORY/LESSON.md` for `event`. Mechanical curation (dedup + cap→archive)
|
|
41
|
+
* rewrites the FILE; the injected BLOCK is then COMPRESSED (newest bullets whole,
|
|
42
|
+
* older ones distilled to their rule) so a growing file never inflates the
|
|
43
|
+
* SessionStart/SubagentStart context. A hard {@link capFragment} budget is the
|
|
44
|
+
* last-resort backstop on top of compression — the regression that motivated it
|
|
45
|
+
* was this exact block silently reaching ~44k tokens. Any curation report
|
|
46
|
+
* surfaces via systemMessage.
|
|
47
|
+
*/
|
|
48
|
+
function injectMemory(cwd: string, event: string, now: number, id: string): string {
|
|
49
|
+
const root = projectRoot(cwd);
|
|
50
|
+
const file = lessonsFileFor(root);
|
|
51
|
+
if (!existsSync(file)) return "";
|
|
52
|
+
let content = "";
|
|
53
|
+
try { content = readFileSync(file, "utf-8").trim(); } catch { return ""; }
|
|
54
|
+
if (!content) return "";
|
|
55
|
+
const { content: curated, archive, report } = curateLessons(content, now, root);
|
|
56
|
+
if (curated !== content) content = persistCuration(file, root, curated, archive, content);
|
|
57
|
+
const body = capFragment("lessons", compressInjection(content));
|
|
58
|
+
const ctx = `Project lessons — never reproduce these:\n${body}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`;
|
|
59
|
+
const notice = report ? `lessons injected\nLESSON.md curation:\n${report}` : "lessons injected";
|
|
60
|
+
return renderInform(id, event, ctx, notice);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Route a fuse-lessons event to its handler. Returns the native stdout for
|
|
65
|
+
* context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
|
|
66
|
+
* MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
|
|
67
|
+
* @param event - The raw hook event name.
|
|
68
|
+
* @param payload - The raw hook payload.
|
|
69
|
+
* @param cwd - Project root for memory injection.
|
|
70
|
+
* @param now - Clock.
|
|
71
|
+
* @param id - Harness target id (defaults to "claude-code" — zero-regression default).
|
|
72
|
+
* @returns The native stdout (possibly empty).
|
|
73
|
+
*/
|
|
74
|
+
export function dispatchLessons(event: string, payload: Record<string, unknown>, cwd: string, now: number, id: string = "claude-code"): string {
|
|
75
|
+
switch (event) {
|
|
76
|
+
case "SessionStart":
|
|
77
|
+
case "SubagentStart":
|
|
78
|
+
case "UserPromptSubmit":
|
|
79
|
+
return injectMemory(cwd, event, now, id);
|
|
80
|
+
case "Stop":
|
|
81
|
+
return remindWrite(payload, now);
|
|
82
|
+
case "PostToolUse":
|
|
83
|
+
markWrite(payload, now);
|
|
84
|
+
return "";
|
|
85
|
+
default:
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse-lessons write-mark + Stop-reminder, scoped by `session_id` when present.
|
|
3
|
+
*
|
|
4
|
+
* WITH a session id (normal Claude Code): each `(session, root)` pair carries
|
|
5
|
+
* its own edit/reminder throttle in {@link module:memory/session-roots}, so a
|
|
6
|
+
* Stop lists and silences ONLY the roots THAT session edited — concurrent
|
|
7
|
+
* sessions on different projects never cross-remind nor steal each other's
|
|
8
|
+
* throttle. WITHOUT a usable session id (a harness that omits it, or the legacy
|
|
9
|
+
* on-disk state) it falls back to the original mono-session behavior: the global
|
|
10
|
+
* flat root registry + the per-project `MEMORY/state.json` throttle.
|
|
11
|
+
*/
|
|
12
|
+
import { dirname, resolve } from "node:path";
|
|
13
|
+
import { contextResponse } from "../../../adapters/claude";
|
|
14
|
+
import { sanitizeSessionId } from "../../home-state";
|
|
15
|
+
import { addRoot, readRoots } from "../../../memory/registry";
|
|
16
|
+
import { collectSessionPending, markSessionRoot } from "../../../memory/session-roots";
|
|
17
|
+
import { nowStamp, readState, setStateField, throttleMs } from "../../../memory/state";
|
|
18
|
+
import { isCodeFile, projectRootOrNull } from "../../../util/project-root";
|
|
19
|
+
import { lessonsStateFileFor } from "./state";
|
|
20
|
+
|
|
21
|
+
/** Sanitized session id from a raw hook payload, or null (→ legacy fallback). */
|
|
22
|
+
function sessionOf(payload: Record<string, unknown>): string | null {
|
|
23
|
+
return sanitizeSessionId(payload.session_id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Legacy (no session id): pending roots across the global flat registry. */
|
|
27
|
+
function collectLegacyPending(now: number, window: number): string[] {
|
|
28
|
+
const pending: string[] = [];
|
|
29
|
+
for (const root of readRoots()) {
|
|
30
|
+
const stateFile = lessonsStateFileFor(root);
|
|
31
|
+
const { lastRemindedAt, lastCodeEditAt } = readState(stateFile);
|
|
32
|
+
if (lastCodeEditAt <= lastRemindedAt) continue;
|
|
33
|
+
if (now - lastRemindedAt < window) continue;
|
|
34
|
+
pending.push(root);
|
|
35
|
+
setStateField(stateFile, "lastRemindedAt", now);
|
|
36
|
+
}
|
|
37
|
+
return pending;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Stop reminder body listing each pending project's lessons file. */
|
|
41
|
+
function reminderText(pending: string[]): string {
|
|
42
|
+
const stamp = nowStamp();
|
|
43
|
+
const targets = pending.map((r) => `- ${r}/MEMORY/LESSON.md`).join("\n");
|
|
44
|
+
return `Before ending: if this session hit a mistake/blocker worth never ` +
|
|
45
|
+
`reproducing, append 1-3 COMPACT bullets OR sharpen/merge existing ones ` +
|
|
46
|
+
`(format \`- [${stamp}] what went wrong → do instead\`, use exactly this ` +
|
|
47
|
+
`timestamp) in each project's lessons file below. Skip if nothing ` +
|
|
48
|
+
`notable.\n${targets}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stop: emit one reminder covering the stopping session's pending projects.
|
|
53
|
+
* @param payload - Raw hook payload (`session_id` selects the scoped path).
|
|
54
|
+
* @param now - Clock.
|
|
55
|
+
* @returns Native Stop stdout, or "" when nothing is pending.
|
|
56
|
+
*/
|
|
57
|
+
export function remindWrite(payload: Record<string, unknown>, now: number): string {
|
|
58
|
+
const window = throttleMs();
|
|
59
|
+
const sid = sessionOf(payload);
|
|
60
|
+
const pending = sid ? collectSessionPending(sid, now, window) : collectLegacyPending(now, window);
|
|
61
|
+
if (pending.length === 0) return "";
|
|
62
|
+
return contextResponse("Stop", reminderText(pending));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* PostToolUse: record the edit against the throttle. A code file arms the
|
|
67
|
+
* reminder; writing `MEMORY/LESSON.md` silences it (the lesson was just saved).
|
|
68
|
+
* Session-scoped when `session_id` is present, else the legacy global path.
|
|
69
|
+
* @param payload - Raw hook payload (`tool_input.file_path`, `session_id`).
|
|
70
|
+
* @param now - Clock.
|
|
71
|
+
*/
|
|
72
|
+
export function markWrite(payload: Record<string, unknown>, now: number): void {
|
|
73
|
+
const input = payload.tool_input as { file_path?: string } | undefined;
|
|
74
|
+
if (!input?.file_path) return;
|
|
75
|
+
const abs = resolve(input.file_path);
|
|
76
|
+
const root = projectRootOrNull(dirname(abs));
|
|
77
|
+
if (!root) return;
|
|
78
|
+
const isLesson = abs === resolve(root, "MEMORY", "LESSON.md");
|
|
79
|
+
if (!isLesson && !isCodeFile(abs)) return;
|
|
80
|
+
const sid = sessionOf(payload);
|
|
81
|
+
if (sid) {
|
|
82
|
+
markSessionRoot(sid, root, isLesson ? "remindedAt" : "editedAt", now);
|
|
83
|
+
} else if (isLesson) {
|
|
84
|
+
setStateField(lessonsStateFileFor(root), "lastRemindedAt", now);
|
|
85
|
+
} else {
|
|
86
|
+
setStateField(lessonsStateFileFor(root), "lastCodeEditAt", now);
|
|
87
|
+
addRoot(root);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-project lessons paths. The `fuse-lessons` plugin stores its lessons under
|
|
3
|
+
* `<root>/MEMORY/` (NOT the harness `.harness/memory/`), so these two path
|
|
4
|
+
* helpers override the layout while ALL state/gitignore/throttle logic is
|
|
5
|
+
* reused from `src/memory` (`setStateField`, `ensureMemoryGitignore`,
|
|
6
|
+
* `readState`, `nowStamp`, `throttleMs`).
|
|
7
|
+
*/
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
/** Absolute `<root>/MEMORY/LESSON.md` — the curated, committable lessons file. */
|
|
11
|
+
export function lessonsFileFor(root: string): string {
|
|
12
|
+
return join(root, "MEMORY", "LESSON.md");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Absolute `<root>/MEMORY/LESSON-archive.md` — cold storage for capped-out bullets. */
|
|
16
|
+
export function lessonsArchiveFileFor(root: string): string {
|
|
17
|
+
return join(root, "MEMORY", "LESSON-archive.md");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
|
|
21
|
+
export function lessonsStateFileFor(root: string): string {
|
|
22
|
+
return join(root, "MEMORY", "state.json");
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubagentStop memory handler. Ports `capture-agent-lesson.py`: log a finished
|
|
3
|
+
* agent's conclusion and, when salient enough, store it as a Graphiti episode.
|
|
4
|
+
* Skips explore-codebase/websearch agents and errored exits.
|
|
5
|
+
*/
|
|
6
|
+
import { isoUtc } from "../security/skill-state";
|
|
7
|
+
import { postEpisode } from "./client";
|
|
8
|
+
import { SALIENCE_THRESHOLD, agentSeverity, salience } from "./salience";
|
|
9
|
+
import { appendMemoryLog } from "./state";
|
|
10
|
+
|
|
11
|
+
/** Agents whose conclusions are never captured. */
|
|
12
|
+
const SKIP = new Set(["explore-codebase", "websearch"]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handle SubagentStop: log + maybe store the agent's conclusion. Side-effect
|
|
16
|
+
* only (no stdout).
|
|
17
|
+
* @param payload - The raw hook payload.
|
|
18
|
+
* @param now - Clock.
|
|
19
|
+
*/
|
|
20
|
+
export async function captureAgentLesson(payload: Record<string, unknown>, now: number): Promise<void> {
|
|
21
|
+
const name = typeof payload.agent_name === "string" ? payload.agent_name : "unknown";
|
|
22
|
+
const lastMsg = typeof payload.last_assistant_message === "string" ? payload.last_assistant_message : "";
|
|
23
|
+
const exitReason = typeof payload.exit_reason === "string" ? payload.exit_reason : "unknown";
|
|
24
|
+
if (!lastMsg || exitReason === "error" || SKIP.has(name)) return;
|
|
25
|
+
const lesson = lastMsg.slice(0, 1000);
|
|
26
|
+
const ts = isoUtc(now);
|
|
27
|
+
appendMemoryLog("agent-lessons.log", `[${ts}] ${name} | ${exitReason} | ${lesson.slice(0, 80)}...`);
|
|
28
|
+
if (salience(agentSeverity(name)) <= SALIENCE_THRESHOLD) return;
|
|
29
|
+
await postEpisode({
|
|
30
|
+
name: "agent_lesson",
|
|
31
|
+
episode_body: `Agent ${name} conclusion: ${lesson}`,
|
|
32
|
+
source_description: `agent-stop-${name}`,
|
|
33
|
+
reference_time: ts,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostToolUse (Bash) memory handler. Ports `auto-capture-error.py`: on a
|
|
3
|
+
* non-zero Bash exit with stderr, store an episode in Graphiti and surface a
|
|
4
|
+
* `<memory-capture>` hint to search past errors / store the eventual solution.
|
|
5
|
+
*/
|
|
6
|
+
import { contextResponse } from "../../../adapters/claude";
|
|
7
|
+
import { isoUtc } from "../security/skill-state";
|
|
8
|
+
import { postEpisode } from "./client";
|
|
9
|
+
import { SALIENCE_THRESHOLD, bashSeverity, salience } from "./salience";
|
|
10
|
+
|
|
11
|
+
/** Extract exit code + stderr from a PostToolUse Bash payload (either field). */
|
|
12
|
+
function bashResult(payload: Record<string, unknown>): { exit: string; stderr: string } {
|
|
13
|
+
const r = (payload.tool_result ?? payload.tool_response) as { exit_code?: unknown; stderr?: unknown } | undefined;
|
|
14
|
+
return { exit: String(r?.exit_code ?? "0"), stderr: typeof r?.stderr === "string" ? r.stderr : "" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handle a Bash PostToolUse: capture a failed command's error in neural memory
|
|
19
|
+
* and return the native additionalContext stdout (or "" when nothing to emit).
|
|
20
|
+
* @param payload - The raw hook payload.
|
|
21
|
+
* @param now - Clock.
|
|
22
|
+
* @returns The native stdout (possibly empty).
|
|
23
|
+
*/
|
|
24
|
+
export async function captureBashError(payload: Record<string, unknown>, now: number): Promise<string> {
|
|
25
|
+
const { exit, stderr } = bashResult(payload);
|
|
26
|
+
if (exit === "0" || !stderr) return "";
|
|
27
|
+
const sev = bashSeverity(stderr);
|
|
28
|
+
if (salience(sev) <= SALIENCE_THRESHOLD) return "";
|
|
29
|
+
const errorMsg = stderr.slice(0, 500);
|
|
30
|
+
await postEpisode({
|
|
31
|
+
name: "bash_error",
|
|
32
|
+
episode_body: `Bash error (exit ${exit}): ${errorMsg}`,
|
|
33
|
+
source_description: "auto-capture",
|
|
34
|
+
reference_time: isoUtc(now),
|
|
35
|
+
});
|
|
36
|
+
const ctx =
|
|
37
|
+
`Error captured in neural memory (Graphiti).\n` +
|
|
38
|
+
`Search for similar past errors: use mcp__qdrant__qdrant-find with query "${errorMsg}"\n` +
|
|
39
|
+
`If you solve this, store the solution: use mcp__qdrant__qdrant-store`;
|
|
40
|
+
return contextResponse("PostToolUse", ctx);
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphiti neural-memory HTTP client (best-effort). Ports the urllib calls in
|
|
3
|
+
* the memory-neural scripts: POST /episodes (store) + POST /search (recall).
|
|
4
|
+
* Every call swallows network errors and honors a 5s timeout, so a hook never
|
|
5
|
+
* fails or hangs when the Graphiti server is absent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TIMEOUT_MS = 5000;
|
|
9
|
+
|
|
10
|
+
/** Base URL `http://<NEURAL_MEMORY_HOST>:<GRAPHITI_PORT>` (env-overridable). */
|
|
11
|
+
export function neuralBase(env: NodeJS.ProcessEnv = process.env): string {
|
|
12
|
+
const host = env.NEURAL_MEMORY_HOST ?? "localhost";
|
|
13
|
+
const port = env.GRAPHITI_PORT ?? "8000";
|
|
14
|
+
return `http://${host}:${port}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A Graphiti episode payload. */
|
|
18
|
+
export interface Episode {
|
|
19
|
+
name: string;
|
|
20
|
+
episode_body: string;
|
|
21
|
+
source_description: string;
|
|
22
|
+
reference_time: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** POST an episode to Graphiti `/episodes`. Resolves silently on any failure. */
|
|
26
|
+
export async function postEpisode(ep: Episode, env: NodeJS.ProcessEnv = process.env): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
await fetch(`${neuralBase(env)}/episodes`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify(ep),
|
|
32
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
33
|
+
});
|
|
34
|
+
} catch { /* best effort */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A single recall hit (content or name). */
|
|
38
|
+
export interface RecallHit {
|
|
39
|
+
content?: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* POST a query to Graphiti `/search`; returns hits or `[]` on any failure
|
|
45
|
+
* (network error, timeout, non-2xx, malformed JSON).
|
|
46
|
+
* @param query - The search query.
|
|
47
|
+
* @param numResults - Max results requested.
|
|
48
|
+
* @param env - Env (for host/port overrides).
|
|
49
|
+
* @returns The recall hits, possibly empty.
|
|
50
|
+
*/
|
|
51
|
+
export async function searchMemory(query: string, numResults: number, env: NodeJS.ProcessEnv = process.env): Promise<RecallHit[]> {
|
|
52
|
+
try {
|
|
53
|
+
const resp = await fetch(`${neuralBase(env)}/search`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({ query, num_results: numResults }),
|
|
57
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
58
|
+
});
|
|
59
|
+
if (!resp.ok) return [];
|
|
60
|
+
const data = (await resp.json()) as { results?: RecallHit[] };
|
|
61
|
+
return Array.isArray(data.results) ? data.results : [];
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse-memory-neural scope dispatcher (async; the handlers hit Graphiti over
|
|
3
|
+
* HTTP, best-effort). Routes by event: SessionStart recalls past lessons,
|
|
4
|
+
* PostToolUse captures Bash errors / tracks graphiti+qdrant ops, SubagentStop
|
|
5
|
+
* captures agent conclusions. Returns the native stdout when handled, or `null`
|
|
6
|
+
* to fall through to the generic pipeline.
|
|
7
|
+
*/
|
|
8
|
+
import { captureAgentLesson } from "./agent-lesson";
|
|
9
|
+
import { captureBashError } from "./capture-error";
|
|
10
|
+
import { trackMemoryOp } from "./track-ops";
|
|
11
|
+
import { recallOnSession } from "./recall";
|
|
12
|
+
|
|
13
|
+
/** Is this a graphiti/qdrant MCP tool call? */
|
|
14
|
+
function isMemoryTool(tool: string): boolean {
|
|
15
|
+
return tool.startsWith("mcp__graphiti") || tool.startsWith("mcp__qdrant");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dispatch a memory-scope lifecycle event to its ported handler.
|
|
20
|
+
* @param event - Raw hook event name.
|
|
21
|
+
* @param payload - Raw hook payload.
|
|
22
|
+
* @param cwd - Project root.
|
|
23
|
+
* @param now - Clock.
|
|
24
|
+
* @returns The native stdout, or `null` when unhandled.
|
|
25
|
+
*/
|
|
26
|
+
export async function dispatchMemory(event: string, payload: Record<string, unknown>, cwd: string, now: number): Promise<string | null> {
|
|
27
|
+
if (event === "SessionStart") return recallOnSession(cwd, now);
|
|
28
|
+
if (event === "SubagentStop" || event === "Stop") {
|
|
29
|
+
await captureAgentLesson(payload, now);
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
if (event === "PostToolUse") {
|
|
33
|
+
const tool = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
34
|
+
if (tool === "Bash") return captureBashError(payload, now);
|
|
35
|
+
if (isMemoryTool(tool)) {
|
|
36
|
+
trackMemoryOp(payload, now);
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStart memory handler. Ports `recall-on-session.py`: detect the project
|
|
3
|
+
* type, recall relevant lessons from Graphiti, log the recall, and inject a
|
|
4
|
+
* neural-memory-recall additionalContext block.
|
|
5
|
+
*/
|
|
6
|
+
import { basename } from "node:path";
|
|
7
|
+
import { contextResponse } from "../../../adapters/claude";
|
|
8
|
+
import { isoUtc } from "../security/skill-state";
|
|
9
|
+
import { searchMemory } from "./client";
|
|
10
|
+
import { appendMemoryLog, detectProjectType } from "./state";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handle SessionStart: recall past lessons for this project and return the
|
|
14
|
+
* native additionalContext stdout (or "" when there is nothing to recall).
|
|
15
|
+
* @param cwd - Project root.
|
|
16
|
+
* @param now - Clock.
|
|
17
|
+
* @returns The native stdout (possibly empty).
|
|
18
|
+
*/
|
|
19
|
+
export async function recallOnSession(cwd: string, now: number): Promise<string> {
|
|
20
|
+
const projectType = detectProjectType(cwd);
|
|
21
|
+
const projectName = basename(cwd);
|
|
22
|
+
const hits = await searchMemory(`${projectType} ${projectName} common errors`, 5);
|
|
23
|
+
appendMemoryLog("recalls.log", `[${isoUtc(now)}] session_recall | ${projectType} | ${projectName}`);
|
|
24
|
+
if (hits.length === 0) return "";
|
|
25
|
+
const lessons = hits.slice(0, 5).map((r) => `- ${r.content || r.name || "unknown"}`).join("\n");
|
|
26
|
+
const ctx =
|
|
27
|
+
`Relevant lessons from past sessions:\n${lessons}\n` +
|
|
28
|
+
`For deeper search: use mcp__qdrant__qdrant-find with project-specific queries.`;
|
|
29
|
+
return contextResponse("SessionStart", ctx);
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Salience + severity scoring for the memory-neural scope. Ports the
|
|
3
|
+
* `_severity` / `_agent_severity` helpers and the shared salience formula from
|
|
4
|
+
* `auto-capture-error.py` and `capture-agent-lesson.py`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Episodes at or below this salience are not stored. */
|
|
8
|
+
export const SALIENCE_THRESHOLD = 0.3;
|
|
9
|
+
|
|
10
|
+
/** Severity (1-10) of a Bash stderr by keyword (mirrors auto-capture-error). */
|
|
11
|
+
export function bashSeverity(stderr: string): number {
|
|
12
|
+
const s = stderr.toLowerCase();
|
|
13
|
+
if (s.includes("fatal") || s.includes("panic")) return 10;
|
|
14
|
+
if (s.includes("error") || s.includes("failed")) return 8;
|
|
15
|
+
if (s.includes("warning")) return 4;
|
|
16
|
+
if (s.includes("deprecated")) return 2;
|
|
17
|
+
return 5;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Severity (1-10) of a finished agent by name (mirrors capture-agent-lesson). */
|
|
21
|
+
export function agentSeverity(name: string): number {
|
|
22
|
+
if (name === "sniper" || name === "sniper-faster") return 8;
|
|
23
|
+
if (name === "research-expert") return 6;
|
|
24
|
+
if (name.endsWith("-expert")) return 7;
|
|
25
|
+
return 5;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Salience from severity: 0.40·sev/10 + 0.30 + 0.20·0.5 + 0.10·0.5. */
|
|
29
|
+
export function salience(severity: number): number {
|
|
30
|
+
return (0.4 * severity) / 10 + 0.3 + 0.2 * 0.5 + 0.1 * 0.5;
|
|
31
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuse-memory-neural scope state: per-line logs under
|
|
3
|
+
* `~/.claude/logs/00-memory` + project-type detection. Ports the shared
|
|
4
|
+
* filesystem helpers of the four memory-neural scripts.
|
|
5
|
+
*/
|
|
6
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { claudeHome } from "../../home-state";
|
|
10
|
+
import { atomicWrite } from "../../../util/json-io";
|
|
11
|
+
|
|
12
|
+
/** `~/.claude/logs/00-memory` log directory. */
|
|
13
|
+
export function memoryLogDir(home: string = homedir()): string {
|
|
14
|
+
return join(claudeHome(home), "logs", "00-memory");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Append a line to a memory log file, creating the dir. When `rotateAt > 0` and
|
|
19
|
+
* the file exceeds it, keep only the newest `keep` lines. Best-effort (errors
|
|
20
|
+
* swallowed), so a hook never fails on a logging issue.
|
|
21
|
+
* @param name - Log file name (e.g. `operations.log`).
|
|
22
|
+
* @param line - Line to append (newline added).
|
|
23
|
+
* @param rotateAt - Rotate when line count exceeds this (0 disables).
|
|
24
|
+
* @param keep - Lines to keep on rotation.
|
|
25
|
+
* @param home - Home dir.
|
|
26
|
+
*/
|
|
27
|
+
export function appendMemoryLog(name: string, line: string, rotateAt = 0, keep = 0, home: string = homedir()): void {
|
|
28
|
+
const dir = memoryLogDir(home);
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
const file = join(dir, name);
|
|
32
|
+
appendFileSync(file, `${line}\n`, "utf-8");
|
|
33
|
+
if (rotateAt > 0) {
|
|
34
|
+
const lines = readFileSync(file, "utf-8").split("\n").filter((l) => l.length > 0);
|
|
35
|
+
if (lines.length > rotateAt) atomicWrite(file, `${lines.slice(-keep).join("\n")}\n`);
|
|
36
|
+
}
|
|
37
|
+
} catch { /* best effort */ }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Detect the project type from cwd markers (mirrors recall-on-session.py). */
|
|
41
|
+
export function detectProjectType(cwd: string): string {
|
|
42
|
+
const markers: Array<[string, string]> = [
|
|
43
|
+
["package.json", "node"], ["composer.json", "php"],
|
|
44
|
+
["Package.swift", "swift"], ["Cargo.toml", "rust"], ["go.mod", "go"],
|
|
45
|
+
];
|
|
46
|
+
for (const [f, t] of markers) if (existsSync(join(cwd, f))) return t;
|
|
47
|
+
if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml"))) return "python";
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostToolUse (mcp__graphiti|mcp__qdrant) memory handler. Ports
|
|
3
|
+
* `track-memory-ops.py`: append `[ts] <tool> | ok|error` to
|
|
4
|
+
* `operations.log`, rotating at 1000 lines (keeping the newest 500).
|
|
5
|
+
*/
|
|
6
|
+
import { isoUtc } from "../security/skill-state";
|
|
7
|
+
import { appendMemoryLog } from "./state";
|
|
8
|
+
|
|
9
|
+
/** Append a memory-operation log line for a graphiti/qdrant tool call. */
|
|
10
|
+
export function trackMemoryOp(payload: Record<string, unknown>, now: number): void {
|
|
11
|
+
const tool = typeof payload.tool_name === "string" ? payload.tool_name : "unknown";
|
|
12
|
+
const result = (payload.tool_result ?? payload.tool_response) as { error?: unknown } | undefined;
|
|
13
|
+
const status = result?.error ? "error" : "ok";
|
|
14
|
+
appendMemoryLog("operations.log", `[${isoUtc(now)}] ${tool} | ${status}`, 1000, 500);
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module post-compact
|
|
3
|
+
* PostCompact handler (core scope): re-inject the reconciliation snapshot plus a
|
|
4
|
+
* one-line reminder that read-state may have been reset by compaction, so the
|
|
5
|
+
* agent re-reads files before editing. Deduped per session/window (compaction can
|
|
6
|
+
* fan out too). Fully fail-open — any error yields "".
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import { contextResponse } from "../../adapters/claude";
|
|
10
|
+
import { oncePerWindow } from "../inject-dedup";
|
|
11
|
+
import { defaultStateDir } from "../paths";
|
|
12
|
+
import { renderSnapshot } from "./snapshot";
|
|
13
|
+
|
|
14
|
+
/** Re-inject at most once per 30s per session (compaction fan-out + retries). */
|
|
15
|
+
const COMPACT_DEDUP_MS = 30_000;
|
|
16
|
+
|
|
17
|
+
/** One-line reminder prepended to the re-injected snapshot. */
|
|
18
|
+
const REMINDER = "Context was compacted — reread files before editing (read-state may be reset).";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Handle PostCompact: emit the reminder + reconciliation snapshot as
|
|
22
|
+
* `additionalContext`, deduped per session. "" on dedup-suppress or any error.
|
|
23
|
+
* @param data - The raw PostCompact payload (`session_id`, `trigger`).
|
|
24
|
+
* @param cwd - Project root.
|
|
25
|
+
* @param moduleUrl - `import.meta.url` of the caller (locates the running package for the version line).
|
|
26
|
+
* @param now - Clock (defaults to `Date.now()`).
|
|
27
|
+
* @returns The native hook stdout, or "".
|
|
28
|
+
*/
|
|
29
|
+
export function postCompactContext(data: Record<string, unknown>, cwd: string, moduleUrl: string, now: number = Date.now()): string {
|
|
30
|
+
try {
|
|
31
|
+
const sessionId = typeof data.session_id === "string" ? data.session_id : "unknown";
|
|
32
|
+
if (!oncePerWindow(`postcompact:${sessionId}`, COMPACT_DEDUP_MS, { now, dir: defaultStateDir(cwd) })) return "";
|
|
33
|
+
const snapshot = renderSnapshot(cwd, moduleUrl);
|
|
34
|
+
return contextResponse("PostCompact", snapshot ? `${REMINDER}\n\n${snapshot}` : REMINDER);
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { contextResponse } from "../../adapters/claude";
|
|
5
|
+
|
|
6
|
+
const TS_EXT = /\.(ts|tsx)$/;
|
|
7
|
+
const TIMEOUT_MS = 10000;
|
|
8
|
+
|
|
9
|
+
/** True when `bin` is resolvable on PATH (mirrors shutil.which). */
|
|
10
|
+
function hasBin(bin: string): boolean {
|
|
11
|
+
try {
|
|
12
|
+
execFileSync(process.platform === "win32" ? "where" : "which", [bin], { stdio: "pipe", timeout: 2000 });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Run `bin args`, returning `{ code, out }` (code 1 on spawn error). */
|
|
20
|
+
function run(bin: string, args: string[]): { code: number; out: string } {
|
|
21
|
+
try {
|
|
22
|
+
const out = execFileSync(bin, args, { encoding: "utf-8", stdio: "pipe", timeout: TIMEOUT_MS });
|
|
23
|
+
return { code: 0, out };
|
|
24
|
+
} catch (err) {
|
|
25
|
+
const e = err as { status?: number; stdout?: string | Buffer };
|
|
26
|
+
return { code: e.status ?? 1, out: typeof e.stdout === "string" ? e.stdout : e.stdout?.toString() ?? "" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handle PostToolUse for TS/TSX: report eslint/prettier issues (never fixes) as
|
|
32
|
+
* additionalContext. Ports `post-tool-use/post-edit-typescript.py`.
|
|
33
|
+
* @param filePath - The edited file path.
|
|
34
|
+
* @returns The native hook stdout (possibly empty).
|
|
35
|
+
*/
|
|
36
|
+
export function postEditTypescript(filePath: string): string {
|
|
37
|
+
if (!filePath || !TS_EXT.test(filePath) || !existsSync(filePath)) return "";
|
|
38
|
+
const issues: string[] = [];
|
|
39
|
+
if (hasBin("eslint")) {
|
|
40
|
+
const r = run("eslint", ["--no-fix", "--format", "compact", filePath]);
|
|
41
|
+
if (r.code !== 0 && r.out.trim()) issues.push(`ESLint:\n${r.out.trim()}`);
|
|
42
|
+
}
|
|
43
|
+
if (hasBin("prettier")) {
|
|
44
|
+
const r = run("prettier", ["--check", filePath]);
|
|
45
|
+
if (r.code !== 0) issues.push(`Prettier: ${basename(filePath)} needs formatting`);
|
|
46
|
+
}
|
|
47
|
+
if (issues.length === 0) return "";
|
|
48
|
+
return contextResponse("PostToolUse", `Lint issues in ${basename(filePath)}: ${issues.join(" | ")}`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostToolUse side-effect trackers for non-core scopes (carto/security/
|
|
3
|
+
* changelog). Pure side-effects — never produces stdout.
|
|
4
|
+
*/
|
|
5
|
+
import type { NormalizedEvent } from "../normalize";
|
|
6
|
+
import type { PluginScope } from "./dispatch";
|
|
7
|
+
import { trackEnrichment } from "./cartographer/track-enrichment";
|
|
8
|
+
import { trackSkillRead } from "./security/track-skill-read";
|
|
9
|
+
import { trackMcpResearch } from "./security/track-mcp";
|
|
10
|
+
import { trackWatchResearch } from "./changelog-research";
|
|
11
|
+
import { dispatchLessons } from "./lessons/dispatch";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Dispatch the appropriate PostToolUse tracker for the invoking scope. Carto
|
|
15
|
+
* persists manual enrichments; security records skill reads + MCP research;
|
|
16
|
+
* changelog records watch research; lessons arms the per-project throttle.
|
|
17
|
+
* Side-effect only.
|
|
18
|
+
* @param scope - The invoking plugin scope.
|
|
19
|
+
* @param event - The normalized event.
|
|
20
|
+
* @param input - The raw tool input.
|
|
21
|
+
* @param now - Clock.
|
|
22
|
+
* @param payload - The raw hook payload (for the lessons mark).
|
|
23
|
+
* @param cwd - The project root (for the lessons mark).
|
|
24
|
+
*/
|
|
25
|
+
export function postTrackingSideEffects(scope: PluginScope, event: NormalizedEvent, input: Record<string, unknown>, now: number, payload: Record<string, unknown> = {}, cwd: string = process.cwd()): void {
|
|
26
|
+
if (scope === "carto" && (event.tool === "Edit" || event.tool === "Write") && event.filePath) {
|
|
27
|
+
trackEnrichment(event.filePath);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (scope === "security") {
|
|
31
|
+
trackSkillRead(event.tool, event.filePath ?? "", now);
|
|
32
|
+
trackMcpResearch(event.tool, input, now);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (scope === "changelog") {
|
|
36
|
+
trackWatchResearch(event.tool, input, now);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (scope === "lessons") dispatchLessons("PostToolUse", payload, cwd, now);
|
|
40
|
+
}
|