byterover-cli 2.6.0 → 3.0.1
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/.env.production +1 -0
- package/README.md +240 -14
- package/dist/agent/core/domain/knowledge/conflict-detector.d.ts +38 -0
- package/dist/agent/core/domain/knowledge/conflict-detector.js +71 -0
- package/dist/agent/core/domain/knowledge/conflict-resolver.d.ts +17 -0
- package/dist/agent/core/domain/knowledge/conflict-resolver.js +118 -0
- package/dist/agent/core/domain/knowledge/utils.d.ts +4 -0
- package/dist/agent/core/domain/knowledge/utils.js +6 -0
- package/dist/agent/core/interfaces/i-curate-service.d.ts +6 -0
- package/dist/agent/infra/tools/implementations/curate-tool.d.ts +67 -34
- package/dist/agent/infra/tools/implementations/curate-tool.js +294 -47
- package/dist/agent/resources/prompts/system-prompt.yml +15 -8
- package/dist/agent/resources/tools/code_exec.txt +3 -0
- package/dist/agent/resources/tools/curate.txt +12 -3
- package/dist/oclif/commands/connectors/install.d.ts +2 -1
- package/dist/oclif/commands/connectors/install.js +38 -3
- package/dist/oclif/commands/curate/index.d.ts +18 -0
- package/dist/oclif/commands/curate/index.js +78 -1
- package/dist/oclif/commands/init.d.ts +12 -0
- package/dist/oclif/commands/init.js +75 -0
- package/dist/oclif/commands/locations.js +1 -1
- package/dist/oclif/commands/providers/connect.d.ts +31 -1
- package/dist/oclif/commands/providers/connect.js +307 -27
- package/dist/oclif/commands/pull.d.ts +1 -0
- package/dist/oclif/commands/pull.js +7 -0
- package/dist/oclif/commands/push.d.ts +1 -0
- package/dist/oclif/commands/push.js +8 -0
- package/dist/oclif/commands/review/approve.d.ts +17 -0
- package/dist/oclif/commands/review/approve.js +37 -0
- package/dist/oclif/commands/review/base-review-decision.d.ts +18 -0
- package/dist/oclif/commands/review/base-review-decision.js +71 -0
- package/dist/oclif/commands/review/pending.d.ts +13 -0
- package/dist/oclif/commands/review/pending.js +94 -0
- package/dist/oclif/commands/review/reject.d.ts +17 -0
- package/dist/oclif/commands/review/reject.js +38 -0
- package/dist/oclif/commands/space/list.d.ts +2 -2
- package/dist/oclif/commands/space/list.js +13 -35
- package/dist/oclif/commands/space/switch.d.ts +2 -7
- package/dist/oclif/commands/space/switch.js +13 -56
- package/dist/oclif/commands/status.d.ts +1 -0
- package/dist/oclif/commands/status.js +11 -1
- package/dist/oclif/commands/vc/add.d.ts +7 -0
- package/dist/oclif/commands/vc/add.js +29 -0
- package/dist/oclif/commands/vc/branch.d.ts +15 -0
- package/dist/oclif/commands/vc/branch.js +70 -0
- package/dist/oclif/commands/vc/checkout.d.ts +14 -0
- package/dist/oclif/commands/vc/checkout.js +47 -0
- package/dist/oclif/commands/vc/clone.d.ts +9 -0
- package/dist/oclif/commands/vc/clone.js +61 -0
- package/dist/oclif/commands/vc/commit.d.ts +10 -0
- package/dist/oclif/commands/vc/commit.js +32 -0
- package/dist/oclif/commands/vc/config.d.ts +10 -0
- package/dist/oclif/commands/vc/config.js +30 -0
- package/dist/oclif/commands/vc/fetch.d.ts +10 -0
- package/dist/oclif/commands/vc/fetch.js +42 -0
- package/dist/oclif/commands/vc/index.d.ts +6 -0
- package/dist/oclif/commands/vc/index.js +8 -0
- package/dist/oclif/commands/vc/init.d.ts +6 -0
- package/dist/oclif/commands/vc/init.js +25 -0
- package/dist/oclif/commands/vc/log.d.ts +13 -0
- package/dist/oclif/commands/vc/log.js +48 -0
- package/dist/oclif/commands/vc/merge.d.ts +19 -0
- package/dist/oclif/commands/vc/merge.js +130 -0
- package/dist/oclif/commands/vc/pull.d.ts +13 -0
- package/dist/oclif/commands/vc/pull.js +60 -0
- package/dist/oclif/commands/vc/push.d.ts +13 -0
- package/dist/oclif/commands/vc/push.js +60 -0
- package/dist/oclif/commands/vc/remote/add.d.ts +10 -0
- package/dist/oclif/commands/vc/remote/add.js +30 -0
- package/dist/oclif/commands/vc/remote/index.d.ts +6 -0
- package/dist/oclif/commands/vc/remote/index.js +16 -0
- package/dist/oclif/commands/vc/remote/set-url.d.ts +10 -0
- package/dist/oclif/commands/vc/remote/set-url.js +30 -0
- package/dist/oclif/commands/vc/reset.d.ts +13 -0
- package/dist/oclif/commands/vc/reset.js +62 -0
- package/dist/oclif/commands/vc/status.d.ts +8 -0
- package/dist/oclif/commands/vc/status.js +106 -0
- package/dist/oclif/hooks/init/validate-brv-config.d.ts +26 -0
- package/dist/oclif/hooks/init/validate-brv-config.js +62 -0
- package/dist/oclif/lib/daemon-client.d.ts +2 -0
- package/dist/oclif/lib/daemon-client.js +36 -10
- package/dist/oclif/lib/prompt-utils.d.ts +43 -0
- package/dist/oclif/lib/prompt-utils.js +84 -0
- package/dist/oclif/lib/spinner.d.ts +8 -0
- package/dist/oclif/lib/spinner.js +23 -0
- package/dist/oclif/lib/task-client.d.ts +5 -0
- package/dist/oclif/lib/task-client.js +15 -2
- package/dist/server/config/environment.d.ts +2 -0
- package/dist/server/config/environment.js +2 -0
- package/dist/server/constants.d.ts +3 -0
- package/dist/server/constants.js +9 -0
- package/dist/server/core/domain/entities/auth-token.d.ts +2 -0
- package/dist/server/core/domain/entities/auth-token.js +7 -1
- package/dist/server/core/domain/entities/curate-log-entry.d.ts +11 -0
- package/dist/server/core/domain/entities/space.d.ts +4 -0
- package/dist/server/core/domain/entities/space.js +8 -0
- package/dist/server/core/domain/entities/team.d.ts +2 -0
- package/dist/server/core/domain/entities/team.js +4 -0
- package/dist/server/core/domain/errors/git-error.d.ts +6 -0
- package/dist/server/core/domain/errors/git-error.js +12 -0
- package/dist/server/core/domain/errors/task-error.d.ts +4 -0
- package/dist/server/core/domain/errors/task-error.js +8 -0
- package/dist/server/core/domain/errors/vc-error.d.ts +5 -0
- package/dist/server/core/domain/errors/vc-error.js +8 -0
- package/dist/server/core/domain/knowledge/markdown-writer.d.ts +4 -1
- package/dist/server/core/domain/knowledge/markdown-writer.js +37 -7
- package/dist/server/core/domain/transport/schemas.d.ts +6 -6
- package/dist/server/core/interfaces/context-tree/i-context-tree-service.d.ts +11 -0
- package/dist/server/core/interfaces/process/i-task-lifecycle-hook.d.ts +6 -0
- package/dist/server/core/interfaces/services/i-git-service.d.ts +234 -0
- package/dist/server/core/interfaces/services/i-git-service.js +1 -0
- package/dist/server/core/interfaces/storage/i-curate-log-store.d.ts +5 -0
- package/dist/server/core/interfaces/storage/i-review-backup-store.d.ts +19 -0
- package/dist/server/core/interfaces/storage/i-review-backup-store.js +1 -0
- package/dist/server/core/interfaces/vc/i-vc-git-config-store.d.ts +8 -0
- package/dist/server/core/interfaces/vc/i-vc-git-config-store.js +1 -0
- package/dist/server/infra/config/auto-init.d.ts +0 -2
- package/dist/server/infra/config/auto-init.js +0 -1
- package/dist/server/infra/context-tree/file-context-tree-service.d.ts +2 -0
- package/dist/server/infra/context-tree/file-context-tree-service.js +13 -0
- package/dist/server/infra/daemon/brv-server.js +23 -3
- package/dist/server/infra/git/cogit-url.d.ts +17 -0
- package/dist/server/infra/git/cogit-url.js +39 -0
- package/dist/server/infra/git/git-http-wrapper.d.ts +20 -0
- package/dist/server/infra/git/git-http-wrapper.js +334 -0
- package/dist/server/infra/git/isomorphic-git-service.d.ts +78 -0
- package/dist/server/infra/git/isomorphic-git-service.js +983 -0
- package/dist/server/infra/http/review-api-handler.d.ts +13 -0
- package/dist/server/infra/http/review-api-handler.js +286 -0
- package/dist/server/infra/http/review-ui.d.ts +7 -0
- package/dist/server/infra/http/review-ui.js +606 -0
- package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +2 -2
- package/dist/server/infra/process/curate-log-handler.d.ts +18 -2
- package/dist/server/infra/process/curate-log-handler.js +50 -13
- package/dist/server/infra/process/feature-handlers.js +41 -1
- package/dist/server/infra/process/task-router.js +16 -0
- package/dist/server/infra/space/http-space-service.js +2 -0
- package/dist/server/infra/storage/file-curate-log-store.d.ts +10 -0
- package/dist/server/infra/storage/file-curate-log-store.js +35 -0
- package/dist/server/infra/storage/file-review-backup-store.d.ts +29 -0
- package/dist/server/infra/storage/file-review-backup-store.js +121 -0
- package/dist/server/infra/transport/handlers/auth-handler.js +9 -5
- package/dist/server/infra/transport/handlers/handler-types.d.ts +9 -0
- package/dist/server/infra/transport/handlers/handler-types.js +11 -0
- package/dist/server/infra/transport/handlers/index.d.ts +4 -0
- package/dist/server/infra/transport/handlers/index.js +2 -0
- package/dist/server/infra/transport/handlers/init-handler.d.ts +1 -0
- package/dist/server/infra/transport/handlers/init-handler.js +13 -1
- package/dist/server/infra/transport/handlers/pull-handler.d.ts +3 -0
- package/dist/server/infra/transport/handlers/pull-handler.js +5 -1
- package/dist/server/infra/transport/handlers/push-handler.d.ts +20 -0
- package/dist/server/infra/transport/handlers/push-handler.js +116 -14
- package/dist/server/infra/transport/handlers/reset-handler.d.ts +11 -0
- package/dist/server/infra/transport/handlers/reset-handler.js +37 -1
- package/dist/server/infra/transport/handlers/review-handler.d.ts +35 -0
- package/dist/server/infra/transport/handlers/review-handler.js +162 -0
- package/dist/server/infra/transport/handlers/space-handler.d.ts +3 -0
- package/dist/server/infra/transport/handlers/space-handler.js +4 -1
- package/dist/server/infra/transport/handlers/status-handler.d.ts +5 -0
- package/dist/server/infra/transport/handlers/status-handler.js +51 -16
- package/dist/server/infra/transport/handlers/vc-handler.d.ts +100 -0
- package/dist/server/infra/transport/handlers/vc-handler.js +1050 -0
- package/dist/server/infra/transport/socket-io-transport-server.d.ts +7 -0
- package/dist/server/infra/transport/socket-io-transport-server.js +12 -1
- package/dist/server/infra/transport/transport-connector.d.ts +1 -1
- package/dist/server/infra/transport/transport-connector.js +2 -1
- package/dist/server/infra/vc/file-vc-git-config-store.d.ts +11 -0
- package/dist/server/infra/vc/file-vc-git-config-store.js +43 -0
- package/dist/server/templates/skill/SKILL.md +167 -33
- package/dist/server/utils/curate-result-parser.d.ts +64 -0
- package/dist/server/utils/curate-result-parser.js +8 -0
- package/dist/server/utils/gitignore.d.ts +9 -0
- package/dist/server/utils/gitignore.js +47 -0
- package/dist/shared/transport/events/index.d.ts +6 -0
- package/dist/shared/transport/events/index.js +3 -0
- package/dist/shared/transport/events/init-events.d.ts +8 -0
- package/dist/shared/transport/events/init-events.js +1 -0
- package/dist/shared/transport/events/push-events.d.ts +6 -0
- package/dist/shared/transport/events/review-events.d.ts +41 -0
- package/dist/shared/transport/events/review-events.js +5 -0
- package/dist/shared/transport/events/vc-events.d.ts +257 -0
- package/dist/shared/transport/events/vc-events.js +67 -0
- package/dist/shared/transport/types/dto.d.ts +6 -1
- package/dist/tui/app/pages/init-project-page.d.ts +9 -0
- package/dist/tui/app/pages/init-project-page.js +54 -0
- package/dist/tui/app/pages/protected-routes.js +14 -6
- package/dist/tui/components/index.d.ts +0 -2
- package/dist/tui/components/index.js +0 -1
- package/dist/tui/features/activity/hooks/use-activity-logs.js +7 -1
- package/dist/tui/features/commands/definitions/index.js +3 -0
- package/dist/tui/features/commands/definitions/space-list.js +9 -18
- package/dist/tui/features/commands/definitions/space-switch.js +10 -6
- package/dist/tui/features/commands/definitions/vc-add.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-add.js +15 -0
- package/dist/tui/features/commands/definitions/vc-branch.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-branch.js +33 -0
- package/dist/tui/features/commands/definitions/vc-checkout.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-checkout.js +32 -0
- package/dist/tui/features/commands/definitions/vc-clone.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-clone.js +18 -0
- package/dist/tui/features/commands/definitions/vc-commit.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-commit.js +32 -0
- package/dist/tui/features/commands/definitions/vc-config.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-config.js +40 -0
- package/dist/tui/features/commands/definitions/vc-fetch.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-fetch.js +37 -0
- package/dist/tui/features/commands/definitions/vc-init.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-init.js +11 -0
- package/dist/tui/features/commands/definitions/vc-log.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-log.js +25 -0
- package/dist/tui/features/commands/definitions/vc-merge.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-merge.js +48 -0
- package/dist/tui/features/commands/definitions/vc-pull.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-pull.js +42 -0
- package/dist/tui/features/commands/definitions/vc-push.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-push.js +38 -0
- package/dist/tui/features/commands/definitions/vc-remote.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-remote.js +57 -0
- package/dist/tui/features/commands/definitions/vc-reset.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-reset.js +35 -0
- package/dist/tui/features/commands/definitions/vc-status.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc-status.js +11 -0
- package/dist/tui/features/commands/definitions/vc.d.ts +2 -0
- package/dist/tui/features/commands/definitions/vc.js +36 -0
- package/dist/tui/features/commands/hooks/use-slash-command-processor.js +5 -5
- package/dist/tui/features/log/api/execute-log.d.ts +8 -0
- package/dist/tui/features/log/api/execute-log.js +13 -0
- package/dist/tui/features/log/components/log-flow.d.ts +14 -0
- package/dist/tui/features/log/components/log-flow.js +29 -0
- package/dist/tui/features/log/utils/format-log.d.ts +3 -0
- package/dist/tui/features/log/utils/format-log.js +42 -0
- package/dist/tui/features/onboarding/hooks/use-app-view-mode.d.ts +9 -5
- package/dist/tui/features/onboarding/hooks/use-app-view-mode.js +12 -5
- package/dist/tui/features/push/components/push-flow.js +9 -2
- package/dist/tui/features/reset/components/reset-flow.js +2 -1
- package/dist/tui/features/status/components/status-view.js +2 -1
- package/dist/tui/features/status/utils/format-status.js +9 -0
- package/dist/tui/features/tasks/hooks/use-task-subscriptions.js +11 -0
- package/dist/tui/features/tasks/stores/tasks-store.d.ts +10 -0
- package/dist/tui/features/tasks/stores/tasks-store.js +16 -0
- package/dist/tui/features/vc/add/api/execute-vc-add.d.ts +8 -0
- package/dist/tui/features/vc/add/api/execute-vc-add.js +13 -0
- package/dist/tui/features/vc/add/components/vc-add-flow.d.ts +7 -0
- package/dist/tui/features/vc/add/components/vc-add-flow.js +35 -0
- package/dist/tui/features/vc/branch/api/execute-vc-branch.d.ts +8 -0
- package/dist/tui/features/vc/branch/api/execute-vc-branch.js +13 -0
- package/dist/tui/features/vc/branch/components/vc-branch-flow.d.ts +8 -0
- package/dist/tui/features/vc/branch/components/vc-branch-flow.js +53 -0
- package/dist/tui/features/vc/branch/utils/format-branch.d.ts +4 -0
- package/dist/tui/features/vc/branch/utils/format-branch.js +12 -0
- package/dist/tui/features/vc/checkout/api/execute-vc-checkout.d.ts +8 -0
- package/dist/tui/features/vc/checkout/api/execute-vc-checkout.js +13 -0
- package/dist/tui/features/vc/checkout/components/vc-checkout-flow.d.ts +8 -0
- package/dist/tui/features/vc/checkout/components/vc-checkout-flow.js +33 -0
- package/dist/tui/features/vc/clone/api/execute-vc-clone.d.ts +8 -0
- package/dist/tui/features/vc/clone/api/execute-vc-clone.js +13 -0
- package/dist/tui/features/vc/clone/components/vc-clone-flow.d.ts +7 -0
- package/dist/tui/features/vc/clone/components/vc-clone-flow.js +79 -0
- package/dist/tui/features/vc/commit/api/execute-vc-commit.d.ts +8 -0
- package/dist/tui/features/vc/commit/api/execute-vc-commit.js +13 -0
- package/dist/tui/features/vc/commit/components/vc-commit-flow.d.ts +7 -0
- package/dist/tui/features/vc/commit/components/vc-commit-flow.js +29 -0
- package/dist/tui/features/vc/config/api/execute-vc-config.d.ts +8 -0
- package/dist/tui/features/vc/config/api/execute-vc-config.js +13 -0
- package/dist/tui/features/vc/config/components/vc-config-flow.d.ts +9 -0
- package/dist/tui/features/vc/config/components/vc-config-flow.js +30 -0
- package/dist/tui/features/vc/fetch/api/execute-vc-fetch.d.ts +8 -0
- package/dist/tui/features/vc/fetch/api/execute-vc-fetch.js +13 -0
- package/dist/tui/features/vc/fetch/components/vc-fetch-flow.d.ts +8 -0
- package/dist/tui/features/vc/fetch/components/vc-fetch-flow.js +75 -0
- package/dist/tui/features/vc/init/api/execute-vc-init.d.ts +8 -0
- package/dist/tui/features/vc/init/api/execute-vc-init.js +13 -0
- package/dist/tui/features/vc/init/components/vc-init-flow.d.ts +10 -0
- package/dist/tui/features/vc/init/components/vc-init-flow.js +37 -0
- package/dist/tui/features/vc/merge/api/execute-vc-merge.d.ts +8 -0
- package/dist/tui/features/vc/merge/api/execute-vc-merge.js +13 -0
- package/dist/tui/features/vc/merge/components/vc-merge-flow.d.ts +11 -0
- package/dist/tui/features/vc/merge/components/vc-merge-flow.js +72 -0
- package/dist/tui/features/vc/pull/api/execute-vc-pull.d.ts +8 -0
- package/dist/tui/features/vc/pull/api/execute-vc-pull.js +13 -0
- package/dist/tui/features/vc/pull/components/vc-pull-flow.d.ts +9 -0
- package/dist/tui/features/vc/pull/components/vc-pull-flow.js +83 -0
- package/dist/tui/features/vc/push/api/execute-vc-push.d.ts +8 -0
- package/dist/tui/features/vc/push/api/execute-vc-push.js +13 -0
- package/dist/tui/features/vc/push/components/vc-push-flow.d.ts +8 -0
- package/dist/tui/features/vc/push/components/vc-push-flow.js +83 -0
- package/dist/tui/features/vc/remote/api/execute-vc-remote.d.ts +8 -0
- package/dist/tui/features/vc/remote/api/execute-vc-remote.js +13 -0
- package/dist/tui/features/vc/remote/components/vc-remote-flow.d.ts +9 -0
- package/dist/tui/features/vc/remote/components/vc-remote-flow.js +42 -0
- package/dist/tui/features/vc/reset/api/execute-vc-reset.d.ts +8 -0
- package/dist/tui/features/vc/reset/api/execute-vc-reset.js +13 -0
- package/dist/tui/features/vc/reset/components/vc-reset-flow.d.ts +10 -0
- package/dist/tui/features/vc/reset/components/vc-reset-flow.js +63 -0
- package/dist/tui/features/vc/status/api/execute-vc-status.d.ts +8 -0
- package/dist/tui/features/vc/status/api/execute-vc-status.js +13 -0
- package/dist/tui/features/vc/status/components/vc-status-flow.d.ts +10 -0
- package/dist/tui/features/vc/status/components/vc-status-flow.js +133 -0
- package/dist/tui/lib/environment.d.ts +8 -0
- package/dist/tui/lib/environment.js +8 -0
- package/dist/tui/utils/error-messages.d.ts +5 -1
- package/dist/tui/utils/error-messages.js +32 -3
- package/oclif.manifest.json +1018 -98
- package/package.json +9 -3
- package/dist/oclif/hooks/prerun/validate-brv-config-version.d.ts +0 -33
- package/dist/oclif/hooks/prerun/validate-brv-config-version.js +0 -86
- package/dist/tui/components/init.d.ts +0 -33
- package/dist/tui/components/init.js +0 -234
- package/dist/tui/features/space/api/get-spaces.d.ts +0 -16
- package/dist/tui/features/space/api/get-spaces.js +0 -17
- package/dist/tui/features/space/api/switch-space.d.ts +0 -11
- package/dist/tui/features/space/api/switch-space.js +0 -24
- package/dist/tui/features/space/components/space-list-view.d.ts +0 -12
- package/dist/tui/features/space/components/space-list-view.js +0 -56
- package/dist/tui/features/space/components/space-switch-flow.d.ts +0 -13
- package/dist/tui/features/space/components/space-switch-flow.js +0 -97
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
1
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
2
|
import { z } from 'zod';
|
|
3
|
+
import { REVIEW_BACKUPS_DIR } from '../../../../server/constants.js';
|
|
4
4
|
import { DirectoryManager } from '../../../../server/core/domain/knowledge/directory-manager.js';
|
|
5
5
|
import { MarkdownWriter, parseFrontmatterScoring } from '../../../../server/core/domain/knowledge/markdown-writer.js';
|
|
6
6
|
import { applyDefaultScoring, determineTier, recordCurateUpdate, } from '../../../../server/core/domain/knowledge/memory-scoring.js';
|
|
7
7
|
import { toSnakeCase } from '../../../../server/utils/file-helpers.js';
|
|
8
|
+
import { deriveImpactFromLoss, detectStructuralLoss } from '../../../core/domain/knowledge/conflict-detector.js';
|
|
9
|
+
import { resolveStructuralLoss } from '../../../core/domain/knowledge/conflict-resolver.js';
|
|
8
10
|
import { ToolName } from '../../../core/domain/tools/constants.js';
|
|
9
11
|
/**
|
|
10
12
|
* Operation types for curating knowledge topics.
|
|
@@ -16,14 +18,23 @@ const OperationType = z.enum(['ADD', 'UPDATE', 'UPSERT', 'MERGE', 'DELETE']);
|
|
|
16
18
|
*/
|
|
17
19
|
const RawConceptSchema = z.object({
|
|
18
20
|
author: z.string().optional().describe('Author or source attribution (e.g., "meowso", "Team Security")'),
|
|
19
|
-
changes: z
|
|
20
|
-
|
|
21
|
+
changes: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('What changes are induced by this concept (e.g., code changes, process updates, market shifts)'),
|
|
25
|
+
files: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Related documents, source files, or resources (e.g., source code paths, reports, data files)'),
|
|
21
29
|
flow: z.string().optional().describe('The process flow or workflow described by this concept'),
|
|
22
|
-
patterns: z
|
|
30
|
+
patterns: z
|
|
31
|
+
.array(z.object({
|
|
23
32
|
description: z.string().describe('What this pattern matches or validates'),
|
|
24
33
|
flags: z.string().optional().describe('Pattern flags (e.g., "gi" for regex)'),
|
|
25
|
-
pattern: z.string().describe('The exact pattern string (e.g., regex pattern)')
|
|
26
|
-
}))
|
|
34
|
+
pattern: z.string().describe('The exact pattern string (e.g., regex pattern)'),
|
|
35
|
+
}))
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Regex or validation patterns related to this concept'),
|
|
27
38
|
task: z.string().optional().describe('What is the task related to this concept'),
|
|
28
39
|
timestamp: z
|
|
29
40
|
.string()
|
|
@@ -38,18 +49,26 @@ const NarrativeSchema = z.object({
|
|
|
38
49
|
.string()
|
|
39
50
|
.optional()
|
|
40
51
|
.describe('Dependency or relationship information (e.g., prerequisite systems, required inputs, related components)'),
|
|
41
|
-
diagrams: z
|
|
42
|
-
|
|
52
|
+
diagrams: z
|
|
53
|
+
.array(z.object({
|
|
54
|
+
content: z
|
|
55
|
+
.string()
|
|
56
|
+
.describe('The full diagram content (Mermaid code, PlantUML code, or ASCII art) - preserved verbatim'),
|
|
43
57
|
title: z.string().optional().describe('Optional title or label for the diagram'),
|
|
44
58
|
type: z.enum(['mermaid', 'plantuml', 'ascii', 'other']).describe('Diagram type for proper rendering'),
|
|
45
|
-
}))
|
|
59
|
+
}))
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Diagrams found in source content - Mermaid, PlantUML, ASCII art, sequence diagrams. Preserve verbatim.'),
|
|
46
62
|
examples: z.string().optional().describe('Concrete examples and use cases demonstrating the concept'),
|
|
47
63
|
highlights: z
|
|
48
64
|
.string()
|
|
49
65
|
.optional()
|
|
50
66
|
.describe('Key highlights, capabilities, deliverables, or notable outcomes (e.g., "User permission can be stale for up to 300 seconds due to Redis cache")'),
|
|
51
67
|
rules: z.string().optional().describe('Exact rules, constraints, or guidelines - preserved verbatim from source'),
|
|
52
|
-
structure: z
|
|
68
|
+
structure: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Structural or organizational documentation (e.g., file layout, data schema, process hierarchy)'),
|
|
53
72
|
});
|
|
54
73
|
/**
|
|
55
74
|
* Fact schema for structured factual statements extracted during curation.
|
|
@@ -59,24 +78,25 @@ const FactSchema = z.object({
|
|
|
59
78
|
.enum(['personal', 'project', 'preference', 'convention', 'team', 'environment', 'other'])
|
|
60
79
|
.optional()
|
|
61
80
|
.describe('Category of the fact (e.g., "personal", "project", "preference", "convention", "team", "environment")'),
|
|
62
|
-
statement: z
|
|
63
|
-
.string()
|
|
64
|
-
.describe('The full factual statement (e.g., "My name is Andy", "We use PostgreSQL 15")'),
|
|
81
|
+
statement: z.string().describe('The full factual statement (e.g., "My name is Andy", "We use PostgreSQL 15")'),
|
|
65
82
|
subject: z
|
|
66
83
|
.string()
|
|
67
84
|
.optional()
|
|
68
85
|
.describe('What the fact is about in snake_case (e.g., "user_name", "database", "sprint_duration")'),
|
|
69
|
-
value: z
|
|
70
|
-
.string()
|
|
71
|
-
.optional()
|
|
72
|
-
.describe('The extracted value (e.g., "Andy", "PostgreSQL 15", "2 weeks")'),
|
|
86
|
+
value: z.string().optional().describe('The extracted value (e.g., "Andy", "PostgreSQL 15", "2 weeks")'),
|
|
73
87
|
});
|
|
74
88
|
/**
|
|
75
89
|
* Content structure for ADD and UPDATE operations.
|
|
76
90
|
*/
|
|
77
91
|
const ContentSchema = z.object({
|
|
78
|
-
facts: z
|
|
79
|
-
|
|
92
|
+
facts: z
|
|
93
|
+
.array(FactSchema)
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Factual statements extracted from content (e.g., personal info, project facts, preferences, conventions)'),
|
|
96
|
+
keywords: z
|
|
97
|
+
.array(z.string())
|
|
98
|
+
.default([])
|
|
99
|
+
.describe('Keywords for search and discovery (e.g., ["jwt", "refresh_token", "rotation"])'),
|
|
80
100
|
narrative: NarrativeSchema.optional().describe('Narrative section with descriptive and structural context'),
|
|
81
101
|
rawConcept: RawConceptSchema.optional().describe('Raw concept section with metadata and technical footprint'),
|
|
82
102
|
relations: z
|
|
@@ -84,7 +104,10 @@ const ContentSchema = z.object({
|
|
|
84
104
|
.optional()
|
|
85
105
|
.describe('Related topics using domain/topic/title.md or domain/topic/subtopic/title.md notation'),
|
|
86
106
|
snippets: z.array(z.string()).optional().describe('Code/text snippets'),
|
|
87
|
-
tags: z
|
|
107
|
+
tags: z
|
|
108
|
+
.array(z.string())
|
|
109
|
+
.default([])
|
|
110
|
+
.describe('Tags for categorization and filtering (e.g., ["authentication", "security", "jwt"])'),
|
|
88
111
|
});
|
|
89
112
|
/**
|
|
90
113
|
* Domain context schema for domain-level context.md files.
|
|
@@ -137,13 +160,25 @@ const SubtopicContextSchema = z.object({
|
|
|
137
160
|
* Single operation schema for curating knowledge.
|
|
138
161
|
*/
|
|
139
162
|
const OperationSchema = z.object({
|
|
163
|
+
confidence: z
|
|
164
|
+
.enum(['high', 'low'])
|
|
165
|
+
.describe('Your confidence in the accuracy and completeness of this operation. Use "high" when you have direct evidence from the source material; use "low" when the information is inferred, uncertain, or incomplete.'),
|
|
140
166
|
content: ContentSchema.optional().describe('Content for ADD/UPDATE operations'),
|
|
141
167
|
domainContext: DomainContextSchema.optional().describe('Domain-level context for new domains. When creating content in a NEW domain, provide this to auto-generate domain/context.md with purpose, scope, ownership, and usage. Only needed when the domain does not exist yet.'),
|
|
168
|
+
impact: z
|
|
169
|
+
.enum(['high', 'low'])
|
|
170
|
+
.describe('Estimated scope of impact of this knowledge change. "high": Changes that alter core decisions, strategies, tools, or established approaches. Any change that contradicts or reverses previously curated knowledge. Updates to existing knowledge that change its core substance. Deletions are always high impact. "low": New additions to previously undocumented topics, minor corrections, supplementary details like examples and clarifications, or updates that extend existing knowledge without changing its core substance.'),
|
|
142
171
|
mergeTarget: z.string().optional().describe('Target path for MERGE operation'),
|
|
143
172
|
mergeTargetTitle: z.string().optional().describe('Title of the target file for MERGE operation'),
|
|
144
173
|
path: z.string().describe('Path: domain/topic/title.md or domain/topic/subtopic/title.md'),
|
|
145
|
-
reason: z
|
|
174
|
+
reason: z
|
|
175
|
+
.string()
|
|
176
|
+
.describe('The motivation and context behind this curation — the WHY, not the what. Describe the decision, event, conversation, or observation that made this knowledge worth capturing. Write it for a human reviewer: they will read this in the web inbox to decide whether to approve or modify the change. Example of a good reason: "After debating caching strategies in PR #42, the team chose Redis with a 5-minute TTL as a deliberate performance/freshness trade-off — future agents should know this was intentional." Bad example: "Updating caching documentation."'),
|
|
146
177
|
subtopicContext: SubtopicContextSchema.optional().describe('Subtopic-level context for new subtopics. When creating content in a NEW subtopic, provide this to auto-generate subtopic/context.md with focus and parent relation. Only needed when the subtopic does not exist yet.'),
|
|
178
|
+
summary: z
|
|
179
|
+
.string()
|
|
180
|
+
.optional()
|
|
181
|
+
.describe('One-line semantic summary of what this knowledge file contains after this operation. For human reviewers to quickly grasp the content without reading the full document. Example: "Caching strategy using Redis with 5-min TTL and write-through invalidation". Required for ADD/UPDATE/UPSERT/MERGE, not needed for DELETE.'),
|
|
147
182
|
title: z
|
|
148
183
|
.string()
|
|
149
184
|
.optional()
|
|
@@ -155,21 +190,20 @@ const OperationSchema = z.object({
|
|
|
155
190
|
* Filter out non-existent files from rawConcept.files.
|
|
156
191
|
* Returns a new content object with only valid file paths.
|
|
157
192
|
*/
|
|
158
|
-
function filterValidFiles(content) {
|
|
193
|
+
async function filterValidFiles(content) {
|
|
159
194
|
if (!content.rawConcept?.files || content.rawConcept.files.length === 0) {
|
|
160
195
|
return content;
|
|
161
196
|
}
|
|
162
|
-
const
|
|
197
|
+
const checks = await Promise.all(content.rawConcept.files.map(async (filePath) => {
|
|
163
198
|
// Skip filesystem validation for URLs and document references
|
|
164
|
-
if (filePath.includes('://'))
|
|
199
|
+
if (filePath.includes('://'))
|
|
165
200
|
return true;
|
|
166
|
-
}
|
|
167
201
|
// Skip entries that look like document references (no path separators, contain spaces)
|
|
168
|
-
if (!filePath.includes('/') && !filePath.includes('\\') && filePath.includes(' '))
|
|
202
|
+
if (!filePath.includes('/') && !filePath.includes('\\') && filePath.includes(' '))
|
|
169
203
|
return true;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
204
|
+
return DirectoryManager.fileExists(filePath);
|
|
205
|
+
}));
|
|
206
|
+
const validFiles = content.rawConcept.files.filter((_, i) => checks[i]);
|
|
173
207
|
// Return content with filtered files (empty array if none exist)
|
|
174
208
|
return {
|
|
175
209
|
...content,
|
|
@@ -187,6 +221,45 @@ export const CurateInputSchema = z.object({
|
|
|
187
221
|
basePath: z.string().default('.brv/context-tree').describe('Base path for knowledge storage'),
|
|
188
222
|
operations: z.array(OperationSchema).describe('Array of curate operations to apply'),
|
|
189
223
|
});
|
|
224
|
+
/**
|
|
225
|
+
* Derive review metadata for a curate operation.
|
|
226
|
+
* confidence and impact are LLM-provided (schema defaults applied by Zod when omitted).
|
|
227
|
+
* needsReview:
|
|
228
|
+
* - DELETE always (irreversible)
|
|
229
|
+
* - high impact always (core decisions, strategy changes, contradictions)
|
|
230
|
+
* - low impact → no review (minor additions/corrections)
|
|
231
|
+
*/
|
|
232
|
+
function deriveReviewMetadata(type, confidence, impact) {
|
|
233
|
+
const needsReview = type === 'DELETE' || impact === 'high';
|
|
234
|
+
return { confidence, impact, needsReview };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Back up a file's content before curate overwrites or deletes it.
|
|
238
|
+
*
|
|
239
|
+
* First-write-wins: if a backup already exists for this path, this is a no-op.
|
|
240
|
+
* This ensures the backup always reflects the snapshot version (state at last push),
|
|
241
|
+
* even when multiple curate operations modify the same file between pushes.
|
|
242
|
+
*
|
|
243
|
+
* @param filePath - Absolute path to the context tree file being modified
|
|
244
|
+
* @param basePath - Context tree base path (e.g., '.brv/context-tree')
|
|
245
|
+
*/
|
|
246
|
+
async function backupBeforeWrite(filePath, basePath) {
|
|
247
|
+
try {
|
|
248
|
+
const brvDir = dirname(resolve(basePath));
|
|
249
|
+
const relativePath = relative(resolve(basePath), resolve(filePath));
|
|
250
|
+
const backupPath = join(brvDir, REVIEW_BACKUPS_DIR, relativePath);
|
|
251
|
+
// First-write-wins: skip if backup already exists
|
|
252
|
+
const backupExists = await DirectoryManager.fileExists(backupPath);
|
|
253
|
+
if (backupExists)
|
|
254
|
+
return;
|
|
255
|
+
// Read current content and save as backup
|
|
256
|
+
const content = await DirectoryManager.readFile(filePath);
|
|
257
|
+
await DirectoryManager.writeFileAtomic(backupPath, content);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Best-effort: backup failure must never block curate operations
|
|
261
|
+
}
|
|
262
|
+
}
|
|
190
263
|
function generateDomainContextMarkdown(domainName, context) {
|
|
191
264
|
const sections = [`# Domain: ${domainName}`, '', '## Purpose', context.purpose, '', '## Scope'];
|
|
192
265
|
if (context.scope.included.length > 0) {
|
|
@@ -360,19 +433,24 @@ function buildFullPath(basePath, knowledgePath) {
|
|
|
360
433
|
* Execute ADD operation - create new domain/topic/subtopic with {title}.md
|
|
361
434
|
*/
|
|
362
435
|
async function executeAdd(basePath, operation) {
|
|
363
|
-
const { content, domainContext, path, reason, subtopicContext, title, topicContext } = operation;
|
|
436
|
+
const { confidence, content, domainContext, impact, path, reason, subtopicContext, summary, title, topicContext } = operation;
|
|
437
|
+
const reviewMeta = deriveReviewMetadata('ADD', confidence, impact);
|
|
364
438
|
if (!title) {
|
|
365
439
|
return {
|
|
440
|
+
...reviewMeta,
|
|
366
441
|
message: 'ADD operation requires a title',
|
|
367
442
|
path,
|
|
443
|
+
reason,
|
|
368
444
|
status: 'failed',
|
|
369
445
|
type: 'ADD',
|
|
370
446
|
};
|
|
371
447
|
}
|
|
372
448
|
if (!content) {
|
|
373
449
|
return {
|
|
450
|
+
...reviewMeta,
|
|
374
451
|
message: 'ADD operation requires content',
|
|
375
452
|
path,
|
|
453
|
+
reason,
|
|
376
454
|
status: 'failed',
|
|
377
455
|
type: 'ADD',
|
|
378
456
|
};
|
|
@@ -381,8 +459,10 @@ async function executeAdd(basePath, operation) {
|
|
|
381
459
|
const parsed = parsePath(path);
|
|
382
460
|
if (!parsed) {
|
|
383
461
|
return {
|
|
462
|
+
...reviewMeta,
|
|
384
463
|
message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
|
|
385
464
|
path,
|
|
465
|
+
reason,
|
|
386
466
|
status: 'failed',
|
|
387
467
|
type: 'ADD',
|
|
388
468
|
};
|
|
@@ -390,8 +470,10 @@ async function executeAdd(basePath, operation) {
|
|
|
390
470
|
const domainValidation = validateDomain(parsed.domain);
|
|
391
471
|
if (!domainValidation.allowed) {
|
|
392
472
|
return {
|
|
473
|
+
...reviewMeta,
|
|
393
474
|
message: domainValidation.reason,
|
|
394
475
|
path,
|
|
476
|
+
reason,
|
|
395
477
|
status: 'failed',
|
|
396
478
|
type: 'ADD',
|
|
397
479
|
};
|
|
@@ -401,16 +483,18 @@ async function executeAdd(basePath, operation) {
|
|
|
401
483
|
const topicPath = join(domainPath, toSnakeCase(parsed.topic));
|
|
402
484
|
const finalPath = parsed.subtopic ? join(topicPath, toSnakeCase(parsed.subtopic)) : topicPath;
|
|
403
485
|
// Filter out non-existent files from rawConcept.files
|
|
404
|
-
const filteredContent = filterValidFiles(content);
|
|
486
|
+
const filteredContent = await filterValidFiles(content);
|
|
405
487
|
const contextContent = MarkdownWriter.generateContext({
|
|
406
488
|
facts: filteredContent.facts,
|
|
407
489
|
keywords: filteredContent.keywords,
|
|
408
490
|
name: title,
|
|
409
491
|
narrative: filteredContent.narrative,
|
|
410
492
|
rawConcept: filteredContent.rawConcept,
|
|
493
|
+
reason,
|
|
411
494
|
relations: filteredContent.relations,
|
|
412
495
|
scoring: applyDefaultScoring(),
|
|
413
496
|
snippets: filteredContent.snippets ?? [],
|
|
497
|
+
summary,
|
|
414
498
|
tags: filteredContent.tags,
|
|
415
499
|
});
|
|
416
500
|
const filename = `${toSnakeCase(title)}.md`;
|
|
@@ -418,39 +502,57 @@ async function executeAdd(basePath, operation) {
|
|
|
418
502
|
await DirectoryManager.writeFileAtomic(contextPath, contextContent);
|
|
419
503
|
await ensureContextMd(basePath, parsed, topicContext, subtopicContext);
|
|
420
504
|
return {
|
|
505
|
+
...reviewMeta,
|
|
421
506
|
filePath: contextPath,
|
|
422
507
|
message: `Created ${path}/${filename} with ${content.snippets?.length || 0} snippets. Reason: ${reason}`,
|
|
423
508
|
path,
|
|
509
|
+
reason,
|
|
424
510
|
status: 'success',
|
|
511
|
+
summary,
|
|
425
512
|
type: 'ADD',
|
|
426
513
|
};
|
|
427
514
|
}
|
|
428
515
|
catch (error) {
|
|
429
516
|
return {
|
|
517
|
+
...reviewMeta,
|
|
430
518
|
message: error instanceof Error ? error.message : String(error),
|
|
431
519
|
path,
|
|
520
|
+
reason,
|
|
432
521
|
status: 'failed',
|
|
433
522
|
type: 'ADD',
|
|
434
523
|
};
|
|
435
524
|
}
|
|
436
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Compute the maximum of two impact levels.
|
|
528
|
+
*/
|
|
529
|
+
function maxImpact(a, b) {
|
|
530
|
+
const rank = { high: 1, low: 0 };
|
|
531
|
+
return rank[a] >= rank[b] ? a : b;
|
|
532
|
+
}
|
|
437
533
|
/**
|
|
438
534
|
* Execute UPDATE operation - modify existing {title}.md
|
|
439
535
|
*/
|
|
440
536
|
async function executeUpdate(basePath, operation) {
|
|
441
|
-
const { content, domainContext, path, reason, subtopicContext, title, topicContext } = operation;
|
|
537
|
+
const { confidence, content, domainContext, impact, path, reason, subtopicContext, summary, title, topicContext } = operation;
|
|
538
|
+
// Used for early-exit validation failures (before structural loss can be assessed)
|
|
539
|
+
const baseReviewMeta = deriveReviewMetadata('UPDATE', confidence, impact);
|
|
442
540
|
if (!title) {
|
|
443
541
|
return {
|
|
542
|
+
...baseReviewMeta,
|
|
444
543
|
message: 'UPDATE operation requires a title',
|
|
445
544
|
path,
|
|
545
|
+
reason,
|
|
446
546
|
status: 'failed',
|
|
447
547
|
type: 'UPDATE',
|
|
448
548
|
};
|
|
449
549
|
}
|
|
450
550
|
if (!content) {
|
|
451
551
|
return {
|
|
552
|
+
...baseReviewMeta,
|
|
452
553
|
message: 'UPDATE operation requires content',
|
|
453
554
|
path,
|
|
555
|
+
reason,
|
|
454
556
|
status: 'failed',
|
|
455
557
|
type: 'UPDATE',
|
|
456
558
|
};
|
|
@@ -459,8 +561,10 @@ async function executeUpdate(basePath, operation) {
|
|
|
459
561
|
const parsed = parsePath(path);
|
|
460
562
|
if (!parsed) {
|
|
461
563
|
return {
|
|
564
|
+
...baseReviewMeta,
|
|
462
565
|
message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
|
|
463
566
|
path,
|
|
567
|
+
reason,
|
|
464
568
|
status: 'failed',
|
|
465
569
|
type: 'UPDATE',
|
|
466
570
|
};
|
|
@@ -471,46 +575,73 @@ async function executeUpdate(basePath, operation) {
|
|
|
471
575
|
const exists = await DirectoryManager.fileExists(contextPath);
|
|
472
576
|
if (!exists) {
|
|
473
577
|
return {
|
|
578
|
+
...baseReviewMeta,
|
|
474
579
|
message: `File does not exist: ${path}/${filename}`,
|
|
475
580
|
path,
|
|
581
|
+
reason,
|
|
476
582
|
status: 'failed',
|
|
477
583
|
type: 'UPDATE',
|
|
478
584
|
};
|
|
479
585
|
}
|
|
480
586
|
await createDomainContextIfMissing(basePath, parsed.domain, domainContext);
|
|
481
|
-
// Read existing file to preserve and
|
|
587
|
+
// Read existing file to preserve scoring metadata and detect structural loss
|
|
482
588
|
const existingContent = await DirectoryManager.readFile(contextPath);
|
|
483
589
|
const existingScoring = existingContent ? parseFrontmatterScoring(existingContent) : undefined;
|
|
484
590
|
const updatedScoring = existingScoring ? recordCurateUpdate(existingScoring) : applyDefaultScoring();
|
|
485
591
|
const newTier = determineTier(updatedScoring.importance ?? 50, (updatedScoring.maturity ?? 'draft'));
|
|
486
592
|
const finalScoring = { ...updatedScoring, maturity: newTier };
|
|
487
593
|
// Filter out non-existent files from rawConcept.files
|
|
488
|
-
const filteredContent = filterValidFiles(content);
|
|
489
|
-
|
|
594
|
+
const filteredContent = await filterValidFiles(content);
|
|
595
|
+
// Extract previous summary from existing file's frontmatter (for review UI)
|
|
596
|
+
const existingParsed = existingContent ? MarkdownWriter.parseContent(existingContent, title) : null;
|
|
597
|
+
const previousSummary = existingParsed?.summary;
|
|
598
|
+
// Detect structural loss and auto-resolve: merge back anything the LLM dropped
|
|
599
|
+
const proposedContextData = {
|
|
490
600
|
facts: filteredContent.facts,
|
|
491
601
|
keywords: filteredContent.keywords,
|
|
492
602
|
name: title,
|
|
493
603
|
narrative: filteredContent.narrative,
|
|
494
604
|
rawConcept: filteredContent.rawConcept,
|
|
495
605
|
relations: filteredContent.relations,
|
|
496
|
-
scoring: finalScoring,
|
|
497
606
|
snippets: filteredContent.snippets ?? [],
|
|
498
607
|
tags: filteredContent.tags,
|
|
608
|
+
};
|
|
609
|
+
let resolvedContextData = proposedContextData;
|
|
610
|
+
let elevatedImpact = impact;
|
|
611
|
+
if (existingParsed) {
|
|
612
|
+
const loss = detectStructuralLoss(existingParsed, proposedContextData);
|
|
613
|
+
const structuralImpact = deriveImpactFromLoss(loss);
|
|
614
|
+
elevatedImpact = maxImpact(impact, structuralImpact);
|
|
615
|
+
resolvedContextData = resolveStructuralLoss(existingParsed, proposedContextData, loss);
|
|
616
|
+
}
|
|
617
|
+
const reviewMeta = deriveReviewMetadata('UPDATE', confidence, elevatedImpact);
|
|
618
|
+
const contextContent = MarkdownWriter.generateContext({
|
|
619
|
+
...resolvedContextData,
|
|
620
|
+
reason,
|
|
621
|
+
scoring: finalScoring,
|
|
622
|
+
summary,
|
|
499
623
|
});
|
|
624
|
+
await backupBeforeWrite(contextPath, basePath);
|
|
500
625
|
await DirectoryManager.writeFileAtomic(contextPath, contextContent);
|
|
501
626
|
await ensureContextMd(basePath, parsed, topicContext, subtopicContext);
|
|
502
627
|
return {
|
|
628
|
+
...reviewMeta,
|
|
503
629
|
filePath: contextPath,
|
|
504
630
|
message: `Updated ${path}/${filename}. Reason: ${reason}`,
|
|
505
631
|
path,
|
|
632
|
+
previousSummary,
|
|
633
|
+
reason,
|
|
506
634
|
status: 'success',
|
|
635
|
+
summary,
|
|
507
636
|
type: 'UPDATE',
|
|
508
637
|
};
|
|
509
638
|
}
|
|
510
639
|
catch (error) {
|
|
511
640
|
return {
|
|
641
|
+
...baseReviewMeta,
|
|
512
642
|
message: error instanceof Error ? error.message : String(error),
|
|
513
643
|
path,
|
|
644
|
+
reason,
|
|
514
645
|
status: 'failed',
|
|
515
646
|
type: 'UPDATE',
|
|
516
647
|
};
|
|
@@ -521,19 +652,24 @@ async function executeUpdate(basePath, operation) {
|
|
|
521
652
|
* This is the recommended operation type as it eliminates the need for pre-checks.
|
|
522
653
|
*/
|
|
523
654
|
async function executeUpsert(basePath, operation) {
|
|
524
|
-
const { path, title } = operation;
|
|
655
|
+
const { path, reason, title } = operation;
|
|
656
|
+
const reviewMeta = deriveReviewMetadata('UPSERT', operation.confidence, operation.impact);
|
|
525
657
|
if (!title) {
|
|
526
658
|
return {
|
|
659
|
+
...reviewMeta,
|
|
527
660
|
message: 'UPSERT operation requires a title',
|
|
528
661
|
path,
|
|
662
|
+
reason,
|
|
529
663
|
status: 'failed',
|
|
530
664
|
type: 'UPSERT',
|
|
531
665
|
};
|
|
532
666
|
}
|
|
533
667
|
if (!operation.content) {
|
|
534
668
|
return {
|
|
669
|
+
...reviewMeta,
|
|
535
670
|
message: 'UPSERT operation requires content',
|
|
536
671
|
path,
|
|
672
|
+
reason,
|
|
537
673
|
status: 'failed',
|
|
538
674
|
type: 'UPSERT',
|
|
539
675
|
};
|
|
@@ -542,8 +678,10 @@ async function executeUpsert(basePath, operation) {
|
|
|
542
678
|
const parsed = parsePath(path);
|
|
543
679
|
if (!parsed) {
|
|
544
680
|
return {
|
|
681
|
+
...reviewMeta,
|
|
545
682
|
message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
|
|
546
683
|
path,
|
|
684
|
+
reason,
|
|
547
685
|
status: 'failed',
|
|
548
686
|
type: 'UPSERT',
|
|
549
687
|
};
|
|
@@ -574,8 +712,10 @@ async function executeUpsert(basePath, operation) {
|
|
|
574
712
|
}
|
|
575
713
|
catch (error) {
|
|
576
714
|
return {
|
|
715
|
+
...reviewMeta,
|
|
577
716
|
message: error instanceof Error ? error.message : String(error),
|
|
578
717
|
path,
|
|
718
|
+
reason,
|
|
579
719
|
status: 'failed',
|
|
580
720
|
type: 'UPSERT',
|
|
581
721
|
};
|
|
@@ -585,27 +725,34 @@ async function executeUpsert(basePath, operation) {
|
|
|
585
725
|
* Execute MERGE operation - combine source file into target file, delete source file
|
|
586
726
|
*/
|
|
587
727
|
async function executeMerge(basePath, operation) {
|
|
588
|
-
const { domainContext, mergeTarget, mergeTargetTitle, path, reason, subtopicContext, title, topicContext } = operation;
|
|
728
|
+
const { confidence, domainContext, impact, mergeTarget, mergeTargetTitle, path, reason, subtopicContext, summary, title, topicContext, } = operation;
|
|
729
|
+
const reviewMeta = deriveReviewMetadata('MERGE', confidence, impact);
|
|
589
730
|
if (!title) {
|
|
590
731
|
return {
|
|
732
|
+
...reviewMeta,
|
|
591
733
|
message: 'MERGE operation requires a title (source file)',
|
|
592
734
|
path,
|
|
735
|
+
reason,
|
|
593
736
|
status: 'failed',
|
|
594
737
|
type: 'MERGE',
|
|
595
738
|
};
|
|
596
739
|
}
|
|
597
740
|
if (!mergeTarget) {
|
|
598
741
|
return {
|
|
742
|
+
...reviewMeta,
|
|
599
743
|
message: 'MERGE operation requires mergeTarget',
|
|
600
744
|
path,
|
|
745
|
+
reason,
|
|
601
746
|
status: 'failed',
|
|
602
747
|
type: 'MERGE',
|
|
603
748
|
};
|
|
604
749
|
}
|
|
605
750
|
if (!mergeTargetTitle) {
|
|
606
751
|
return {
|
|
752
|
+
...reviewMeta,
|
|
607
753
|
message: 'MERGE operation requires mergeTargetTitle',
|
|
608
754
|
path,
|
|
755
|
+
reason,
|
|
609
756
|
status: 'failed',
|
|
610
757
|
type: 'MERGE',
|
|
611
758
|
};
|
|
@@ -615,8 +762,10 @@ async function executeMerge(basePath, operation) {
|
|
|
615
762
|
const targetParsed = parsePath(mergeTarget);
|
|
616
763
|
if (!sourceParsed || !targetParsed) {
|
|
617
764
|
return {
|
|
765
|
+
...reviewMeta,
|
|
618
766
|
message: `Invalid path format. Expected domain/topic or domain/topic/subtopic`,
|
|
619
767
|
path,
|
|
768
|
+
reason,
|
|
620
769
|
status: 'failed',
|
|
621
770
|
type: 'MERGE',
|
|
622
771
|
};
|
|
@@ -631,16 +780,20 @@ async function executeMerge(basePath, operation) {
|
|
|
631
780
|
const targetExists = await DirectoryManager.fileExists(targetContextPath);
|
|
632
781
|
if (!sourceExists) {
|
|
633
782
|
return {
|
|
783
|
+
...reviewMeta,
|
|
634
784
|
message: `Source file does not exist: ${path}/${sourceFilename}`,
|
|
635
785
|
path,
|
|
786
|
+
reason,
|
|
636
787
|
status: 'failed',
|
|
637
788
|
type: 'MERGE',
|
|
638
789
|
};
|
|
639
790
|
}
|
|
640
791
|
if (!targetExists) {
|
|
641
792
|
return {
|
|
793
|
+
...reviewMeta,
|
|
642
794
|
message: `Target file does not exist: ${mergeTarget}/${targetFilename}`,
|
|
643
795
|
path,
|
|
796
|
+
reason,
|
|
644
797
|
status: 'failed',
|
|
645
798
|
type: 'MERGE',
|
|
646
799
|
};
|
|
@@ -649,23 +802,36 @@ async function executeMerge(basePath, operation) {
|
|
|
649
802
|
await createDomainContextIfMissing(basePath, targetParsed.domain, domainContext);
|
|
650
803
|
const sourceContent = await DirectoryManager.readFile(sourceContextPath);
|
|
651
804
|
const targetContent = await DirectoryManager.readFile(targetContextPath);
|
|
652
|
-
|
|
805
|
+
// Extract previous summary from target file (for review UI)
|
|
806
|
+
const targetParsedContent = MarkdownWriter.parseContent(targetContent, mergeTargetTitle);
|
|
807
|
+
const previousSummary = targetParsedContent.summary;
|
|
808
|
+
// Backup both files before merge modifies target and deletes source
|
|
809
|
+
await backupBeforeWrite(targetContextPath, basePath);
|
|
810
|
+
await backupBeforeWrite(sourceContextPath, basePath);
|
|
811
|
+
const mergedContent = MarkdownWriter.mergeContexts(sourceContent, targetContent, reason, summary);
|
|
653
812
|
await DirectoryManager.writeFileAtomic(targetContextPath, mergedContent);
|
|
654
813
|
await DirectoryManager.deleteFile(sourceContextPath);
|
|
655
814
|
await ensureContextMd(basePath, sourceParsed, topicContext, subtopicContext);
|
|
656
815
|
await ensureContextMd(basePath, targetParsed, topicContext, subtopicContext);
|
|
657
816
|
return {
|
|
817
|
+
...reviewMeta,
|
|
818
|
+
additionalFilePaths: [sourceContextPath],
|
|
658
819
|
filePath: targetContextPath,
|
|
659
820
|
message: `Merged ${path}/${sourceFilename} into ${mergeTarget}/${targetFilename}. Reason: ${reason}`,
|
|
660
821
|
path,
|
|
822
|
+
previousSummary,
|
|
823
|
+
reason,
|
|
661
824
|
status: 'success',
|
|
825
|
+
summary,
|
|
662
826
|
type: 'MERGE',
|
|
663
827
|
};
|
|
664
828
|
}
|
|
665
829
|
catch (error) {
|
|
666
830
|
return {
|
|
831
|
+
...reviewMeta,
|
|
667
832
|
message: error instanceof Error ? error.message : String(error),
|
|
668
833
|
path,
|
|
834
|
+
reason,
|
|
669
835
|
status: 'failed',
|
|
670
836
|
type: 'MERGE',
|
|
671
837
|
};
|
|
@@ -677,6 +843,7 @@ async function executeMerge(basePath, operation) {
|
|
|
677
843
|
*/
|
|
678
844
|
async function executeDelete(basePath, operation) {
|
|
679
845
|
const { path, reason, title } = operation;
|
|
846
|
+
const reviewMeta = deriveReviewMetadata('DELETE', operation.confidence, operation.impact);
|
|
680
847
|
try {
|
|
681
848
|
const fullPath = buildFullPath(basePath, path);
|
|
682
849
|
if (title) {
|
|
@@ -686,16 +853,34 @@ async function executeDelete(basePath, operation) {
|
|
|
686
853
|
const exists = await DirectoryManager.fileExists(filePath);
|
|
687
854
|
if (!exists) {
|
|
688
855
|
return {
|
|
856
|
+
...reviewMeta,
|
|
689
857
|
message: `File does not exist: ${path}/${filename}`,
|
|
690
858
|
path,
|
|
859
|
+
reason,
|
|
691
860
|
status: 'failed',
|
|
692
861
|
type: 'DELETE',
|
|
693
862
|
};
|
|
694
863
|
}
|
|
864
|
+
// Extract previous summary from file being deleted (for review UI)
|
|
865
|
+
let previousSummary;
|
|
866
|
+
try {
|
|
867
|
+
const existingContent = await DirectoryManager.readFile(filePath);
|
|
868
|
+
if (existingContent) {
|
|
869
|
+
previousSummary = MarkdownWriter.parseContent(existingContent, title).summary;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
// Best-effort: summary extraction failure must never block delete
|
|
874
|
+
}
|
|
875
|
+
await backupBeforeWrite(filePath, basePath);
|
|
695
876
|
await DirectoryManager.deleteFile(filePath);
|
|
696
877
|
return {
|
|
878
|
+
...reviewMeta,
|
|
879
|
+
filePath,
|
|
697
880
|
message: `Deleted ${path}/${filename}. Reason: ${reason}`,
|
|
698
881
|
path,
|
|
882
|
+
previousSummary,
|
|
883
|
+
reason,
|
|
699
884
|
status: 'success',
|
|
700
885
|
type: 'DELETE',
|
|
701
886
|
};
|
|
@@ -704,24 +889,56 @@ async function executeDelete(basePath, operation) {
|
|
|
704
889
|
const exists = await DirectoryManager.folderExists(fullPath);
|
|
705
890
|
if (!exists) {
|
|
706
891
|
return {
|
|
892
|
+
...reviewMeta,
|
|
707
893
|
message: `Folder does not exist: ${path}`,
|
|
708
894
|
path,
|
|
895
|
+
reason,
|
|
709
896
|
status: 'failed',
|
|
710
897
|
type: 'DELETE',
|
|
711
898
|
};
|
|
712
899
|
}
|
|
900
|
+
// Backup all markdown files in the folder before deleting
|
|
901
|
+
const mdFiles = await DirectoryManager.listMarkdownFiles(fullPath);
|
|
902
|
+
// Extract previous summary as bullet list of individual file summaries (for review UI)
|
|
903
|
+
let previousSummary;
|
|
904
|
+
try {
|
|
905
|
+
const contentFiles = mdFiles.filter((f) => {
|
|
906
|
+
const name = basename(f);
|
|
907
|
+
return name !== '_index.md' && name !== 'context.md';
|
|
908
|
+
});
|
|
909
|
+
const contents = await Promise.all(contentFiles.map(async (f) => ({ content: await DirectoryManager.readFile(f), name: basename(f) })));
|
|
910
|
+
const bullets = contents
|
|
911
|
+
.filter((c) => c.content !== null && c.content !== undefined)
|
|
912
|
+
.map((c) => ({ name: c.name, summary: MarkdownWriter.parseContent(c.content, c.name.replace(/\.md$/, '')).summary }))
|
|
913
|
+
.filter((c) => c.summary !== undefined)
|
|
914
|
+
.map((c) => `- ${c.name.replace(/\.md$/, '').replaceAll('_', ' ')}: ${c.summary}`);
|
|
915
|
+
if (bullets.length > 0) {
|
|
916
|
+
previousSummary = bullets.join('\n');
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
catch {
|
|
920
|
+
// Best-effort: summary extraction failure must never block delete
|
|
921
|
+
}
|
|
922
|
+
await Promise.all(mdFiles.map((f) => backupBeforeWrite(f, basePath)));
|
|
713
923
|
await DirectoryManager.deleteTopicRecursive(fullPath);
|
|
714
924
|
return {
|
|
925
|
+
...reviewMeta,
|
|
926
|
+
additionalFilePaths: mdFiles,
|
|
927
|
+
filePath: fullPath,
|
|
715
928
|
message: `Deleted folder ${path}. Reason: ${reason}`,
|
|
716
929
|
path,
|
|
930
|
+
previousSummary,
|
|
931
|
+
reason,
|
|
717
932
|
status: 'success',
|
|
718
933
|
type: 'DELETE',
|
|
719
934
|
};
|
|
720
935
|
}
|
|
721
936
|
catch (error) {
|
|
722
937
|
return {
|
|
938
|
+
...reviewMeta,
|
|
723
939
|
message: error instanceof Error ? error.message : String(error),
|
|
724
940
|
path,
|
|
941
|
+
reason,
|
|
725
942
|
status: 'failed',
|
|
726
943
|
type: 'DELETE',
|
|
727
944
|
};
|
|
@@ -737,8 +954,12 @@ export async function executeCurate(input, _context) {
|
|
|
737
954
|
return {
|
|
738
955
|
applied: [
|
|
739
956
|
{
|
|
957
|
+
confidence: 'high',
|
|
958
|
+
impact: 'low',
|
|
740
959
|
message: `Invalid input: ${parseResult.error.message}`,
|
|
960
|
+
needsReview: false,
|
|
741
961
|
path: '',
|
|
962
|
+
reason: '',
|
|
742
963
|
status: 'failed',
|
|
743
964
|
type: 'ADD',
|
|
744
965
|
},
|
|
@@ -806,8 +1027,12 @@ export async function executeCurate(input, _context) {
|
|
|
806
1027
|
// Exhaustive type check - TypeScript will error if any case is missed
|
|
807
1028
|
const exhaustiveCheck = operation.type;
|
|
808
1029
|
result = {
|
|
1030
|
+
confidence: 'high',
|
|
1031
|
+
impact: 'low',
|
|
809
1032
|
message: `Unknown operation type: ${exhaustiveCheck}`,
|
|
1033
|
+
needsReview: false,
|
|
810
1034
|
path: operation.path,
|
|
1035
|
+
reason: operation.reason,
|
|
811
1036
|
status: 'failed',
|
|
812
1037
|
type: operation.type,
|
|
813
1038
|
};
|
|
@@ -847,13 +1072,15 @@ export function createCurateTool(workingDirectory) {
|
|
|
847
1072
|
|
|
848
1073
|
**Operations:**
|
|
849
1074
|
1. **ADD** - Create new titled context file in domain/topic/subtopic
|
|
850
|
-
- Requires: path, title, content
|
|
1075
|
+
- Requires: path, title, content, confidence, impact, reason
|
|
851
1076
|
- Relations must be in the format of "domain/topic/title.md" or "domain/topic/subtopic/title.md"
|
|
852
1077
|
- Example with Raw Concept + Narrative:
|
|
853
1078
|
{
|
|
854
1079
|
type: "ADD",
|
|
855
1080
|
path: "structure/caching",
|
|
856
1081
|
title: "Redis User Permissions",
|
|
1082
|
+
confidence: "high",
|
|
1083
|
+
impact: "medium",
|
|
857
1084
|
content: {
|
|
858
1085
|
rawConcept: {
|
|
859
1086
|
task: "Introduce Redis cache for getUserPermissions(userId)",
|
|
@@ -869,26 +1096,46 @@ export function createCurateTool(workingDirectory) {
|
|
|
869
1096
|
},
|
|
870
1097
|
relations: ["structure/api-endpoints/validation.md", "structure/api-endpoints/error-handling/retry-logic.md"]
|
|
871
1098
|
},
|
|
872
|
-
reason: "
|
|
1099
|
+
reason: "Introduced after team discussion in PR #42: chose Redis over in-process cache to share state across replicas. The 5-minute staleness was a deliberate trade-off, not an oversight — future agents should not 'fix' it."
|
|
873
1100
|
}
|
|
874
1101
|
- Creates: structure/caching/redis_user_permissions.md
|
|
875
1102
|
|
|
876
1103
|
2. **UPDATE** - Modify existing titled context file (full replacement)
|
|
877
|
-
- Requires: path, title, content, reason
|
|
1104
|
+
- Requires: path, title, content, confidence, impact, reason
|
|
878
1105
|
- Relations must be in the format of "domain/topic/title.md" or "domain/topic/subtopic/title.md"
|
|
879
1106
|
- Supports same content structure as ADD
|
|
1107
|
+
- reason example: \`"Token expiry was changed from 1h to 15min in the security audit (Jira SEC-204). Updated to reflect the new default and the reasoning: shorter TTL reduces blast radius of leaked tokens."\`
|
|
880
1108
|
|
|
881
1109
|
3. **MERGE** - Combine source file into target file, delete source
|
|
882
|
-
- Requires: path (source), title (source file), mergeTarget (destination path), mergeTargetTitle (destination file), reason
|
|
883
|
-
- Example: { type: "MERGE", path: "code_style/old_topic", title: "Old Guide", mergeTarget: "code_style/new_topic", mergeTargetTitle: "New Guide", reason: "
|
|
1110
|
+
- Requires: path (source), title (source file), mergeTarget (destination path), mergeTargetTitle (destination file), confidence, impact, reason
|
|
1111
|
+
- Example: { type: "MERGE", path: "code_style/old_topic", title: "Old Guide", mergeTarget: "code_style/new_topic", mergeTargetTitle: "New Guide", confidence: "high", impact: "medium", reason: "Both files cover the same conventions; merging keeps a single source of truth and avoids contradictions." }
|
|
884
1112
|
- Raw concepts and narratives are intelligently merged
|
|
885
1113
|
|
|
886
1114
|
4. **DELETE** - Remove specific file or entire folder
|
|
887
|
-
- Requires: path, title (optional), reason
|
|
1115
|
+
- Requires: path, title (optional), confidence, impact, reason
|
|
888
1116
|
- With title: deletes specific file; without title: deletes entire folder
|
|
1117
|
+
- Example: { type: "DELETE", path: "auth/legacy", title: "Session Token Flow", confidence: "high", impact: "high", reason: "Session-based auth was fully replaced by JWT in v2.0 (PR #88). Keeping this would mislead future agents into thinking sessions are still in use." }
|
|
1118
|
+
|
|
1119
|
+
**Review Metadata (per operation — always provide these):**
|
|
1120
|
+
- **confidence**: How confident you are in the accuracy/completeness of this knowledge.
|
|
1121
|
+
- \`"high"\`: You have direct evidence from the source material, codebase, or conversation.
|
|
1122
|
+
- \`"low"\`: The information is inferred, partially known, or uncertain.
|
|
1123
|
+
- **impact**: The scope of this knowledge change.
|
|
1124
|
+
- \`"high"\`: A deletion, or a major architectural/structural change.
|
|
1125
|
+
- \`"medium"\`: A significant update to existing knowledge.
|
|
1126
|
+
- \`"low"\`: A new addition or minor update.
|
|
1127
|
+
- **reason**: The human-readable motivation for this curation. This is the most important review field — a human reviewer will read it in the web inbox to decide whether to approve, edit, or reject the change.
|
|
1128
|
+
- **Capture the WHY, not the what.** The what is already encoded in type, path, title, and content. The reason should answer: *What triggered this? What decision was made? What context would be lost without this knowledge?*
|
|
1129
|
+
- **Write for a future human or agent reader**, not for yourself in the moment. Ask: "If someone reads this 6 months from now with no context, will they understand why this knowledge exists and why it should not be changed?"
|
|
1130
|
+
- **Include trade-offs and intent.** If a decision was deliberate (a performance trade-off, a rejected alternative, a known limitation), say so explicitly — this prevents future agents from "fixing" something that was intentional.
|
|
1131
|
+
- Good: \`"Decided in PR #42 to use Redis over in-process cache to share state across replicas. The 5-min staleness is a deliberate trade-off — do not optimize it away."\`
|
|
1132
|
+
- Good: \`"Session auth was fully removed in v2.0; keeping this doc would mislead future agents into thinking it's still active."\`
|
|
1133
|
+
- Bad: \`"Updating caching docs"\` — describes the operation, not the motivation
|
|
1134
|
+
- Bad: \`"New pattern"\` — vague, gives a reviewer nothing to evaluate
|
|
1135
|
+
- Low-confidence or DELETE operations are automatically flagged for human review in the web inbox after \`brv push\`.
|
|
889
1136
|
|
|
890
1137
|
**CRITICAL - Path vs Title separation:**
|
|
891
|
-
- "path" = folder location only (domain/topic or domain/topic/subtopic) - NEVER include file extension suffixes
|
|
1138
|
+
- "path" = folder location only (domain/topic or domain/topic/subtopic) - NEVER include file extension suffixes
|
|
892
1139
|
- "title" = the context name (becomes {title}.md file automatically)
|
|
893
1140
|
- The system auto-generates the .md file from title - DO NOT put .md or _md anywhere in path
|
|
894
1141
|
|