coding-os 0.3.2__py3-none-any.whl
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.
- adapters/claude/README.md +6 -0
- adapters/claude/_install_helpers/extract_stacks.py +43 -0
- adapters/claude/_install_helpers/update_mcp_json.py +75 -0
- adapters/claude/adapter.yaml +164 -0
- adapters/claude/hooks/README.md +40 -0
- adapters/claude/hooks/agent_memory_sync.py +131 -0
- adapters/claude/hooks/ensure-agent-memory-link.sh +36 -0
- adapters/claude/hooks/sync-agent-memory.sh +21 -0
- adapters/claude/install.sh +86 -0
- adapters/claude/sdk_dispatcher.py +871 -0
- adapters/claude/settings.local.template.json +31 -0
- adapters/claude/settings.template.json +808 -0
- adapters/claude/update_mcp_json.py +85 -0
- adapters/codex/adapter.yaml +254 -0
- adapters/codex/chat_provider.py +230 -0
- adapters/codex/commands/formula-f1.md +129 -0
- adapters/codex/commands/formula-f10.md +100 -0
- adapters/codex/commands/formula-f11.md +123 -0
- adapters/codex/commands/formula-f2.md +139 -0
- adapters/codex/commands/formula-f3.md +127 -0
- adapters/codex/commands/formula-f4.md +101 -0
- adapters/codex/commands/formula-f5.md +135 -0
- adapters/codex/commands/formula-f6.md +147 -0
- adapters/codex/commands/formula-f7.md +111 -0
- adapters/codex/commands/formula-f8.md +133 -0
- adapters/codex/commands/formula-f9.md +112 -0
- adapters/codex/enable_codex_hooks.py +94 -0
- adapters/codex/ensure_codex_mcp.py +124 -0
- adapters/codex/hooks/codex-merge-hook-output.py +72 -0
- adapters/codex/hooks/codex-normalize-edit.py +96 -0
- adapters/codex/hooks/codex-postedit-dispatch.sh +75 -0
- adapters/codex/hooks/codex-posttool-dispatch.sh +70 -0
- adapters/codex/hooks/codex-preedit-dispatch.sh +83 -0
- adapters/codex/hooks/codex-pretool-dispatch.sh +82 -0
- adapters/codex/hooks/codex-sessionend-dispatch.sh +20 -0
- adapters/codex/hooks/codex-sessionstart-dispatch.sh +68 -0
- adapters/codex/hooks/codex-stop-dispatch.sh +73 -0
- adapters/codex/hooks/codex-userpromptsubmit-dispatch.sh +74 -0
- adapters/codex/hooks.template.json +208 -0
- adapters/codex/install.sh +83 -0
- adapters/codex/sdk_dispatcher.py +449 -0
- board_os/__init__.py +39 -0
- board_os/_agent_runtime.py +256 -0
- board_os/config.py +421 -0
- board_os/git_coherence.py +107 -0
- board_os/hub_adapter_manifest.py +140 -0
- board_os/mcp_tools.py +3228 -0
- board_os/migration.py +166 -0
- board_os/parser.py +317 -0
- board_os/presence.py +156 -0
- board_os/sync.py +320 -0
- board_os/transition_gates.py +224 -0
- board_os/transition_gates_cli.py +272 -0
- board_os/transition_gates_validator.py +551 -0
- board_os/verify_suites.py +126 -0
- board_os/verify_suites_cli.py +328 -0
- board_os/workflow.py +967 -0
- cli/__init__.py +0 -0
- cli/_data_types.py +248 -0
- cli/_init_helpers.py +587 -0
- cli/_resources.py +100 -0
- cli/adapter_registry.py +239 -0
- cli/add_stack.py +315 -0
- cli/aggregator.py +438 -0
- cli/board_commands.py +1218 -0
- cli/brain_commands.py +255 -0
- cli/cognition.py +345 -0
- cli/config_composer.py +349 -0
- cli/core_version.py +41 -0
- cli/cron_commands.py +278 -0
- cli/db_reset.py +298 -0
- cli/doc_commands.py +111 -0
- cli/doctor.py +2953 -0
- cli/doctor_board.py +365 -0
- cli/doctor_extras.py +1121 -0
- cli/doctor_graph.py +608 -0
- cli/doctor_tokens.py +254 -0
- cli/graph_commands.py +1265 -0
- cli/hook_renderer.py +393 -0
- cli/hub_commands.py +580 -0
- cli/list_adapters.py +79 -0
- cli/list_stacks.py +105 -0
- cli/logs_commands.py +89 -0
- cli/main.py +3070 -0
- cli/materialize_file.py +65 -0
- cli/mcp_start.py +153 -0
- cli/module_commands.py +513 -0
- cli/pr_commands.py +2024 -0
- cli/preset_commands.py +126 -0
- cli/preset_registry.py +171 -0
- cli/project_overrides.py +119 -0
- cli/registry.py +365 -0
- cli/remove_stack.py +492 -0
- cli/renderer.py +620 -0
- cli/setup.py +464 -0
- cli/skill_commands.py +689 -0
- cli/skill_registry.py +235 -0
- cli/skills_list.py +332 -0
- cli/stack_lint.py +351 -0
- cli/stack_registry.py +688 -0
- cli/subsystems.py +335 -0
- cli/sync_all.py +310 -0
- cli/tail_command.py +410 -0
- cli/update.py +608 -0
- cli/verify_since_edit.py +439 -0
- coding_os-0.3.2.dist-info/METADATA +508 -0
- coding_os-0.3.2.dist-info/RECORD +1304 -0
- coding_os-0.3.2.dist-info/WHEEL +5 -0
- coding_os-0.3.2.dist-info/entry_points.txt +5 -0
- coding_os-0.3.2.dist-info/licenses/LICENSE +201 -0
- coding_os-0.3.2.dist-info/top_level.txt +10 -0
- core/__init__.py +0 -0
- core/board_os/__init__.py +39 -0
- core/board_os/_agent_runtime.py +256 -0
- core/board_os/config.py +421 -0
- core/board_os/git_coherence.py +107 -0
- core/board_os/hub_adapter_manifest.py +140 -0
- core/board_os/mcp_tools.py +3228 -0
- core/board_os/migration.py +166 -0
- core/board_os/parser.py +317 -0
- core/board_os/presence.py +156 -0
- core/board_os/sync.py +320 -0
- core/board_os/transition-gates.yaml +176 -0
- core/board_os/transition_gates.py +224 -0
- core/board_os/transition_gates_cli.py +272 -0
- core/board_os/transition_gates_validator.py +551 -0
- core/board_os/verify-suites.yaml +113 -0
- core/board_os/verify_suites.py +126 -0
- core/board_os/verify_suites_cli.py +328 -0
- core/board_os/workflow.py +967 -0
- core/commands/board.md +27 -0
- core/commands/classify.md +23 -0
- core/commands/compose.md +23 -0
- core/commands/daily.md +31 -0
- core/commands/diagnose.md +7 -0
- core/commands/memory-search.md +23 -0
- core/commands/new-project.md +33 -0
- core/commands/retro.md +38 -0
- core/commands/review.md +14 -0
- core/commands/task.md +17 -0
- core/commands/verify.md +34 -0
- core/docs/thinking_os-final-edition.md +1449 -0
- core/doctor-config.yaml +74 -0
- core/graph_os/__init__.py +29 -0
- core/graph_os/backend.py +233 -0
- core/graph_os/backends/__init__.py +13 -0
- core/graph_os/backends/sqlite_backend.py +1053 -0
- core/graph_os/bench/__init__.py +17 -0
- core/graph_os/bench/fixtures.py +56 -0
- core/graph_os/bench/harness.py +95 -0
- core/graph_os/bench/persian_precision.py +142 -0
- core/graph_os/bench/scale_500k.py +120 -0
- core/graph_os/bench/token_cost.py +170 -0
- core/graph_os/bench/viewer_fps.py +110 -0
- core/graph_os/communities.py +410 -0
- core/graph_os/enterprise.py +218 -0
- core/graph_os/entry_points.py +226 -0
- core/graph_os/extractors/__init__.py +25 -0
- core/graph_os/extractors/code_generic.py +914 -0
- core/graph_os/extractors/code_go.py +1422 -0
- core/graph_os/extractors/code_json.py +340 -0
- core/graph_os/extractors/code_php.py +979 -0
- core/graph_os/extractors/code_python.py +1454 -0
- core/graph_os/extractors/code_shell.py +538 -0
- core/graph_os/extractors/code_toml.py +302 -0
- core/graph_os/extractors/code_ts.py +1665 -0
- core/graph_os/extractors/code_yaml.py +394 -0
- core/graph_os/extractors/contracts.py +1592 -0
- core/graph_os/extractors/md_links.py +890 -0
- core/graph_os/extractors/task_deps.py +345 -0
- core/graph_os/groups/__init__.py +22 -0
- core/graph_os/groups/cross_repo.py +156 -0
- core/graph_os/groups/manifest.py +141 -0
- core/graph_os/ingest/__init__.py +19 -0
- core/graph_os/ingest/base.py +306 -0
- core/graph_os/ingest/github.py +112 -0
- core/graph_os/ingest/zip.py +95 -0
- core/graph_os/toolchain.py +393 -0
- core/graph_os/tools/__init__.py +9 -0
- core/graph_os/tools/graph.py +5573 -0
- core/graph_os/tools/reindex_dispatch.py +730 -0
- core/graph_os/tree_sitter_overlay.py +235 -0
- core/graph_os/types.py +252 -0
- core/graph_os/vec_index.py +277 -0
- core/graph_os/viewer/__init__.py +12 -0
- core/graph_os/viewer/exporter.py +93 -0
- core/graph_os/viewer/template.py +189 -0
- core/hooks/_helpers/_paths.py +40 -0
- core/hooks/_helpers/advance_role.py +72 -0
- core/hooks/_helpers/auto_compose.py +228 -0
- core/hooks/_helpers/auto_validate_lessons.py +55 -0
- core/hooks/_helpers/branch_guard_check.py +796 -0
- core/hooks/_helpers/check_commit_message.py +108 -0
- core/hooks/_helpers/check_dangerous_rm.py +80 -0
- core/hooks/_helpers/check_git_bypass.py +154 -0
- core/hooks/_helpers/check_git_destructive.py +77 -0
- core/hooks/_helpers/check_settings_write.py +97 -0
- core/hooks/_helpers/consume_override.py +51 -0
- core/hooks/_helpers/context_budget.py +77 -0
- core/hooks/_helpers/cos_say_json.py +103 -0
- core/hooks/_helpers/destructive_edit_check.py +163 -0
- core/hooks/_helpers/detect_status_transition.py +82 -0
- core/hooks/_helpers/digest_regen.py +56 -0
- core/hooks/_helpers/doc_sync_check.py +498 -0
- core/hooks/_helpers/drain_embedding_outbox.py +52 -0
- core/hooks/_helpers/extract_additional_context.py +51 -0
- core/hooks/_helpers/extract_commit_msg_arg.py +74 -0
- core/hooks/_helpers/git_command_parse.py +424 -0
- core/hooks/_helpers/git_settings_fields.py +47 -0
- core/hooks/_helpers/graph_context_match.py +37 -0
- core/hooks/_helpers/graph_marker_check.py +70 -0
- core/hooks/_helpers/jit_recall.py +56 -0
- core/hooks/_helpers/json_field.py +41 -0
- core/hooks/_helpers/narrative_signal.py +59 -0
- core/hooks/_helpers/observation_count.py +31 -0
- core/hooks/_helpers/pre_commit_batch.py +177 -0
- core/hooks/_helpers/pre_commit_fake_input.py +42 -0
- core/hooks/_helpers/presence_gc.py +102 -0
- core/hooks/_helpers/presence_write.py +167 -0
- core/hooks/_helpers/recover_indirect.py +35 -0
- core/hooks/_helpers/routing_evolution.py +104 -0
- core/hooks/_helpers/session_recap.py +72 -0
- core/hooks/_helpers/skill_primer.py +229 -0
- core/hooks/_helpers/task_sync.py +59 -0
- core/hooks/_helpers/tool_failure_capture.py +147 -0
- core/hooks/_helpers/trajectory_autosnap.py +278 -0
- core/hooks/_helpers/trajectory_startup.py +62 -0
- core/hooks/_helpers/turn_summary.py +82 -0
- core/hooks/_helpers/validate_task_frontmatter.py +98 -0
- core/hooks/_helpers/wip_limit_check.py +103 -0
- core/hooks/_helpers/wip_lines.py +53 -0
- core/hooks/_helpers/work_log_append.py +89 -0
- core/hooks/_helpers/wrap_dispatch_output.py +82 -0
- core/hooks/advance-role.sh +48 -0
- core/hooks/agent-presence.sh +179 -0
- core/hooks/auto-brain-decay.sh +184 -0
- core/hooks/auto-compose-roles.sh +83 -0
- core/hooks/auto-graph-reconcile-shell.sh +119 -0
- core/hooks/auto-regen-doc-index.sh +120 -0
- core/hooks/auto-reindex-docs.sh +130 -0
- core/hooks/auto-task-sync.sh +56 -0
- core/hooks/auto-trace-rotate.sh +88 -0
- core/hooks/block-bad-patterns.sh +212 -0
- core/hooks/block-dangerous-commands.sh +182 -0
- core/hooks/block-hardcoded-literals.sh +90 -0
- core/hooks/block-migration-conflict.sh +114 -0
- core/hooks/block-protected-files.sh +129 -0
- core/hooks/block-secrets.sh +185 -0
- core/hooks/block-shared-tree-edit.sh +75 -0
- core/hooks/block-uv-heredoc.sh +78 -0
- core/hooks/branch-guard.sh +122 -0
- core/hooks/capture-observation.sh +76 -0
- core/hooks/capture-tool-failure.sh +24 -0
- core/hooks/capture-work-log.sh +89 -0
- core/hooks/check-agents-md-refs.sh +75 -0
- core/hooks/check-agents-md-size.sh +49 -0
- core/hooks/check-capture-worked.sh +148 -0
- core/hooks/check-doc-size.sh +61 -0
- core/hooks/check-mcp-extras.sh +92 -0
- core/hooks/check-state.sh +87 -0
- core/hooks/classify-task-mode.sh +103 -0
- core/hooks/cos-env.sh +1301 -0
- core/hooks/drain-embedding-outbox.sh +24 -0
- core/hooks/enforce-anti-ambiguity.sh +74 -0
- core/hooks/enforce-commit-message.sh +73 -0
- core/hooks/enforce-doc-anchor.sh +224 -0
- core/hooks/enforce-doc-sync.sh +206 -0
- core/hooks/enforce-graph-context.sh +89 -0
- core/hooks/enforce-graph-first-read.sh +94 -0
- core/hooks/enforce-memory-check.sh +128 -0
- core/hooks/enforce-rename-plan.sh +78 -0
- core/hooks/enforce-scaffold-boundary.sh +68 -0
- core/hooks/enforce-skill.sh +125 -0
- core/hooks/enforce-task-body.sh +52 -0
- core/hooks/enforce-task-start.sh +81 -0
- core/hooks/enforce-task-transition.sh +75 -0
- core/hooks/enforce-template.sh +143 -0
- core/hooks/enforce-verify.sh +112 -0
- core/hooks/enforce-wip-limit.sh +43 -0
- core/hooks/enforce-zoom.sh +69 -0
- core/hooks/ensure-hub-up.sh +67 -0
- core/hooks/inject-mcp-caller-session.sh +70 -0
- core/hooks/jit-recall.sh +65 -0
- core/hooks/link-commit-to-task.sh +143 -0
- core/hooks/lint-task.sh +40 -0
- core/hooks/nudge-docs-first.sh +71 -0
- core/hooks/nudge-git-mode.sh +29 -0
- core/hooks/nudge-graph-os.sh +118 -0
- core/hooks/nudge-learn-narrative.sh +36 -0
- core/hooks/nudge-model-routing.sh +32 -0
- core/hooks/nudge-reentry.sh +101 -0
- core/hooks/nudge-reuse-first.sh +68 -0
- core/hooks/nudge-task-discovery.sh +81 -0
- core/hooks/nudge-thinking-os.sh +109 -0
- core/hooks/pr-reap.sh +23 -0
- core/hooks/reclaim-sweep.sh +58 -0
- core/hooks/record-verify-auto.sh +77 -0
- core/hooks/record-verify.sh +74 -0
- core/hooks/regen-reminder.sh +104 -0
- core/hooks/registry.yaml +1262 -0
- core/hooks/remind-daily.sh +27 -0
- core/hooks/remind-dogfood.sh +70 -0
- core/hooks/remind-learn-validate.sh +94 -0
- core/hooks/rules-primer.sh +50 -0
- core/hooks/search-enforce-inventory.sh +108 -0
- core/hooks/search-verify-remaining.sh +132 -0
- core/hooks/session-context.sh +729 -0
- core/hooks/session-end.sh +145 -0
- core/hooks/session-skill-primer.sh +43 -0
- core/hooks/snapshot-transcript.sh +56 -0
- core/hooks/sync-task-current.sh +85 -0
- core/hooks/test-first-reminder.sh +120 -0
- core/hooks/test-governor.sh +173 -0
- core/hooks/thinking_os-gate.sh +52 -0
- core/hooks/track-backtrack.sh +35 -0
- core/hooks/track-discovery.sh +121 -0
- core/hooks/track-skill.sh +53 -0
- core/hooks/validate-task-frontmatter.sh +49 -0
- core/hooks/verify-rename-callers.sh +119 -0
- core/hooks/warn-abandoned-task.sh +99 -0
- core/hooks/warn-destructive-edit.sh +64 -0
- core/hooks/warn-diff-size.sh +43 -0
- core/hooks/warn-graph-empty.sh +81 -0
- core/hooks/warn-mcp-down.sh +190 -0
- core/hooks/write-state.sh +55 -0
- core/logging_os/__init__.py +33 -0
- core/logging_os/api.py +127 -0
- core/logging_os/bridge.py +80 -0
- core/logging_os/config.py +172 -0
- core/logging_os/fingerprint.py +25 -0
- core/logging_os/redact.py +53 -0
- core/logging_os/render.py +83 -0
- core/logging_os/sinks.py +164 -0
- core/rules/anti-overengineering.md +44 -0
- core/rules/api-contract-discipline.md +41 -0
- core/rules/dimension-registry.md +155 -0
- core/rules/git-workflow.md +57 -0
- core/rules/memory.md +46 -0
- core/rules/model-routing.md +22 -0
- core/rules/skill-enforcement.md +75 -0
- core/rules/test-discipline.md +38 -0
- core/rules/thinking_os.md +48 -0
- core/rules/transparency-banner.md +37 -0
- core/runtime_paths.yaml +36 -0
- core/scaffold_manifest.json +14430 -0
- core/scheduled/__init__.py +0 -0
- core/scheduled/_activity.py +126 -0
- core/scheduled/_state.py +113 -0
- core/scheduled/config.py +86 -0
- core/scheduled/dep_reconcile.py +135 -0
- core/scheduled/error_sweep.py +137 -0
- core/scheduled/nightly.py +930 -0
- core/scheduled/responsive_extract.py +65 -0
- core/schemas/adapter.schema.json +269 -0
- core/schemas/preset.schema.json +50 -0
- core/schemas/skill.schema.json +81 -0
- core/schemas/stack.schema.json +404 -0
- core/scripts/_lib.sh +9 -0
- core/scripts/docs-lint.sh +228 -0
- core/scripts/docs-nav-fix.sh +133 -0
- core/scripts/docs-staleness-check.sh +154 -0
- core/scripts/install-adapter.sh +266 -0
- core/scripts/link-stack-skills.sh +52 -0
- core/scripts/log-latest.sh +106 -0
- core/scripts/log-search.sh +89 -0
- core/scripts/log-write.sh +134 -0
- core/scripts/ref-resolve.sh +71 -0
- core/skills/a11y/SKILL.md +305 -0
- core/skills/a11y/assets/a11y-checklist.md +137 -0
- core/skills/a11y/references/aria-and-focus.md +247 -0
- core/skills/a11y/references/rn-accessibility.md +343 -0
- core/skills/a11y/references/screen-reader-testing.md +190 -0
- core/skills/agent-memory/SKILL.md +191 -0
- core/skills/agent-memory/assets/memory-checklist.md +21 -0
- core/skills/agent-memory/references/memory-recipes.md +57 -0
- core/skills/api-design/SKILL.md +232 -0
- core/skills/api-design/assets/api-design-checklist.md +110 -0
- core/skills/api-design/references/error-envelope.md +381 -0
- core/skills/api-design/references/idempotency-pagination.md +312 -0
- core/skills/api-design/references/rest-contracts.md +426 -0
- core/skills/auth-patterns/SKILL.md +352 -0
- core/skills/auth-patterns/assets/auth-checklist.md +118 -0
- core/skills/auth-patterns/references/jwt-and-service-tokens.md +343 -0
- core/skills/auth-patterns/references/passkeys-2fa.md +289 -0
- core/skills/auth-patterns/references/sessions-vs-jwt.md +230 -0
- core/skills/auth-patterns/scripts/cookie-flag-check.py +146 -0
- core/skills/backend-fundamentals/SKILL.md +238 -0
- core/skills/backend-fundamentals/assets/backend-checklist.md +27 -0
- core/skills/backend-fundamentals/references/backend-patterns.md +56 -0
- core/skills/backend-fundamentals/scripts/check_layering.py +83 -0
- core/skills/clean-code/SKILL.md +642 -0
- core/skills/clean-code/scripts/audit-fail-closed.py +167 -0
- core/skills/codebase-explorer/SKILL.md +89 -0
- core/skills/codebase-explorer/assets/reading-checklist.md +24 -0
- core/skills/codebase-explorer/references/reading-strategies.md +52 -0
- core/skills/codebase-explorer/scripts/outline.py +99 -0
- core/skills/db-design/SKILL.md +327 -0
- core/skills/db-design/assets/migration-template.sql +49 -0
- core/skills/db-design/references/migration-discipline.md +290 -0
- core/skills/db-design/references/postgres-patterns.md +340 -0
- core/skills/db-design/scripts/migration-safety.sh +150 -0
- core/skills/deployment-cicd/SKILL.md +260 -0
- core/skills/deployment-cicd/assets/deploy-checklist.md +26 -0
- core/skills/deployment-cicd/references/pipeline-and-release.md +54 -0
- core/skills/deployment-cicd/scripts/lint_workflow.py +79 -0
- core/skills/docker/SKILL.md +114 -0
- core/skills/docker/assets/dockerfile-checklist.md +31 -0
- core/skills/docker/references/compose-patterns.md +66 -0
- core/skills/docker/references/dockerfile-optimization.md +64 -0
- core/skills/docker/scripts/lint_dockerfile.sh +48 -0
- core/skills/docker/versions.json +16 -0
- core/skills/end-to-end-testing/SKILL.md +101 -0
- core/skills/end-to-end-testing/assets/e2e-checklist.md +23 -0
- core/skills/end-to-end-testing/references/maestro.md +63 -0
- core/skills/end-to-end-testing/references/playwright.md +68 -0
- core/skills/end-to-end-testing/scripts/lint_e2e.py +88 -0
- core/skills/end-to-end-testing/versions.json +16 -0
- core/skills/frontend-design/SKILL.md +76 -0
- core/skills/frontend-design/assets/design-checklist.md +29 -0
- core/skills/frontend-design/references/design-principles.md +65 -0
- core/skills/frontend-design/scripts/check_contrast.py +89 -0
- core/skills/frontend-fundamentals/SKILL.md +213 -0
- core/skills/frontend-fundamentals/assets/frontend-checklist.md +25 -0
- core/skills/frontend-fundamentals/references/rendering-and-state.md +66 -0
- core/skills/frontend-fundamentals/scripts/check_frontend.py +86 -0
- core/skills/graph-explorer/SKILL.md +215 -0
- core/skills/graph-explorer/scripts/explain-impact.sh +64 -0
- core/skills/graphql/SKILL.md +187 -0
- core/skills/grpc-microservices/SKILL.md +174 -0
- core/skills/hexagonal-architecture/SKILL.md +199 -0
- core/skills/hexagonal-architecture/assets/folder-scaffold.md +233 -0
- core/skills/hexagonal-architecture/references/anti-patterns.md +129 -0
- core/skills/hexagonal-architecture/references/go-fiber-layout.md +429 -0
- core/skills/hexagonal-architecture/references/python-fastapi-layout.md +453 -0
- core/skills/hexagonal-architecture/references/react-native-layout.md +428 -0
- core/skills/i18n/SKILL.md +126 -0
- core/skills/incident-response/SKILL.md +225 -0
- core/skills/incident-response/assets/incident-checklist.md +29 -0
- core/skills/incident-response/references/severity-and-runbook.md +53 -0
- core/skills/incident-response/scripts/classify_severity.py +86 -0
- core/skills/linux-sysadmin/SKILL.md +115 -0
- core/skills/linux-sysadmin/assets/hardening-checklist.md +29 -0
- core/skills/linux-sysadmin/references/ssh-hardening.md +62 -0
- core/skills/linux-sysadmin/references/systemd-and-services.md +77 -0
- core/skills/linux-sysadmin/scripts/triage.sh +50 -0
- core/skills/linux-sysadmin/versions.json +17 -0
- core/skills/llm-patterns/SKILL.md +410 -0
- core/skills/llm-patterns/assets/llm-feature-checklist.md +26 -0
- core/skills/llm-patterns/references/rag-and-evals.md +59 -0
- core/skills/llm-patterns/scripts/estimate_tokens.py +75 -0
- core/skills/messaging-queues/SKILL.md +142 -0
- core/skills/mobile-fundamentals/SKILL.md +406 -0
- core/skills/mobile-fundamentals/assets/mobile-launch-checklist.md +130 -0
- core/skills/mobile-fundamentals/references/navigation-and-deep-links.md +337 -0
- core/skills/mobile-fundamentals/references/offline-sync.md +339 -0
- core/skills/node-backend/SKILL.md +114 -0
- core/skills/node-backend/assets/node-checklist.md +25 -0
- core/skills/node-backend/references/async-and-errors.md +67 -0
- core/skills/node-backend/references/event-loop.md +65 -0
- core/skills/node-backend/scripts/check_package.py +78 -0
- core/skills/node-backend/versions.json +17 -0
- core/skills/observability/SKILL.md +289 -0
- core/skills/observability/assets/observability-checklist.md +27 -0
- core/skills/observability/references/instrumentation.md +57 -0
- core/skills/observability/scripts/lint_logging.py +77 -0
- core/skills/payments/SKILL.md +102 -0
- core/skills/performance/SKILL.md +305 -0
- core/skills/performance/assets/perf-checklist.md +143 -0
- core/skills/performance/references/mobile-performance.md +249 -0
- core/skills/performance/references/web-vitals.md +209 -0
- core/skills/php/SKILL.md +116 -0
- core/skills/php/assets/php-checklist.md +26 -0
- core/skills/php/references/modern-php.md +64 -0
- core/skills/php/references/security.md +72 -0
- core/skills/php/scripts/scan_php_smells.py +99 -0
- core/skills/php/versions.json +9 -0
- core/skills/pr-mode-driver/SKILL.md +65 -0
- core/skills/realtime-websockets/SKILL.md +152 -0
- core/skills/redis/SKILL.md +105 -0
- core/skills/redis/assets/redis-checklist.md +27 -0
- core/skills/redis/references/operations.md +66 -0
- core/skills/redis/references/patterns.md +62 -0
- core/skills/redis/scripts/analyze_info.py +101 -0
- core/skills/redis/versions.json +9 -0
- core/skills/search/SKILL.md +91 -0
- core/skills/search/references/grep.md +76 -0
- core/skills/search/scripts/verify-count.sh +73 -0
- core/skills/search-infra/SKILL.md +109 -0
- core/skills/security-mobile/SKILL.md +394 -0
- core/skills/security-mobile/assets/mobile-security-checklist.md +117 -0
- core/skills/security-mobile/references/masvs-l1-checklist.md +127 -0
- core/skills/security-web/SKILL.md +217 -0
- core/skills/security-web/assets/security-web-checklist.md +167 -0
- core/skills/security-web/references/owasp-top-10.md +551 -0
- core/skills/security-web/references/supply-chain.md +179 -0
- core/skills/security-web/scripts/csp-check.sh +153 -0
- core/skills/shell-scripting/SKILL.md +128 -0
- core/skills/shell-scripting/assets/script-checklist.md +33 -0
- core/skills/shell-scripting/references/argument-parsing.md +84 -0
- core/skills/shell-scripting/references/bash-robustness.md +78 -0
- core/skills/shell-scripting/scripts/lint_script.sh +53 -0
- core/skills/shell-scripting/scripts/new_script.py +131 -0
- core/skills/shell-scripting/versions.json +23 -0
- core/skills/sql-authoring/SKILL.md +104 -0
- core/skills/sql-authoring/assets/query-review-checklist.md +26 -0
- core/skills/sql-authoring/references/query-patterns.md +89 -0
- core/skills/sql-authoring/references/reading-explain.md +52 -0
- core/skills/sql-authoring/scripts/analyze_plan.py +100 -0
- core/skills/sql-authoring/versions.json +17 -0
- core/skills/state-management/SKILL.md +428 -0
- core/skills/state-management/references/tanstack-query-recipes.md +301 -0
- core/skills/state-management/references/zustand-recipes.md +364 -0
- core/skills/supabase/SKILL.md +108 -0
- core/skills/supabase/assets/supabase-checklist.md +26 -0
- core/skills/supabase/references/realtime-and-storage.md +55 -0
- core/skills/supabase/references/rls-and-auth.md +65 -0
- core/skills/supabase/scripts/check_rls.py +88 -0
- core/skills/supabase/versions.json +9 -0
- core/skills/task-driver/SKILL.md +272 -0
- core/skills/task-driver/scripts/task-lint.sh +163 -0
- core/skills/technical-writing/SKILL.md +85 -0
- core/skills/technical-writing/assets/doc-checklist.md +29 -0
- core/skills/technical-writing/references/doc-anatomy.md +53 -0
- core/skills/technical-writing/references/writing-craft.md +59 -0
- core/skills/technical-writing/scripts/new_doc.py +81 -0
- core/skills/terraform-k8s/SKILL.md +133 -0
- core/skills/testing-strategy/SKILL.md +266 -0
- core/skills/testing-strategy/assets/test-review-checklist.md +25 -0
- core/skills/testing-strategy/references/test-types.md +52 -0
- core/skills/testing-strategy/scripts/coverage_gate.py +79 -0
- core/skills/thinking_os/SKILL.md +288 -0
- core/skills/thinking_os/scripts/classify.sh +122 -0
- core/skills/typescript/SKILL.md +110 -0
- core/skills/typescript/assets/typescript-checklist.md +24 -0
- core/skills/typescript/references/strictness.md +53 -0
- core/skills/typescript/references/type-system.md +86 -0
- core/skills/typescript/scripts/check_tsconfig.py +92 -0
- core/skills/typescript/versions.json +9 -0
- core/subsystems.yaml +202 -0
- core/thinking_os/__init__.py +1 -0
- core/thinking_os/_agent_markers.py +32 -0
- core/thinking_os/agents/README.md +71 -0
- core/thinking_os/agents/analyst.md +139 -0
- core/thinking_os/agents/architect.md +127 -0
- core/thinking_os/agents/debugger.md +111 -0
- core/thinking_os/agents/deployer.md +112 -0
- core/thinking_os/agents/distiller.md +28 -0
- core/thinking_os/agents/documenter.md +101 -0
- core/thinking_os/agents/implementer.md +135 -0
- core/thinking_os/agents/internal/session_observer.md +39 -0
- core/thinking_os/agents/observer.md +100 -0
- core/thinking_os/agents/onboarder.md +81 -0
- core/thinking_os/agents/refactorer.md +123 -0
- core/thinking_os/agents/repairer.md +47 -0
- core/thinking_os/agents/researcher.md +129 -0
- core/thinking_os/agents/reviewer.md +147 -0
- core/thinking_os/agents/security_auditor.md +133 -0
- core/thinking_os/background.py +405 -0
- core/thinking_os/bootstrap_outcomes.py +200 -0
- core/thinking_os/budget.py +302 -0
- core/thinking_os/capture.py +495 -0
- core/thinking_os/cognition.py +516 -0
- core/thinking_os/cognition_schemas.py +517 -0
- core/thinking_os/compress.py +192 -0
- core/thinking_os/concepts.py +233 -0
- core/thinking_os/dashboard.py +159 -0
- core/thinking_os/database.py +2883 -0
- core/thinking_os/decay.py +393 -0
- core/thinking_os/digest.py +295 -0
- core/thinking_os/dispatcher.py +192 -0
- core/thinking_os/dispatcher_helpers.py +48 -0
- core/thinking_os/dispatchers/__init__.py +3 -0
- core/thinking_os/dispatchers/default.py +47 -0
- core/thinking_os/distill.py +192 -0
- core/thinking_os/doc_indexer.py +905 -0
- core/thinking_os/embeddings.py +943 -0
- core/thinking_os/formula_composer.py +556 -0
- core/thinking_os/gate_marker.py +75 -0
- core/thinking_os/graph.py +296 -0
- core/thinking_os/graph_indexer.py +360 -0
- core/thinking_os/health_check.py +517 -0
- core/thinking_os/impact.py +119 -0
- core/thinking_os/memory_gc.py +356 -0
- core/thinking_os/migrator_embeddings.py +318 -0
- core/thinking_os/precision.py +194 -0
- core/thinking_os/presets/registry.yaml +127 -0
- core/thinking_os/record_outcome.py +399 -0
- core/thinking_os/repair.py +105 -0
- core/thinking_os/retrieval_quality.py +239 -0
- core/thinking_os/roles/analyst.yaml +83 -0
- core/thinking_os/roles/architect.yaml +88 -0
- core/thinking_os/roles/debugger.yaml +71 -0
- core/thinking_os/roles/deployer.yaml +71 -0
- core/thinking_os/roles/documenter.yaml +72 -0
- core/thinking_os/roles/implementer.yaml +83 -0
- core/thinking_os/roles/observer.yaml +70 -0
- core/thinking_os/roles/refactorer.yaml +71 -0
- core/thinking_os/roles/researcher.yaml +72 -0
- core/thinking_os/roles/reviewer.yaml +79 -0
- core/thinking_os/roles/security_auditor.yaml +83 -0
- core/thinking_os/roles_state.py +168 -0
- core/thinking_os/sanitizer.py +320 -0
- core/thinking_os/server.py +3160 -0
- core/thinking_os/session_enrich.py +272 -0
- core/thinking_os/session_observe_worker.py +111 -0
- core/thinking_os/session_startup.py +74 -0
- core/thinking_os/session_summary.py +245 -0
- core/thinking_os/situations/registry.yaml +99 -0
- core/thinking_os/task_analyzer.py +462 -0
- core/thinking_os/task_parser.py +342 -0
- core/thinking_os/task_sync.py +73 -0
- core/thinking_os/tools/__init__.py +6 -0
- core/thinking_os/tools/_shared.py +947 -0
- core/thinking_os/tools/cognition.py +1867 -0
- core/thinking_os/tools/docs.py +770 -0
- core/thinking_os/tools/learning.py +2078 -0
- core/thinking_os/tools/logs.py +79 -0
- core/thinking_os/tools/memory.py +840 -0
- core/thinking_os/tools/metrics.py +200 -0
- core/thinking_os/tools/retrieve.py +415 -0
- core/thinking_os/tools/routing.py +658 -0
- core/thinking_os/tools/tasks.py +449 -0
- core/thinking_os/tools/trajectory.py +181 -0
- core/thinking_os/tracing.py +235 -0
- core/web/__init__.py +5 -0
- core/web/_cache.py +118 -0
- core/web/_deps.py +56 -0
- core/web/_envelope.py +85 -0
- core/web/_project_context.py +140 -0
- core/web/chat_providers.py +108 -0
- core/web/init_jobs.py +216 -0
- core/web/routes/__init__.py +25 -0
- core/web/routes/_bounded_read.py +76 -0
- core/web/routes/board.py +1089 -0
- core/web/routes/cognition.py +1838 -0
- core/web/routes/config.py +635 -0
- core/web/routes/graph.py +513 -0
- core/web/routes/health.py +180 -0
- core/web/routes/hooks.py +288 -0
- core/web/routes/hub.py +1219 -0
- core/web/routes/logs.py +374 -0
- core/web/routes/metrics.py +43 -0
- core/web/routes/observability.py +400 -0
- core/web/routes/patterns.py +227 -0
- core/web/routes/presence.py +609 -0
- core/web/routes/roles.py +446 -0
- core/web/routes/scheduled.py +261 -0
- core/web/routes/search.py +238 -0
- core/web/routes/sessions.py +220 -0
- core/web/routes/settings.py +363 -0
- core/web/routes/stream.py +547 -0
- core/web/security.py +157 -0
- core/web/server.py +283 -0
- graph_os/__init__.py +29 -0
- graph_os/backend.py +233 -0
- graph_os/backends/__init__.py +13 -0
- graph_os/backends/sqlite_backend.py +1053 -0
- graph_os/communities.py +410 -0
- graph_os/enterprise.py +218 -0
- graph_os/entry_points.py +226 -0
- graph_os/extractors/__init__.py +25 -0
- graph_os/extractors/code_generic.py +914 -0
- graph_os/extractors/code_go.py +1422 -0
- graph_os/extractors/code_json.py +340 -0
- graph_os/extractors/code_php.py +979 -0
- graph_os/extractors/code_python.py +1454 -0
- graph_os/extractors/code_shell.py +538 -0
- graph_os/extractors/code_toml.py +302 -0
- graph_os/extractors/code_ts.py +1665 -0
- graph_os/extractors/code_yaml.py +394 -0
- graph_os/extractors/contracts.py +1592 -0
- graph_os/extractors/md_links.py +890 -0
- graph_os/extractors/task_deps.py +345 -0
- graph_os/groups/__init__.py +22 -0
- graph_os/groups/cross_repo.py +156 -0
- graph_os/groups/manifest.py +141 -0
- graph_os/ingest/__init__.py +19 -0
- graph_os/ingest/base.py +306 -0
- graph_os/ingest/github.py +112 -0
- graph_os/ingest/zip.py +95 -0
- graph_os/toolchain.py +393 -0
- graph_os/tools/__init__.py +9 -0
- graph_os/tools/graph.py +5573 -0
- graph_os/tools/reindex_dispatch.py +730 -0
- graph_os/tree_sitter_overlay.py +235 -0
- graph_os/types.py +252 -0
- graph_os/vec_index.py +277 -0
- graph_os/viewer/__init__.py +12 -0
- graph_os/viewer/exporter.py +93 -0
- graph_os/viewer/template.py +189 -0
- scheduled/__init__.py +0 -0
- scheduled/_activity.py +126 -0
- scheduled/_state.py +113 -0
- scheduled/config.py +86 -0
- scheduled/dep_reconcile.py +135 -0
- scheduled/error_sweep.py +137 -0
- scheduled/nightly.py +930 -0
- scheduled/responsive_extract.py +65 -0
- scripts/__init__.py +4 -0
- scripts/_commit_msg_body.sh +31 -0
- scripts/_post_commit_body.sh +49 -0
- scripts/_pre_commit_body.sh +121 -0
- scripts/_prepare_commit_msg_body.sh +53 -0
- scripts/audit_mcp_tools.py +693 -0
- scripts/bench_sdk_dispatcher.py +177 -0
- scripts/capture_golden.py +169 -0
- scripts/check_graph_phantoms.py +75 -0
- scripts/dev/audit_doc_links.py +359 -0
- scripts/dev/audit_scaffold_module_tags.py +107 -0
- scripts/dev/backfill_doc_headers.py +328 -0
- scripts/dev/backfill_nav_lines.py +119 -0
- scripts/dev/fix_nav_placement.py +106 -0
- scripts/dev/inspect_sdk_options.py +45 -0
- scripts/dev/migrate_check_ids.py +170 -0
- scripts/dev/strip_purpose_blocks.py +154 -0
- scripts/dump_openapi.py +66 -0
- scripts/e2e_dispatch_tool.py +195 -0
- scripts/generate_manifest.py +166 -0
- scripts/golden_sections.py +20 -0
- scripts/graph_demo.py +161 -0
- scripts/install-git-hooks.sh +47 -0
- scripts/migrate_embeddings_minilm_to_bge_m3.py +84 -0
- scripts/operational_eval.py +445 -0
- scripts/probe_agent_session_resolver.py +59 -0
- scripts/prune_deleted_path.py +127 -0
- scripts/refactor_agent_dual_mode.py +171 -0
- scripts/refresh_skill_versions.py +302 -0
- scripts/regen_doc_index.py +209 -0
- scripts/regen_doctor_schema.py +62 -0
- scripts/regen_rules.py +94 -0
- scripts/rename_formulas_to_semantic.py +241 -0
- scripts/smoke_db_connections.py +183 -0
- scripts/smoke_doc_header.py +72 -0
- scripts/smoke_graph_e2e.py +374 -0
- scripts/smoke_sdk_dispatch.py +84 -0
- scripts/smoke_uid_resolver.py +164 -0
- scripts/verify_dispatchers.py +244 -0
- scripts/verify_phase_c_e2e.py +436 -0
- templates/__init__.py +6 -0
- templates/_base/Makefile.base +353 -0
- templates/_base/base.yaml +59 -0
- templates/_base/coding-os.yaml.template +39 -0
- templates/_base/dimension-registry.template.md +68 -0
- templates/_base/domain-config.template.json +46 -0
- templates/_base/fragments/anatomy-map.md.tmpl +11 -0
- templates/_base/fragments/context-discipline.md.tmpl +3 -0
- templates/_base/fragments/core-loop.md.tmpl +52 -0
- templates/_base/fragments/engineering-routing.md.tmpl +3 -0
- templates/_base/fragments/header.md.tmpl +6 -0
- templates/_base/fragments/identity.md.tmpl +3 -0
- templates/_base/fragments/principles.md.tmpl +3 -0
- templates/_base/fragments/retrieval-routing.md.tmpl +23 -0
- templates/_base/fragments/session-handoff.md.tmpl +3 -0
- templates/_base/fragments/skills.md.tmpl +3 -0
- templates/_base/fragments/ssot-map.md.tmpl +3 -0
- templates/_base/fragments/stop-conditions.md.tmpl +3 -0
- templates/_base/fragments/subagent-dispatch.md.tmpl +3 -0
- templates/_base/fragments/task-authoring.md.tmpl +69 -0
- templates/_base/fragments/task-logging.md.tmpl +8 -0
- templates/_base/fragments/tool-routing.md.tmpl +9 -0
- templates/_base/fragments/verification-matrix.md.tmpl +12 -0
- templates/_base/lang/dart/analysis_options.yaml +7 -0
- templates/_base/lang/php/phpcs.xml.dist +10 -0
- templates/_base/lang/python/pyproject.toml +19 -0
- templates/_base/lang/rust/clippy.toml +5 -0
- templates/_base/lang/rust/rustfmt.toml +3 -0
- templates/_base/lang/typescript/eslint.config.js +26 -0
- templates/_base/lang/typescript/tsconfig.json +15 -0
- templates/_base/lang/typescript/vitest.config.ts +10 -0
- templates/_base/scaffold/changes.log +1 -0
- templates/_base/scaffold/docs/00-index.md +55 -0
- templates/_base/scaffold/docs/_meta/feature-dependency-tree.md +30 -0
- templates/_base/scaffold/docs/_meta/foundation-map.md +56 -0
- templates/_base/scaffold/docs/_meta/questions.md +8 -0
- templates/_base/scaffold/docs/_meta/roadmap.md +33 -0
- templates/_base/scaffold/docs/api-contracts/00-index.md +58 -0
- templates/_base/scaffold/docs/api-contracts/error-format.md +58 -0
- templates/_base/scaffold/docs/architecture/00-index.md +42 -0
- templates/_base/scaffold/docs/architecture/adr/00-index.md +39 -0
- templates/_base/scaffold/docs/engineering/00-index.md +9 -0
- templates/_base/scaffold/docs/governance/00-index.md +55 -0
- templates/_base/scaffold/docs/governance/_templates/doc-cheat-sheet.md +202 -0
- templates/_base/scaffold/docs/governance/_templates/playbook-template.md +81 -0
- templates/_base/scaffold/docs/governance/_templates/post-mortem-template.md +85 -0
- templates/_base/scaffold/docs/governance/_templates/runbook-template.md +88 -0
- templates/_base/scaffold/docs/governance/_templates/security-review-template.md +111 -0
- templates/_base/scaffold/docs/governance/_templates/task-detail.md +61 -0
- templates/_base/scaffold/docs/governance/agent-workflow.md +101 -0
- templates/_base/scaffold/docs/governance/anatomy-contract.md +150 -0
- templates/_base/scaffold/docs/governance/critical-rules.md +224 -0
- templates/_base/scaffold/docs/governance/decision-records.md +58 -0
- templates/_base/scaffold/docs/governance/docs-first-protocol.md +157 -0
- templates/_base/scaffold/docs/governance/docs-system.md +151 -0
- templates/_base/scaffold/docs/governance/gdpr-compliance.md +66 -0
- templates/_base/scaffold/docs/governance/mcp-tool-inventory.md +112 -0
- templates/_base/scaffold/docs/governance/risk-register.md +26 -0
- templates/_base/scaffold/docs/governance/scaffold-boundary-contract.md +161 -0
- templates/_base/scaffold/docs/governance/task-lifecycle.md +125 -0
- templates/_base/scaffold/docs/governance/wrapper-derivation.md +50 -0
- templates/_base/scaffold/docs/insights/00-index.md +17 -0
- templates/_base/scaffold/docs/ops/00-index.md +59 -0
- templates/_base/scaffold/docs/ops/runbooks/00-index.md +9 -0
- templates/_base/scaffold/docs/playbooks/00-index.md +12 -0
- templates/_base/scaffold/docs/playbooks/research-validation.md +29 -0
- templates/_base/scaffold/docs/playbooks/security-review.md +41 -0
- templates/_base/scaffold/docs/prd/00-index.md +43 -0
- templates/_base/scaffold/docs/prd/01-snapshot-vision.md +56 -0
- templates/_base/scaffold/docs/workflow/workflow-guide.md +138 -0
- templates/_base/scaffold/src/shared/README.md +23 -0
- templates/_base/skill-enforcement.template.md +14 -0
- templates/_base/task-detail.template.md +61 -0
- templates/_presets/ai-saas.yaml +9 -0
- templates/_presets/django-next.yaml +8 -0
- templates/_presets/dotnet-react.yaml +8 -0
- templates/_presets/flutter-baas.yaml +8 -0
- templates/_presets/go-react.yaml +8 -0
- templates/_presets/hexagonal-product.yaml +16 -0
- templates/_presets/jamstack.yaml +8 -0
- templates/_presets/laravel-vue.yaml +8 -0
- templates/_presets/mean.yaml +8 -0
- templates/_presets/mern.yaml +9 -0
- templates/_presets/nest-angular.yaml +8 -0
- templates/_presets/nextjs-fastapi.yaml +10 -0
- templates/_presets/nuxt-fullstack.yaml +9 -0
- templates/_presets/pern.yaml +9 -0
- templates/_presets/rails-react.yaml +8 -0
- templates/_presets/rn-api.yaml +8 -0
- templates/_presets/rust-svelte.yaml +8 -0
- templates/_presets/spring-react.yaml +8 -0
- templates/_presets/t3-style.yaml +9 -0
- templates/_presets/tall.yaml +8 -0
- templates/_presets/wordpress-cms.yaml +8 -0
- templates/angular/rules/frontend.md +19 -0
- templates/angular/scaffold/docs/engineering/accessibility.md +46 -0
- templates/angular/scaffold/docs/engineering/angular-rules.md +35 -0
- templates/angular/scaffold/docs/playbooks/angular-app.md +42 -0
- templates/angular/scaffold/src/frontend/angular.json +52 -0
- templates/angular/scaffold/src/frontend/package.json +28 -0
- templates/angular/scaffold/src/frontend/src/app/app.component.ts +19 -0
- templates/angular/scaffold/src/frontend/src/app/app.config.ts +22 -0
- templates/angular/scaffold/src/frontend/src/app/app.routes.ts +8 -0
- templates/angular/scaffold/src/frontend/src/app/core/global-error-handler.ts +12 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.component.ts +14 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.service.spec.ts +16 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.service.ts +10 -0
- templates/angular/scaffold/src/frontend/src/index.html +11 -0
- templates/angular/scaffold/src/frontend/src/main.ts +9 -0
- templates/angular/scaffold/src/frontend/src/styles.css +14 -0
- templates/angular/scaffold/src/frontend/tsconfig.app.json +8 -0
- templates/angular/scaffold/src/frontend/tsconfig.json +27 -0
- templates/angular/scaffold/src/frontend/tsconfig.spec.json +8 -0
- templates/angular/scaffold-boundary.yaml +27 -0
- templates/angular/skills/angular/SKILL.md +79 -0
- templates/angular/skills/angular/references/anatomy.md +69 -0
- templates/angular/stack.yaml +75 -0
- templates/aspnet-core/rules/backend.md +20 -0
- templates/aspnet-core/scaffold/docs/engineering/aspnet-core-rules.md +36 -0
- templates/aspnet-core/scaffold/docs/playbooks/aspnet-core-service.md +40 -0
- templates/aspnet-core/scaffold/src/backend/Backend.csproj +11 -0
- templates/aspnet-core/scaffold/src/backend/Backend.sln +27 -0
- templates/aspnet-core/scaffold/src/backend/Common/ExceptionHandlingMiddleware.cs +35 -0
- templates/aspnet-core/scaffold/src/backend/Features/Health/HealthEndpoints.cs +10 -0
- templates/aspnet-core/scaffold/src/backend/Features/Health/HealthService.cs +9 -0
- templates/aspnet-core/scaffold/src/backend/Program.cs +22 -0
- templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/Backend.Tests.csproj +22 -0
- templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/HealthServiceTests.cs +15 -0
- templates/aspnet-core/scaffold-boundary.yaml +24 -0
- templates/aspnet-core/skills/aspnet-core/SKILL.md +80 -0
- templates/aspnet-core/skills/aspnet-core/references/anatomy.md +65 -0
- templates/aspnet-core/stack.yaml +68 -0
- templates/astro/rules/frontend.md +20 -0
- templates/astro/scaffold/docs/engineering/astro-rules.md +40 -0
- templates/astro/scaffold/docs/playbooks/astro-app.md +57 -0
- templates/astro/scaffold/docs/playbooks/content-seo.md +37 -0
- templates/astro/scaffold/src/frontend/astro.config.mjs +10 -0
- templates/astro/scaffold/src/frontend/package.json +23 -0
- templates/astro/scaffold/src/frontend/src/components/Greeting.astro +13 -0
- templates/astro/scaffold/src/frontend/src/content/posts/hello.md +10 -0
- templates/astro/scaffold/src/frontend/src/content.config.ts +19 -0
- templates/astro/scaffold/src/frontend/src/lib/problem.test.ts +39 -0
- templates/astro/scaffold/src/frontend/src/lib/problem.ts +30 -0
- templates/astro/scaffold/src/frontend/src/pages/api/health.ts +13 -0
- templates/astro/scaffold/src/frontend/src/pages/index.astro +21 -0
- templates/astro/scaffold/src/frontend/tsconfig.json +9 -0
- templates/astro/scaffold/src/frontend/vitest.config.ts +10 -0
- templates/astro/scaffold-boundary.yaml +28 -0
- templates/astro/skills/astro/SKILL.md +71 -0
- templates/astro/skills/astro/references/anatomy.md +66 -0
- templates/astro/stack.yaml +75 -0
- templates/csharp-plain/scaffold/src/backend/Backend.csproj +12 -0
- templates/csharp-plain/scaffold/src/backend/Program.cs +1 -0
- templates/csharp-plain/scaffold-boundary.yaml +23 -0
- templates/csharp-plain/stack.yaml +50 -0
- templates/django/rules/backend.md +18 -0
- templates/django/scaffold/docs/engineering/anti-ambiguity.md +74 -0
- templates/django/scaffold/docs/engineering/backend-rules.md +133 -0
- templates/django/scaffold/docs/engineering/glossary.md +51 -0
- templates/django/scaffold/docs/engineering/logging-standards.md +107 -0
- templates/django/scaffold/docs/engineering/naming-conventions.md +68 -0
- templates/django/scaffold/docs/engineering/secrets-rotation-runbook.md +142 -0
- templates/django/scaffold/docs/playbooks/backend-api.md +119 -0
- templates/django/scaffold/src/backend/config/__init__.py +0 -0
- templates/django/scaffold/src/backend/config/settings.py +31 -0
- templates/django/scaffold/src/backend/config/urls.py +11 -0
- templates/django/scaffold/src/backend/config/wsgi.py +6 -0
- templates/django/scaffold/src/backend/manage.py +14 -0
- templates/django/scaffold/src/backend/pyproject.toml +37 -0
- templates/django/scaffold/src/backend/tests/test_health.py +4 -0
- templates/django/scaffold-boundary.yaml +25 -0
- templates/django/skills/python-django/SKILL.md +450 -0
- templates/django/skills/python-django/references/anatomy.md +117 -0
- templates/django/skills/python-django/scripts/new_endpoint.py +89 -0
- templates/django/stack.yaml +73 -0
- templates/fastapi/rules/backend.md +18 -0
- templates/fastapi/scaffold/docs/engineering/fastapi-rules.md +37 -0
- templates/fastapi/scaffold/docs/playbooks/fastapi-service.md +30 -0
- templates/fastapi/scaffold/src/backend/app/__init__.py +0 -0
- templates/fastapi/scaffold/src/backend/app/main.py +8 -0
- templates/fastapi/scaffold/src/backend/pyproject.toml +39 -0
- templates/fastapi/scaffold/src/backend/tests/test_health.py +11 -0
- templates/fastapi/scaffold-boundary.yaml +25 -0
- templates/fastapi/skills/python-fastapi/SKILL.md +75 -0
- templates/fastapi/skills/python-fastapi/references/anatomy.md +117 -0
- templates/fastapi/skills/python-fastapi/scripts/new_endpoint.py +101 -0
- templates/fastapi/stack.yaml +57 -0
- templates/flutter/rules/mobile.md +26 -0
- templates/flutter/scaffold/docs/engineering/flutter-rules.md +35 -0
- templates/flutter/scaffold/docs/playbooks/flutter-app.md +45 -0
- templates/flutter/scaffold/src/mobile/lib/core/error_mapper.dart +17 -0
- templates/flutter/scaffold/src/mobile/lib/core/router.dart +13 -0
- templates/flutter/scaffold/src/mobile/lib/main.dart +22 -0
- templates/flutter/scaffold/src/mobile/lib/screens/health_screen.dart +32 -0
- templates/flutter/scaffold/src/mobile/lib/services/health_service.dart +11 -0
- templates/flutter/scaffold/src/mobile/lib/state/health_provider.dart +13 -0
- templates/flutter/scaffold/src/mobile/pubspec.yaml +22 -0
- templates/flutter/scaffold/src/mobile/test/health_provider_test.dart +90 -0
- templates/flutter/scaffold-boundary.yaml +28 -0
- templates/flutter/skills/flutter/SKILL.md +76 -0
- templates/flutter/skills/flutter/references/anatomy.md +64 -0
- templates/flutter/stack.yaml +70 -0
- templates/go/rules/backend.md +19 -0
- templates/go/scaffold/docs/engineering/go-rules.md +45 -0
- templates/go/scaffold/docs/playbooks/go-service.md +30 -0
- templates/go/scaffold/src/backend/cmd/api/main.go +22 -0
- templates/go/scaffold/src/backend/cmd/api/main_test.go +20 -0
- templates/go/scaffold/src/backend/go.mod +3 -0
- templates/go/scaffold-boundary.yaml +25 -0
- templates/go/skills/go-patterns/SKILL.md +68 -0
- templates/go/skills/go-patterns/assets/go-checklist.md +29 -0
- templates/go/skills/go-patterns/references/anatomy.md +115 -0
- templates/go/skills/go-patterns/references/go-2026-idioms.md +92 -0
- templates/go/skills/go-patterns/scripts/new_endpoint.py +117 -0
- templates/go/skills/go-patterns/versions.json +16 -0
- templates/go/stack.yaml +54 -0
- templates/go-fiber/rules/backend.md +20 -0
- templates/go-fiber/scaffold/docs/engineering/fiber-rules.md +97 -0
- templates/go-fiber/scaffold/docs/playbooks/fiber-service.md +149 -0
- templates/go-fiber/scaffold/src/backend/cmd/api/main.go +21 -0
- templates/go-fiber/scaffold/src/backend/cmd/api/main_test.go +17 -0
- templates/go-fiber/scaffold/src/backend/go.mod +23 -0
- templates/go-fiber/scaffold/src/backend/go.sum +49 -0
- templates/go-fiber/scaffold-boundary.yaml +26 -0
- templates/go-fiber/skills/go-fiber/SKILL.md +203 -0
- templates/go-fiber/skills/go-fiber/assets/fiber-checklist.md +26 -0
- templates/go-fiber/skills/go-fiber/references/anatomy.md +116 -0
- templates/go-fiber/skills/go-fiber/references/fiber-v3-patterns.md +89 -0
- templates/go-fiber/skills/go-fiber/scripts/new_endpoint.py +107 -0
- templates/go-fiber/skills/go-fiber/versions.json +16 -0
- templates/go-fiber/stack.yaml +59 -0
- templates/go-plain/scaffold/src/backend/go.mod +3 -0
- templates/go-plain/scaffold/src/backend/main.go +7 -0
- templates/go-plain/scaffold-boundary.yaml +22 -0
- templates/go-plain/stack.yaml +51 -0
- templates/java-plain/scaffold/src/backend/mvnw +302 -0
- templates/java-plain/scaffold/src/backend/pom.xml +41 -0
- templates/java-plain/scaffold/src/backend/src/main/java/com/example/app/Main.java +10 -0
- templates/java-plain/scaffold-boundary.yaml +23 -0
- templates/java-plain/stack.yaml +51 -0
- templates/laravel/rules/backend.md +20 -0
- templates/laravel/scaffold/docs/engineering/laravel-rules.md +28 -0
- templates/laravel/scaffold/docs/playbooks/laravel-service.md +28 -0
- templates/laravel/scaffold/src/backend/app/Exceptions/Handler.php +27 -0
- templates/laravel/scaffold/src/backend/app/Http/Controllers/HealthController.php +15 -0
- templates/laravel/scaffold/src/backend/app/Support/HealthStatus.php +14 -0
- templates/laravel/scaffold/src/backend/composer.json +24 -0
- templates/laravel/scaffold/src/backend/phpunit.xml +10 -0
- templates/laravel/scaffold/src/backend/public/index.php +8 -0
- templates/laravel/scaffold/src/backend/routes/api.php +7 -0
- templates/laravel/scaffold/src/backend/tests/Unit/HealthStatusTest.php +16 -0
- templates/laravel/scaffold-boundary.yaml +23 -0
- templates/laravel/skills/laravel/SKILL.md +56 -0
- templates/laravel/skills/laravel/references/anatomy.md +65 -0
- templates/laravel/stack.yaml +66 -0
- templates/meta/rules/graph-first.md +27 -0
- templates/meta/rules/hook-author.md +19 -0
- templates/meta/rules/mcp-tool-author.md +18 -0
- templates/meta/rules/meta-engineering.md +17 -0
- templates/meta/scaffold-boundary.yaml +55 -0
- templates/meta/skills/claude-sdk-integration/SKILL.md +163 -0
- templates/meta/skills/claude-sdk-integration/assets/sdk-checklist.md +27 -0
- templates/meta/skills/claude-sdk-integration/scripts/check_model_ids.py +97 -0
- templates/meta/skills/graph-os-authoring/SKILL.md +278 -0
- templates/meta/skills/graph-os-authoring/assets/graph-os-checklist.md +25 -0
- templates/meta/skills/graph-os-authoring/scripts/new_extractor.py +76 -0
- templates/meta/skills/hook-authoring/SKILL.md +292 -0
- templates/meta/skills/hook-authoring/assets/hook-checklist.md +30 -0
- templates/meta/skills/hook-authoring/scripts/new_hook.sh +75 -0
- templates/meta/skills/mcp-tool-authoring/SKILL.md +301 -0
- templates/meta/skills/mcp-tool-authoring/assets/mcp-tool-checklist.md +29 -0
- templates/meta/skills/mcp-tool-authoring/scripts/new_tool.py +74 -0
- templates/meta/skills/meta-engineering/SKILL.md +151 -0
- templates/meta/skills/meta-engineering/assets/meta-edit-checklist.md +28 -0
- templates/meta/skills/meta-engineering/scripts/which_layer.py +61 -0
- templates/meta/skills/python-meta-server/SKILL.md +162 -0
- templates/meta/skills/python-meta-server/assets/meta-server-checklist.md +28 -0
- templates/meta/skills/python-meta-server/scripts/check_envelope.py +91 -0
- templates/meta/skills/react-vite-hub/SKILL.md +140 -0
- templates/meta/skills/react-vite-hub/assets/hub-ui-checklist.md +23 -0
- templates/meta/skills/react-vite-hub/scripts/check_vite_env.py +73 -0
- templates/meta/stack.yaml +119 -0
- templates/nestjs/rules/backend.md +20 -0
- templates/nestjs/scaffold/docs/engineering/nestjs-rules.md +33 -0
- templates/nestjs/scaffold/docs/playbooks/nestjs-service.md +39 -0
- templates/nestjs/scaffold/src/backend/nest-cli.json +5 -0
- templates/nestjs/scaffold/src/backend/package.json +28 -0
- templates/nestjs/scaffold/src/backend/src/app.module.ts +9 -0
- templates/nestjs/scaffold/src/backend/src/common/all-exceptions.filter.ts +59 -0
- templates/nestjs/scaffold/src/backend/src/health/health.controller.ts +14 -0
- templates/nestjs/scaffold/src/backend/src/health/health.module.ts +10 -0
- templates/nestjs/scaffold/src/backend/src/health/health.service.spec.ts +21 -0
- templates/nestjs/scaffold/src/backend/src/health/health.service.ts +9 -0
- templates/nestjs/scaffold/src/backend/src/main.ts +26 -0
- templates/nestjs/scaffold/src/backend/tsconfig.json +16 -0
- templates/nestjs/scaffold/src/backend/vitest.config.ts +9 -0
- templates/nestjs/scaffold-boundary.yaml +25 -0
- templates/nestjs/skills/nestjs/SKILL.md +67 -0
- templates/nestjs/skills/nestjs/references/anatomy.md +65 -0
- templates/nestjs/stack.yaml +68 -0
- templates/nextjs/rules/frontend.md +18 -0
- templates/nextjs/scaffold/docs/design/00-index.md +23 -0
- templates/nextjs/scaffold/docs/design/colors-tokens.md +141 -0
- templates/nextjs/scaffold/docs/design/components-patterns.md +159 -0
- templates/nextjs/scaffold/docs/design/motion-accessibility.md +137 -0
- templates/nextjs/scaffold/docs/design/typography-spacing.md +107 -0
- templates/nextjs/scaffold/docs/engineering/accessibility-web.md +56 -0
- templates/nextjs/scaffold/docs/engineering/copywriting-standard.md +102 -0
- templates/nextjs/scaffold/docs/engineering/formatting-rules.md +89 -0
- templates/nextjs/scaffold/docs/engineering/frontend-rendering-rules.md +80 -0
- templates/nextjs/scaffold/docs/engineering/frontend-rules.md +183 -0
- templates/nextjs/scaffold/docs/engineering/i18n-policy.md +99 -0
- templates/nextjs/scaffold/docs/pages-content-spec/00-index.md +78 -0
- templates/nextjs/scaffold/docs/playbooks/content-seo.md +55 -0
- templates/nextjs/scaffold/docs/playbooks/docs-governance.md +51 -0
- templates/nextjs/scaffold/docs/playbooks/frontend-ui.md +63 -0
- templates/nextjs/scaffold/src/frontend/app/layout.tsx +14 -0
- templates/nextjs/scaffold/src/frontend/app/page.tsx +3 -0
- templates/nextjs/scaffold/src/frontend/eslint.config.js +17 -0
- templates/nextjs/scaffold/src/frontend/lib/greeting.test.ts +9 -0
- templates/nextjs/scaffold/src/frontend/lib/greeting.ts +3 -0
- templates/nextjs/scaffold/src/frontend/package.json +28 -0
- templates/nextjs/scaffold/src/frontend/tsconfig.json +18 -0
- templates/nextjs/scaffold/src/frontend/vitest.config.ts +10 -0
- templates/nextjs/scaffold-boundary.yaml +30 -0
- templates/nextjs/skills/nextjs-react/SKILL.md +485 -0
- templates/nextjs/skills/nextjs-react/references/anatomy.md +116 -0
- templates/nextjs/skills/nextjs-react/scripts/new_component.py +72 -0
- templates/nextjs/stack.yaml +81 -0
- templates/node-express/rules/backend.md +20 -0
- templates/node-express/scaffold/docs/engineering/express-rules.md +30 -0
- templates/node-express/scaffold/docs/playbooks/express-service.md +35 -0
- templates/node-express/scaffold/src/backend/package.json +24 -0
- templates/node-express/scaffold/src/backend/src/index.ts +17 -0
- templates/node-express/scaffold/src/backend/src/middleware/error-handler.ts +12 -0
- templates/node-express/scaffold/src/backend/src/routes/health.test.ts +33 -0
- templates/node-express/scaffold/src/backend/src/routes/health.ts +7 -0
- templates/node-express/scaffold/src/backend/tsconfig.json +15 -0
- templates/node-express/scaffold/src/backend/types/express-bootstrap.d.ts +21 -0
- templates/node-express/scaffold-boundary.yaml +25 -0
- templates/node-express/skills/node-express/SKILL.md +70 -0
- templates/node-express/skills/node-express/references/anatomy.md +63 -0
- templates/node-express/stack.yaml +63 -0
- templates/python/scaffold/docs/engineering/python-rules.md +27 -0
- templates/python/scaffold/docs/playbooks/python-library.md +35 -0
- templates/python/stack.yaml +60 -0
- templates/rails/rules/backend.md +10 -0
- templates/rails/scaffold/docs/engineering/rails-rules.md +33 -0
- templates/rails/scaffold/docs/playbooks/rails-service.md +42 -0
- templates/rails/scaffold/src/backend/Gemfile +12 -0
- templates/rails/scaffold/src/backend/app/controllers/application_controller.rb +26 -0
- templates/rails/scaffold/src/backend/app/controllers/health_controller.rb +6 -0
- templates/rails/scaffold/src/backend/app/models/health.rb +6 -0
- templates/rails/scaffold/src/backend/config/application.rb +12 -0
- templates/rails/scaffold/src/backend/config/boot.rb +3 -0
- templates/rails/scaffold/src/backend/config/routes.rb +4 -0
- templates/rails/scaffold/src/backend/config.ru +5 -0
- templates/rails/scaffold/src/backend/spec/rails_helper.rb +18 -0
- templates/rails/scaffold/src/backend/spec/requests/health_spec.rb +24 -0
- templates/rails/scaffold-boundary.yaml +25 -0
- templates/rails/skills/rails/SKILL.md +62 -0
- templates/rails/skills/rails/references/anatomy.md +71 -0
- templates/rails/stack.yaml +72 -0
- templates/react-native/rules/mobile.md +26 -0
- templates/react-native/scaffold/docs/engineering/accessibility-mobile.md +95 -0
- templates/react-native/scaffold/docs/engineering/mobile-rules.md +56 -0
- templates/react-native/scaffold/docs/engineering/offline-first.md +61 -0
- templates/react-native/scaffold/docs/playbooks/mobile-app.md +49 -0
- templates/react-native/scaffold/src/mobile/App.tsx +9 -0
- templates/react-native/scaffold/src/mobile/eslint.config.js +17 -0
- templates/react-native/scaffold/src/mobile/package.json +23 -0
- templates/react-native/scaffold/src/mobile/src/greeting.test.ts +9 -0
- templates/react-native/scaffold/src/mobile/src/greeting.ts +3 -0
- templates/react-native/scaffold/src/mobile/tsconfig.json +17 -0
- templates/react-native/scaffold/src/mobile/vitest.config.ts +10 -0
- templates/react-native/scaffold-boundary.yaml +30 -0
- templates/react-native/skills/react-native-mobile/SKILL.md +119 -0
- templates/react-native/skills/react-native-mobile/assets/rn-mobile-checklist.md +28 -0
- templates/react-native/skills/react-native-mobile/references/anatomy.md +140 -0
- templates/react-native/skills/react-native-mobile/references/rn-2026-practices.md +54 -0
- templates/react-native/skills/react-native-mobile/scripts/new_screen.py +73 -0
- templates/react-native/skills/react-native-mobile/versions.json +16 -0
- templates/react-native/skills/react-native-patterns/SKILL.md +512 -0
- templates/react-native/skills/react-native-patterns/assets/rn-review-checklist.md +26 -0
- templates/react-native/skills/react-native-patterns/references/anatomy.md +62 -0
- templates/react-native/skills/react-native-patterns/references/list-performance.md +70 -0
- templates/react-native/skills/react-native-patterns/scripts/scan_rn_perf.py +77 -0
- templates/react-native/stack.yaml +70 -0
- templates/ruby-plain/scaffold/src/backend/Gemfile +8 -0
- templates/ruby-plain/scaffold/src/backend/main.rb +3 -0
- templates/ruby-plain/scaffold-boundary.yaml +23 -0
- templates/ruby-plain/stack.yaml +50 -0
- templates/rust-axum/rules/backend.md +20 -0
- templates/rust-axum/scaffold/docs/engineering/rust-axum-rules.md +35 -0
- templates/rust-axum/scaffold/docs/playbooks/rust-axum-service.md +43 -0
- templates/rust-axum/scaffold/src/backend/Cargo.toml +20 -0
- templates/rust-axum/scaffold/src/backend/src/app.rs +12 -0
- templates/rust-axum/scaffold/src/backend/src/error.rs +48 -0
- templates/rust-axum/scaffold/src/backend/src/main.rs +24 -0
- templates/rust-axum/scaffold/src/backend/src/routes/health.rs +37 -0
- templates/rust-axum/scaffold/src/backend/src/routes/mod.rs +2 -0
- templates/rust-axum/scaffold-boundary.yaml +25 -0
- templates/rust-axum/skills/rust/SKILL.md +73 -0
- templates/rust-axum/skills/rust/references/anatomy.md +63 -0
- templates/rust-axum/stack.yaml +65 -0
- templates/rust-plain/scaffold/src/backend/Cargo.toml +6 -0
- templates/rust-plain/scaffold/src/backend/src/main.rs +3 -0
- templates/rust-plain/scaffold-boundary.yaml +23 -0
- templates/rust-plain/stack.yaml +51 -0
- templates/spring-boot/rules/backend.md +20 -0
- templates/spring-boot/scaffold/docs/engineering/spring-boot-rules.md +35 -0
- templates/spring-boot/scaffold/docs/playbooks/spring-boot-service.md +45 -0
- templates/spring-boot/scaffold/src/backend/mvnw +302 -0
- templates/spring-boot/scaffold/src/backend/pom.xml +69 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/Application.java +16 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/common/GlobalExceptionHandler.java +31 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthController.java +22 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthService.java +12 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthStatus.java +4 -0
- templates/spring-boot/scaffold/src/backend/src/test/java/com/example/app/health/HealthServiceTest.java +15 -0
- templates/spring-boot/scaffold-boundary.yaml +26 -0
- templates/spring-boot/skills/spring-boot/SKILL.md +84 -0
- templates/spring-boot/skills/spring-boot/references/anatomy.md +63 -0
- templates/spring-boot/stack.yaml +65 -0
- templates/svelte-sveltekit/rules/frontend.md +20 -0
- templates/svelte-sveltekit/scaffold/docs/engineering/svelte-sveltekit-rules.md +37 -0
- templates/svelte-sveltekit/scaffold/docs/playbooks/svelte-sveltekit-app.md +36 -0
- templates/svelte-sveltekit/scaffold/src/frontend/package.json +22 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/app.html +12 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/hooks.server.ts +14 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/components/Greeting.svelte +6 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.test.ts +26 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.ts +4 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+layout.svelte +24 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.svelte +9 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/health/+server.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/svelte.config.js +10 -0
- templates/svelte-sveltekit/scaffold/src/frontend/tsconfig.json +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/vite.config.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/vitest.config.ts +11 -0
- templates/svelte-sveltekit/scaffold-boundary.yaml +29 -0
- templates/svelte-sveltekit/skills/svelte/SKILL.md +90 -0
- templates/svelte-sveltekit/skills/svelte/references/anatomy.md +62 -0
- templates/svelte-sveltekit/stack.yaml +70 -0
- templates/typescript-plain/scaffold/src/index.ts +3 -0
- templates/typescript-plain/scaffold/tsconfig.json +13 -0
- templates/typescript-plain/scaffold-boundary.yaml +22 -0
- templates/typescript-plain/stack.yaml +44 -0
- templates/vue-nuxt/rules/frontend.md +19 -0
- templates/vue-nuxt/scaffold/docs/engineering/nuxt-rules.md +30 -0
- templates/vue-nuxt/scaffold/docs/playbooks/nuxt-app.md +29 -0
- templates/vue-nuxt/scaffold/src/frontend/app.vue +3 -0
- templates/vue-nuxt/scaffold/src/frontend/nuxt.config.ts +11 -0
- templates/vue-nuxt/scaffold/src/frontend/package.json +20 -0
- templates/vue-nuxt/scaffold/src/frontend/pages/index.test.ts +19 -0
- templates/vue-nuxt/scaffold/src/frontend/pages/index.vue +11 -0
- templates/vue-nuxt/scaffold/src/frontend/vitest.config.ts +12 -0
- templates/vue-nuxt/scaffold-boundary.yaml +26 -0
- templates/vue-nuxt/skills/vue-nuxt/SKILL.md +57 -0
- templates/vue-nuxt/skills/vue-nuxt/references/anatomy.md +60 -0
- templates/vue-nuxt/stack.yaml +61 -0
- templates/wordpress/rules/backend.md +19 -0
- templates/wordpress/scaffold/docs/engineering/wordpress-rules.md +28 -0
- templates/wordpress/scaffold/docs/playbooks/wordpress-service.md +29 -0
- templates/wordpress/scaffold/src/backend/composer.json +17 -0
- templates/wordpress/scaffold/src/backend/phpcs.xml.dist +11 -0
- templates/wordpress/scaffold/src/backend/phpunit.xml +10 -0
- templates/wordpress/scaffold/src/backend/plugin/inc/health.php +8 -0
- templates/wordpress/scaffold/src/backend/plugin/plugin.php +28 -0
- templates/wordpress/scaffold/src/backend/tests/HealthStatusTest.php +15 -0
- templates/wordpress/scaffold/src/backend/theme/functions.php +18 -0
- templates/wordpress/scaffold/src/backend/theme/style.css +11 -0
- templates/wordpress/scaffold-boundary.yaml +23 -0
- templates/wordpress/skills/wordpress/SKILL.md +110 -0
- templates/wordpress/skills/wordpress/assets/wp-checklist.md +28 -0
- templates/wordpress/skills/wordpress/references/wp-development.md +75 -0
- templates/wordpress/skills/wordpress/references/wp-security.md +68 -0
- templates/wordpress/skills/wordpress/scripts/scan_wp_smells.py +91 -0
- templates/wordpress/skills/wordpress/versions.json +16 -0
- templates/wordpress/stack.yaml +60 -0
- thinking_os/__init__.py +1 -0
- thinking_os/_agent_markers.py +32 -0
- thinking_os/background.py +405 -0
- thinking_os/bootstrap_outcomes.py +200 -0
- thinking_os/budget.py +302 -0
- thinking_os/capture.py +495 -0
- thinking_os/cognition.py +516 -0
- thinking_os/cognition_schemas.py +517 -0
- thinking_os/compress.py +192 -0
- thinking_os/concepts.py +233 -0
- thinking_os/dashboard.py +159 -0
- thinking_os/database.py +2883 -0
- thinking_os/decay.py +393 -0
- thinking_os/digest.py +295 -0
- thinking_os/dispatcher.py +192 -0
- thinking_os/dispatcher_helpers.py +48 -0
- thinking_os/dispatchers/__init__.py +3 -0
- thinking_os/dispatchers/default.py +47 -0
- thinking_os/distill.py +192 -0
- thinking_os/doc_indexer.py +905 -0
- thinking_os/embeddings.py +943 -0
- thinking_os/formula_composer.py +556 -0
- thinking_os/gate_marker.py +75 -0
- thinking_os/graph.py +296 -0
- thinking_os/graph_indexer.py +360 -0
- thinking_os/health_check.py +517 -0
- thinking_os/impact.py +119 -0
- thinking_os/memory_gc.py +356 -0
- thinking_os/migrator_embeddings.py +318 -0
- thinking_os/precision.py +194 -0
- thinking_os/record_outcome.py +399 -0
- thinking_os/repair.py +105 -0
- thinking_os/retrieval_quality.py +239 -0
- thinking_os/roles_state.py +168 -0
- thinking_os/sanitizer.py +320 -0
- thinking_os/server.py +3160 -0
- thinking_os/session_enrich.py +272 -0
- thinking_os/session_observe_worker.py +111 -0
- thinking_os/session_startup.py +74 -0
- thinking_os/session_summary.py +245 -0
- thinking_os/task_analyzer.py +462 -0
- thinking_os/task_parser.py +342 -0
- thinking_os/task_sync.py +73 -0
- thinking_os/tools/__init__.py +6 -0
- thinking_os/tools/_shared.py +947 -0
- thinking_os/tools/cognition.py +1867 -0
- thinking_os/tools/docs.py +770 -0
- thinking_os/tools/learning.py +2078 -0
- thinking_os/tools/logs.py +79 -0
- thinking_os/tools/memory.py +840 -0
- thinking_os/tools/metrics.py +200 -0
- thinking_os/tools/retrieve.py +415 -0
- thinking_os/tools/routing.py +658 -0
- thinking_os/tools/tasks.py +449 -0
- thinking_os/tools/trajectory.py +181 -0
- thinking_os/tracing.py +235 -0
- web/__init__.py +5 -0
- web/_cache.py +118 -0
- web/_deps.py +56 -0
- web/_envelope.py +85 -0
- web/_project_context.py +140 -0
- web/chat_providers.py +108 -0
- web/init_jobs.py +216 -0
- web/routes/__init__.py +25 -0
- web/routes/_bounded_read.py +76 -0
- web/routes/board.py +1089 -0
- web/routes/cognition.py +1838 -0
- web/routes/config.py +635 -0
- web/routes/graph.py +513 -0
- web/routes/health.py +180 -0
- web/routes/hooks.py +288 -0
- web/routes/hub.py +1219 -0
- web/routes/logs.py +374 -0
- web/routes/metrics.py +43 -0
- web/routes/observability.py +400 -0
- web/routes/patterns.py +227 -0
- web/routes/presence.py +609 -0
- web/routes/roles.py +446 -0
- web/routes/scheduled.py +261 -0
- web/routes/search.py +238 -0
- web/routes/sessions.py +220 -0
- web/routes/settings.py +363 -0
- web/routes/stream.py +547 -0
- web/security.py +157 -0
- web/server.py +283 -0
|
@@ -0,0 +1,1867 @@
|
|
|
1
|
+
"""Coding OS — Formula-agent supervisor MCP tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sqlite3
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from tools._shared import _gated_module, fail, ok, safe_tool
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("coding_os.tools.cognition")
|
|
18
|
+
|
|
19
|
+
# Canonical corrective action per backtrack root cause — the SSOT shared by
|
|
20
|
+
# cos_backtrack_log (returned as the agent's next-step suggestion) and
|
|
21
|
+
# learn_extract's anatomy mining (the remedy paired with a recurring cause that
|
|
22
|
+
# carries no recorded remedy). Keys match the backtrack root_cause enum.
|
|
23
|
+
CANONICAL_REMEDIES: dict[str, str] = {
|
|
24
|
+
"wrong_model": "Use cos_route_model to select the right model before re-dispatching.",
|
|
25
|
+
"scope_too_large": "Decompose via cos_task_create and pick the smallest slice.",
|
|
26
|
+
"missing_context": "Run cos_doc_search or cos_search to load relevant context first.",
|
|
27
|
+
"tool_failure": "Run cos_health to verify permissions/env vars, then retry with explicit paths.",
|
|
28
|
+
"spec_ambiguity": "Log open questions via cos_discovery and resolve with user before implementing.",
|
|
29
|
+
"env_mismatch": "Run cos doctor to validate environment config, then restart the session.",
|
|
30
|
+
"other": "Re-classify the problem (Cynefin gate) and review the Anti-Paralysis advisory.",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Lazy import of cognition — avoids circular at module load time
|
|
35
|
+
def _cog():
|
|
36
|
+
import cognition as _mod
|
|
37
|
+
|
|
38
|
+
return _mod
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _schemas():
|
|
42
|
+
import cognition_schemas as _mod
|
|
43
|
+
|
|
44
|
+
return _mod
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Internal helpers
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _now_iso() -> str:
|
|
53
|
+
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_role_persistence_cache: dict[str, tuple[str | None, Any]] | None = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _resolve_role_persistence(role_id: str) -> tuple[str | None, Any]:
|
|
60
|
+
global _role_persistence_cache
|
|
61
|
+
if _role_persistence_cache is None:
|
|
62
|
+
_role_persistence_cache = {}
|
|
63
|
+
cog = _cog()
|
|
64
|
+
schemas_mod = _schemas()
|
|
65
|
+
# Primary source: ROLE_OUTPUT_CLASSES registry in cognition_schemas.
|
|
66
|
+
# Frontmatter `output_schema:` / `bundle_field:` override on a per-
|
|
67
|
+
# role basis (lets a deployment swap the Pydantic class without
|
|
68
|
+
# editing the registry).
|
|
69
|
+
for rid, cls in schemas_mod.ROLE_OUTPUT_CLASSES.items():
|
|
70
|
+
_role_persistence_cache[rid] = (rid, cls)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
registry = cog.load_agent_registry()
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
logger.warning("agent registry load failed: %s", exc)
|
|
76
|
+
registry = {}
|
|
77
|
+
for rid, meta in registry.items():
|
|
78
|
+
if not isinstance(meta, dict):
|
|
79
|
+
continue
|
|
80
|
+
field = meta.get("bundle_field") or rid
|
|
81
|
+
schema_ref = meta.get("output_schema")
|
|
82
|
+
cls = _role_persistence_cache.get(rid, (None, None))[1]
|
|
83
|
+
if isinstance(schema_ref, str) and schema_ref.strip():
|
|
84
|
+
cls_name = schema_ref.split(".")[-1].strip()
|
|
85
|
+
if cls_name.isidentifier():
|
|
86
|
+
override = getattr(schemas_mod, cls_name, None)
|
|
87
|
+
if override is not None:
|
|
88
|
+
cls = override
|
|
89
|
+
_role_persistence_cache[rid] = (field, cls)
|
|
90
|
+
return _role_persistence_cache.get(role_id, (None, None))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _all_bundle_fields() -> set[str]:
|
|
94
|
+
"""Bundle field names from every registered role (data-driven)."""
|
|
95
|
+
cog = _cog()
|
|
96
|
+
try:
|
|
97
|
+
registry = cog.load_agent_registry()
|
|
98
|
+
except Exception:
|
|
99
|
+
return set()
|
|
100
|
+
out: set[str] = set()
|
|
101
|
+
for rid, meta in registry.items():
|
|
102
|
+
if isinstance(meta, dict):
|
|
103
|
+
out.add(str(meta.get("bundle_field") or rid))
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _resolve_agent_dir() -> Path:
|
|
108
|
+
import os as _os
|
|
109
|
+
|
|
110
|
+
explicit = _os.environ.get("COS_AGENT_DIR")
|
|
111
|
+
if explicit:
|
|
112
|
+
d = Path(explicit)
|
|
113
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
return d
|
|
115
|
+
agent = _os.environ.get("COS_AGENT") or "claude"
|
|
116
|
+
d = Path(".coding-os") / agent
|
|
117
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
return d
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _bundle_path(session_id: str) -> Path:
|
|
122
|
+
agent_dir = _resolve_agent_dir()
|
|
123
|
+
return agent_dir / f"evidence_bundle_{session_id}.json"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _load_bundle(session_id: str, task_marker: str, persona_id: str) -> Any:
|
|
127
|
+
schemas = _schemas()
|
|
128
|
+
path = _bundle_path(session_id)
|
|
129
|
+
if path.exists():
|
|
130
|
+
try:
|
|
131
|
+
data = json.loads(path.read_text())
|
|
132
|
+
return schemas.EvidenceBundle.model_validate(data)
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
|
|
135
|
+
corrupt = path.with_suffix(f".corrupt-{ts}.json")
|
|
136
|
+
path.rename(corrupt)
|
|
137
|
+
logger.warning("Corrupted bundle quarantined to %s: %s", corrupt, exc)
|
|
138
|
+
return schemas.EvidenceBundle(task_marker=task_marker, persona_id=persona_id)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _save_bundle(session_id: str, bundle: Any) -> None:
|
|
142
|
+
import fcntl
|
|
143
|
+
|
|
144
|
+
path = _bundle_path(session_id)
|
|
145
|
+
payload = bundle.model_dump_json(indent=2)
|
|
146
|
+
# flock-serialize concurrent writers so a parallel run can't interleave a
|
|
147
|
+
# half-written bundle. flush+fsync BEFORE the lock drops — close() releases
|
|
148
|
+
# the lock, and a buffered write would otherwise hit disk after LOCK_UN.
|
|
149
|
+
with path.open("a+") as fh:
|
|
150
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
151
|
+
fh.seek(0)
|
|
152
|
+
fh.truncate()
|
|
153
|
+
fh.write(payload)
|
|
154
|
+
fh.flush()
|
|
155
|
+
os.fsync(fh.fileno())
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# cos_supervise
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def register_cos_supervise(mcp, db_path): # db_path reserved for future warm-history ranking
|
|
164
|
+
@mcp.tool(
|
|
165
|
+
name="cos_supervise",
|
|
166
|
+
description=(
|
|
167
|
+
"Return the next action the main agent should take: dispatch a "
|
|
168
|
+
"formula-agent, backtrack, or signal done. Call repeatedly after "
|
|
169
|
+
"recording each formula output via cos_supervise_record_output. "
|
|
170
|
+
"Never spawns agents itself — only tells the main agent what to dispatch."
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
@safe_tool
|
|
174
|
+
def cos_supervise(
|
|
175
|
+
session_id: str,
|
|
176
|
+
task_marker: str,
|
|
177
|
+
persona_id: str,
|
|
178
|
+
intensity: str = "standard",
|
|
179
|
+
situation_id: str = "",
|
|
180
|
+
phase: str = "ROUTING",
|
|
181
|
+
dispatched: str = "[]",
|
|
182
|
+
pending: str = "[]",
|
|
183
|
+
backtrack_count: int = 0,
|
|
184
|
+
) -> str:
|
|
185
|
+
cog = _cog()
|
|
186
|
+
schemas = _schemas()
|
|
187
|
+
|
|
188
|
+
state = schemas.SupervisorState(
|
|
189
|
+
session_id=session_id,
|
|
190
|
+
task_marker=task_marker,
|
|
191
|
+
persona_id=persona_id,
|
|
192
|
+
intensity=intensity,
|
|
193
|
+
situation_id=situation_id or None,
|
|
194
|
+
phase=phase,
|
|
195
|
+
dispatched=json.loads(dispatched),
|
|
196
|
+
pending=json.loads(pending),
|
|
197
|
+
backtrack_count=backtrack_count,
|
|
198
|
+
)
|
|
199
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
200
|
+
action = cog.advance(state, bundle)
|
|
201
|
+
|
|
202
|
+
# emit trace event for every supervisor transition
|
|
203
|
+
try:
|
|
204
|
+
import tracing
|
|
205
|
+
|
|
206
|
+
event_kind = "supervise_action"
|
|
207
|
+
if action.action == "dispatch":
|
|
208
|
+
event_kind = "role_dispatch"
|
|
209
|
+
elif action.action == "dispatch_parallel":
|
|
210
|
+
event_kind = "parallel_dispatch"
|
|
211
|
+
elif action.action == "backtrack":
|
|
212
|
+
event_kind = "backtrack"
|
|
213
|
+
elif action.action == "done":
|
|
214
|
+
event_kind = "task_done"
|
|
215
|
+
tracing.emit(
|
|
216
|
+
session_id,
|
|
217
|
+
event_kind,
|
|
218
|
+
{
|
|
219
|
+
"action": action.action,
|
|
220
|
+
"formula": action.formula,
|
|
221
|
+
"formulas": action.formulas,
|
|
222
|
+
"reason": action.reason,
|
|
223
|
+
"phase": state.phase,
|
|
224
|
+
"dispatched_count": len(state.dispatched),
|
|
225
|
+
"backtrack_count": state.backtrack_count,
|
|
226
|
+
},
|
|
227
|
+
role=action.formula,
|
|
228
|
+
phase=state.phase,
|
|
229
|
+
)
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
from core.logging_os import swallow_safe
|
|
232
|
+
|
|
233
|
+
swallow_safe("thinking_os.cognition", "supervise output trace emit failed", exc=exc)
|
|
234
|
+
|
|
235
|
+
# Mirror the dispatch-time model precedence (claude-sdk.md §7.3
|
|
236
|
+
# tiers 2-3) so the main agent can pick a model BEFORE the run tool.
|
|
237
|
+
model_hints: dict = {}
|
|
238
|
+
if action.action in ("dispatch", "dispatch_parallel") and action.formula:
|
|
239
|
+
role_meta = cog.load_agent_registry().get(action.formula) or {}
|
|
240
|
+
model_hints = {
|
|
241
|
+
"preset_hint": _preset_role_hint(session_id, action.formula, db_path),
|
|
242
|
+
"role_pref": role_meta.get("model_pref") or {},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return ok(
|
|
246
|
+
{
|
|
247
|
+
"action": action.action,
|
|
248
|
+
"formula": action.formula,
|
|
249
|
+
"formulas": action.formulas,
|
|
250
|
+
"agent_file": action.agent_file,
|
|
251
|
+
"reason": action.reason,
|
|
252
|
+
"advisory": action.advisory,
|
|
253
|
+
"model_hints": model_hints,
|
|
254
|
+
"state": {
|
|
255
|
+
"phase": state.phase,
|
|
256
|
+
"dispatched": state.dispatched,
|
|
257
|
+
"pending": state.pending,
|
|
258
|
+
"backtrack_count": state.backtrack_count,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
meta={"layer": "routing", "source": "supervisor"},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return cos_supervise
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# cos_supervise_record_output
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def register_cos_supervise_record_output(mcp, db_path):
|
|
273
|
+
@mcp.tool(
|
|
274
|
+
name="cos_supervise_record_output",
|
|
275
|
+
description=(
|
|
276
|
+
"Append a formula-agent's output to the session EvidenceBundle "
|
|
277
|
+
"and record the dispatch in formula_dispatches. Call after each "
|
|
278
|
+
"formula-agent returns. status: ok|fail|timeout."
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
@safe_tool
|
|
282
|
+
def cos_supervise_record_output(
|
|
283
|
+
session_id: str,
|
|
284
|
+
task_marker: str,
|
|
285
|
+
persona_id: str,
|
|
286
|
+
formula_id: str,
|
|
287
|
+
output_json: str,
|
|
288
|
+
status: str = "ok",
|
|
289
|
+
latency_ms: int = 0,
|
|
290
|
+
) -> str:
|
|
291
|
+
# Validate formula_id before any use. A malformed value (e.g. an XML
|
|
292
|
+
# tool-call fragment leaking the arg boundary — `researcher</formula_id>...`)
|
|
293
|
+
# once landed verbatim in the typed column. Reject non-identifier values
|
|
294
|
+
# up front rather than persisting garbage.
|
|
295
|
+
if not formula_id or not re.fullmatch(r"[a-z0-9_]+", formula_id):
|
|
296
|
+
return fail("validation", "formula_id must match [a-z0-9_]+")
|
|
297
|
+
|
|
298
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
299
|
+
|
|
300
|
+
# Data-driven role → bundle-field + Pydantic class (frontmatter SSOT).
|
|
301
|
+
error_detail: str | None = None
|
|
302
|
+
field, cls = _resolve_role_persistence(formula_id)
|
|
303
|
+
if field and cls and status == "ok":
|
|
304
|
+
try:
|
|
305
|
+
parsed = cls.model_validate_json(output_json)
|
|
306
|
+
setattr(bundle, field, parsed)
|
|
307
|
+
except Exception as exc:
|
|
308
|
+
logger.warning("Failed to parse %s output: %s", formula_id, exc)
|
|
309
|
+
bundle.degraded_formulas.append(formula_id)
|
|
310
|
+
status = "fail"
|
|
311
|
+
error_detail = f"{formula_id} parse: {exc}"
|
|
312
|
+
elif status == "timeout":
|
|
313
|
+
bundle.degraded_formulas.append(formula_id)
|
|
314
|
+
error_detail = "timeout"
|
|
315
|
+
|
|
316
|
+
# Caller-supplied non-ok status (not a parse failure) carries its reason
|
|
317
|
+
# in output_json — keep a bounded slice so the failure stays diagnosable.
|
|
318
|
+
if status != "ok" and not error_detail:
|
|
319
|
+
error_detail = (output_json or "")[:1000]
|
|
320
|
+
|
|
321
|
+
_save_bundle(session_id, bundle)
|
|
322
|
+
|
|
323
|
+
output_hash = hashlib.sha256(output_json.encode()).hexdigest()[:16]
|
|
324
|
+
input_hash = hashlib.sha256(f"{session_id}:{formula_id}".encode()).hexdigest()[:16]
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
with sqlite3.connect(db_path) as conn:
|
|
328
|
+
conn.execute(
|
|
329
|
+
"INSERT INTO formula_dispatches "
|
|
330
|
+
"(session_id, task_marker, persona_id, formula_id, input_hash, "
|
|
331
|
+
"output_hash, latency_ms, status, ts, error) "
|
|
332
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
|
333
|
+
(
|
|
334
|
+
session_id,
|
|
335
|
+
task_marker,
|
|
336
|
+
persona_id,
|
|
337
|
+
formula_id,
|
|
338
|
+
input_hash,
|
|
339
|
+
output_hash,
|
|
340
|
+
latency_ms,
|
|
341
|
+
status,
|
|
342
|
+
_now_iso(),
|
|
343
|
+
error_detail,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
except Exception as exc:
|
|
347
|
+
logger.debug("formula_dispatches insert failed: %s", exc)
|
|
348
|
+
|
|
349
|
+
filled = sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
|
|
350
|
+
# emit trace event so the flowchart replay knows a role completed
|
|
351
|
+
try:
|
|
352
|
+
import tracing
|
|
353
|
+
|
|
354
|
+
tracing.emit(
|
|
355
|
+
session_id,
|
|
356
|
+
"role_output_recorded",
|
|
357
|
+
{
|
|
358
|
+
"formula_id": formula_id,
|
|
359
|
+
"status": status,
|
|
360
|
+
"latency_ms": latency_ms,
|
|
361
|
+
"output_hash": output_hash,
|
|
362
|
+
"bundle_fields_filled": filled,
|
|
363
|
+
},
|
|
364
|
+
role=formula_id,
|
|
365
|
+
)
|
|
366
|
+
except Exception:
|
|
367
|
+
pass # tracing must never break caller
|
|
368
|
+
return ok(
|
|
369
|
+
{"formula_id": formula_id, "status": status, "bundle_fields_filled": filled},
|
|
370
|
+
meta={"layer": "routing"},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return cos_supervise_record_output
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ---------------------------------------------------------------------------
|
|
377
|
+
# cos_dispatch_formula
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def register_cos_dispatch_formula(mcp, db_path):
|
|
382
|
+
@mcp.tool(
|
|
383
|
+
name="cos_dispatch_formula",
|
|
384
|
+
description=(
|
|
385
|
+
"Return the rendered agent prompt and input slice for a formula-agent. "
|
|
386
|
+
"The main agent uses this to construct the subagent dispatch. "
|
|
387
|
+
"Does NOT spawn the subagent — returns prompt text only."
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
@safe_tool
|
|
391
|
+
def cos_dispatch_formula(
|
|
392
|
+
formula_id: str,
|
|
393
|
+
session_id: str,
|
|
394
|
+
task_marker: str,
|
|
395
|
+
persona_id: str,
|
|
396
|
+
intensity: str = "standard",
|
|
397
|
+
) -> str:
|
|
398
|
+
cog = _cog()
|
|
399
|
+
agents = cog.load_agent_registry()
|
|
400
|
+
meta = agents.get(formula_id)
|
|
401
|
+
if not meta:
|
|
402
|
+
return fail("not_found", f"No agent file for formula {formula_id}")
|
|
403
|
+
|
|
404
|
+
agent_file = f"src/core/thinking_os/agents/{meta['_file']}"
|
|
405
|
+
agent_path = Path(__file__).resolve().parent.parent / "agents" / meta["_file"]
|
|
406
|
+
prompt_text = agent_path.read_text() if agent_path.exists() else ""
|
|
407
|
+
|
|
408
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
409
|
+
input_slice = cog.build_input_slice(formula_id, bundle)
|
|
410
|
+
input_slice["intensity_steps"] = cog._intensity_steps(formula_id, intensity)
|
|
411
|
+
|
|
412
|
+
return ok(
|
|
413
|
+
{
|
|
414
|
+
"formula_id": formula_id,
|
|
415
|
+
"agent_file": agent_file,
|
|
416
|
+
"prompt_text": prompt_text,
|
|
417
|
+
"input_slice": input_slice,
|
|
418
|
+
"timeout_s": meta.get("timeout_s", 90),
|
|
419
|
+
"model_pref": meta.get("model_pref", {}),
|
|
420
|
+
},
|
|
421
|
+
meta={"layer": "routing"},
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
return cos_dispatch_formula
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# ---------------------------------------------------------------------------
|
|
428
|
+
# cos_ambiguity_check
|
|
429
|
+
# ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def register_cos_ambiguity_check(mcp, db_path):
|
|
433
|
+
@mcp.tool(
|
|
434
|
+
name="cos_ambiguity_check",
|
|
435
|
+
description=(
|
|
436
|
+
"Run the 7-criteria Anti-Ambiguity gate over the session EvidenceBundle. "
|
|
437
|
+
"Returns violations (formula, criterion, detail). Empty list = gate passes. "
|
|
438
|
+
"Fires once at PLAN→EXECUTE; CLEAR 1 tasks skip this check."
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
@safe_tool
|
|
442
|
+
def cos_ambiguity_check(
|
|
443
|
+
session_id: str,
|
|
444
|
+
task_marker: str,
|
|
445
|
+
persona_id: str,
|
|
446
|
+
) -> str:
|
|
447
|
+
cog = _cog()
|
|
448
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
449
|
+
violations = cog.ambiguity_check(bundle)
|
|
450
|
+
|
|
451
|
+
# The enforce-anti-ambiguity gate reads this table as the CURRENT ambiguity
|
|
452
|
+
# state for the session, so each check supersedes the prior one: clear old
|
|
453
|
+
# rows first (a pass leaves none → the gate clears) then record this check's.
|
|
454
|
+
try:
|
|
455
|
+
with sqlite3.connect(db_path) as conn:
|
|
456
|
+
conn.execute("DELETE FROM ambiguity_violations WHERE session_id = ?", (session_id,))
|
|
457
|
+
for v in violations:
|
|
458
|
+
conn.execute(
|
|
459
|
+
"INSERT INTO ambiguity_violations "
|
|
460
|
+
"(session_id, formula_id, criterion, detail, ts) "
|
|
461
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
462
|
+
(
|
|
463
|
+
session_id,
|
|
464
|
+
v["formula"],
|
|
465
|
+
v["criterion"],
|
|
466
|
+
v.get("detail", ""),
|
|
467
|
+
_now_iso(),
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
except Exception as exc:
|
|
471
|
+
logger.debug("ambiguity_violations write failed: %s", exc)
|
|
472
|
+
|
|
473
|
+
return ok(
|
|
474
|
+
{"violations": violations, "passed": len(violations) == 0},
|
|
475
|
+
meta={"layer": "routing"},
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
return cos_ambiguity_check
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# ---------------------------------------------------------------------------
|
|
482
|
+
# cos_traceability
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def register_cos_traceability(mcp, db_path):
|
|
487
|
+
@mcp.tool(
|
|
488
|
+
name="cos_traceability",
|
|
489
|
+
description=(
|
|
490
|
+
"Read-only audit: verify that tasks have doc anchors and that "
|
|
491
|
+
"recent formula dispatches have matching evidence in the bundle. "
|
|
492
|
+
"Idempotent and non-blocking. scope: task|project."
|
|
493
|
+
),
|
|
494
|
+
)
|
|
495
|
+
@safe_tool
|
|
496
|
+
def cos_traceability(
|
|
497
|
+
session_id: str,
|
|
498
|
+
task_marker: str,
|
|
499
|
+
persona_id: str,
|
|
500
|
+
scope: str = "task",
|
|
501
|
+
) -> str:
|
|
502
|
+
gaps = []
|
|
503
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
with sqlite3.connect(db_path) as conn:
|
|
507
|
+
rows = conn.execute(
|
|
508
|
+
"SELECT formula_id, status FROM formula_dispatches "
|
|
509
|
+
"WHERE session_id=? ORDER BY ts",
|
|
510
|
+
(session_id,),
|
|
511
|
+
).fetchall()
|
|
512
|
+
except Exception:
|
|
513
|
+
rows = []
|
|
514
|
+
|
|
515
|
+
dispatched_ids = {r[0] for r in rows if r[1] == "ok"}
|
|
516
|
+
# Data-driven traceability: every dispatched role with a bundle field
|
|
517
|
+
# is checked. Roles missing bundle_field in their frontmatter are skipped
|
|
518
|
+
# (correct behavior — non-persisting roles don't appear in the bundle).
|
|
519
|
+
for fid in dispatched_ids:
|
|
520
|
+
field, _cls = _resolve_role_persistence(fid)
|
|
521
|
+
if field and getattr(bundle, field, None) is None:
|
|
522
|
+
gaps.append({"formula": fid, "detail": "dispatched but no output in bundle"})
|
|
523
|
+
|
|
524
|
+
total = len(dispatched_ids)
|
|
525
|
+
score = 1.0 if total == 0 else (total - len(gaps)) / total
|
|
526
|
+
|
|
527
|
+
return ok(
|
|
528
|
+
{"gaps": gaps, "redundancies": [], "score": round(score, 2), "scope": scope},
|
|
529
|
+
meta={"layer": "routing"},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return cos_traceability
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# ---------------------------------------------------------------------------
|
|
536
|
+
# cos_backtrack_log
|
|
537
|
+
# ---------------------------------------------------------------------------
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def register_cos_backtrack_log(mcp, db_path):
|
|
541
|
+
@mcp.tool(
|
|
542
|
+
name="cos_backtrack_log",
|
|
543
|
+
description=(
|
|
544
|
+
"Record a backtrack event. Returns {count, advisory, suggested_action, "
|
|
545
|
+
"root_cause_summary}. advisory fires at ≥3/≥5 backtracks. "
|
|
546
|
+
"suggested_action gives a concrete next step when root_cause is supplied. "
|
|
547
|
+
"root_cause_summary shows per-cause counts for this session."
|
|
548
|
+
),
|
|
549
|
+
)
|
|
550
|
+
@safe_tool
|
|
551
|
+
def cos_backtrack_log(
|
|
552
|
+
session_id: str,
|
|
553
|
+
from_formula: str,
|
|
554
|
+
to_formula: str,
|
|
555
|
+
reason: str,
|
|
556
|
+
task_marker: str = "",
|
|
557
|
+
persona_id: str = "",
|
|
558
|
+
hypothesis: str = "",
|
|
559
|
+
failure_signal: str = "",
|
|
560
|
+
root_cause: str = "",
|
|
561
|
+
corrective_action: str = "",
|
|
562
|
+
) -> str:
|
|
563
|
+
_VALID_ROOT_CAUSES = set(CANONICAL_REMEDIES)
|
|
564
|
+
|
|
565
|
+
# Silently clear invalid root_cause to avoid polluting the enum
|
|
566
|
+
if root_cause and root_cause not in _VALID_ROOT_CAUSES:
|
|
567
|
+
root_cause = "other"
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
with sqlite3.connect(db_path) as conn:
|
|
571
|
+
# Try inserting with anatomy columns (v25); fall back to base schema
|
|
572
|
+
try:
|
|
573
|
+
conn.execute(
|
|
574
|
+
"INSERT INTO backtrack_events "
|
|
575
|
+
"(session_id, from_formula, to_formula, reason, ts, "
|
|
576
|
+
" hypothesis, failure_signal, root_cause, corrective_action) "
|
|
577
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
578
|
+
(
|
|
579
|
+
session_id,
|
|
580
|
+
from_formula,
|
|
581
|
+
to_formula,
|
|
582
|
+
reason,
|
|
583
|
+
_now_iso(),
|
|
584
|
+
hypothesis or None,
|
|
585
|
+
failure_signal or None,
|
|
586
|
+
root_cause or None,
|
|
587
|
+
corrective_action or None,
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
except sqlite3.OperationalError:
|
|
591
|
+
# v25 columns not yet applied — insert base fields only
|
|
592
|
+
conn.execute(
|
|
593
|
+
"INSERT INTO backtrack_events "
|
|
594
|
+
"(session_id, from_formula, to_formula, reason, ts) "
|
|
595
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
596
|
+
(session_id, from_formula, to_formula, reason, _now_iso()),
|
|
597
|
+
)
|
|
598
|
+
count = conn.execute(
|
|
599
|
+
"SELECT COUNT(*) FROM backtrack_events WHERE session_id=?",
|
|
600
|
+
(session_id,),
|
|
601
|
+
).fetchone()[0]
|
|
602
|
+
|
|
603
|
+
# C1: root_cause_summary — per-cause backtrack counts this session
|
|
604
|
+
root_cause_summary: dict[str, int] = {}
|
|
605
|
+
try:
|
|
606
|
+
rows = conn.execute(
|
|
607
|
+
"SELECT root_cause, COUNT(*) AS cnt FROM backtrack_events "
|
|
608
|
+
"WHERE session_id=? AND root_cause IS NOT NULL "
|
|
609
|
+
"GROUP BY root_cause",
|
|
610
|
+
(session_id,),
|
|
611
|
+
).fetchall()
|
|
612
|
+
root_cause_summary = {r[0]: r[1] for r in rows}
|
|
613
|
+
except sqlite3.OperationalError:
|
|
614
|
+
root_cause_summary = {} # pre-v25: root_cause column absent
|
|
615
|
+
|
|
616
|
+
except Exception as exc:
|
|
617
|
+
return fail("internal", f"backtrack_log failed: {exc}")
|
|
618
|
+
|
|
619
|
+
advisory = ""
|
|
620
|
+
if count >= 5:
|
|
621
|
+
advisory = (
|
|
622
|
+
f"Anti-Paralysis: {count} backtracks this session. "
|
|
623
|
+
"Consider narrowing task scope or raising intensity level."
|
|
624
|
+
)
|
|
625
|
+
elif count >= 3:
|
|
626
|
+
advisory = f"Anti-Paralysis: {count} backtracks. Review scope if pattern continues."
|
|
627
|
+
|
|
628
|
+
# C2: concrete next step for the supplied root_cause
|
|
629
|
+
suggested_action = CANONICAL_REMEDIES.get(root_cause, "") if root_cause else ""
|
|
630
|
+
|
|
631
|
+
# emit trace event for replay
|
|
632
|
+
try:
|
|
633
|
+
import tracing
|
|
634
|
+
|
|
635
|
+
tracing.emit(
|
|
636
|
+
session_id,
|
|
637
|
+
"backtrack",
|
|
638
|
+
{
|
|
639
|
+
"from": from_formula,
|
|
640
|
+
"to": to_formula,
|
|
641
|
+
"reason": reason,
|
|
642
|
+
"count": count,
|
|
643
|
+
},
|
|
644
|
+
role=from_formula,
|
|
645
|
+
)
|
|
646
|
+
if advisory:
|
|
647
|
+
tracing.emit(
|
|
648
|
+
session_id,
|
|
649
|
+
"anti_paralysis_warn",
|
|
650
|
+
{
|
|
651
|
+
"count": count,
|
|
652
|
+
"advisory": advisory,
|
|
653
|
+
},
|
|
654
|
+
)
|
|
655
|
+
except Exception as _exc:
|
|
656
|
+
logger.debug("backtrack tracing skipped: %s", _exc)
|
|
657
|
+
|
|
658
|
+
return ok(
|
|
659
|
+
{
|
|
660
|
+
"count": count,
|
|
661
|
+
"advisory": advisory,
|
|
662
|
+
"suggested_action": suggested_action,
|
|
663
|
+
"root_cause_summary": root_cause_summary,
|
|
664
|
+
},
|
|
665
|
+
meta={"layer": "routing"},
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
return cos_backtrack_log
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
# ---------------------------------------------------------------------------
|
|
672
|
+
# cos_discovery
|
|
673
|
+
# ---------------------------------------------------------------------------
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def register_cos_discovery(mcp, db_path):
|
|
677
|
+
@mcp.tool(
|
|
678
|
+
name="cos_discovery",
|
|
679
|
+
description=(
|
|
680
|
+
"Capture a mid-work discovery. decision=backtrack_now triggers an "
|
|
681
|
+
"immediate backtrack recommendation. decision=record_for_later stores "
|
|
682
|
+
"the discovery for session summary review."
|
|
683
|
+
),
|
|
684
|
+
)
|
|
685
|
+
@safe_tool
|
|
686
|
+
def cos_discovery(
|
|
687
|
+
session_id: str,
|
|
688
|
+
task_marker: str,
|
|
689
|
+
persona_id: str,
|
|
690
|
+
kind: str,
|
|
691
|
+
summary: str,
|
|
692
|
+
impact_assessment: str,
|
|
693
|
+
decision: str,
|
|
694
|
+
) -> str:
|
|
695
|
+
if decision not in ("backtrack_now", "record_for_later"):
|
|
696
|
+
return fail("validation", "decision must be backtrack_now or record_for_later")
|
|
697
|
+
|
|
698
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
699
|
+
schemas = _schemas()
|
|
700
|
+
disc = schemas.Discovery(
|
|
701
|
+
kind=kind,
|
|
702
|
+
summary=summary,
|
|
703
|
+
impact_assessment=impact_assessment,
|
|
704
|
+
decision=decision,
|
|
705
|
+
ts=_now_iso(),
|
|
706
|
+
)
|
|
707
|
+
bundle.discoveries.append(disc)
|
|
708
|
+
_save_bundle(session_id, bundle)
|
|
709
|
+
|
|
710
|
+
# Also store as observation for session_summary to surface
|
|
711
|
+
try:
|
|
712
|
+
with sqlite3.connect(db_path) as conn:
|
|
713
|
+
conn.execute(
|
|
714
|
+
"INSERT INTO observations (session_id, kind, content, ts) VALUES (?, ?, ?, ?)",
|
|
715
|
+
(
|
|
716
|
+
session_id,
|
|
717
|
+
f"discovery:{kind}",
|
|
718
|
+
json.dumps(
|
|
719
|
+
{"summary": summary, "impact": impact_assessment, "decision": decision}
|
|
720
|
+
),
|
|
721
|
+
_now_iso(),
|
|
722
|
+
),
|
|
723
|
+
)
|
|
724
|
+
except Exception as exc:
|
|
725
|
+
logger.debug("observation insert failed: %s", exc)
|
|
726
|
+
|
|
727
|
+
return ok(
|
|
728
|
+
{"stored": True, "action_required": decision == "backtrack_now"},
|
|
729
|
+
meta={"layer": "routing"},
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
return cos_discovery
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
# ---------------------------------------------------------------------------
|
|
736
|
+
# cos_situation_detect
|
|
737
|
+
# ---------------------------------------------------------------------------
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def register_cos_situation_detect(mcp, db_path):
|
|
741
|
+
@mcp.tool(
|
|
742
|
+
name="cos_situation_detect",
|
|
743
|
+
description=(
|
|
744
|
+
"Classify a set of signals into a situational dispatch chain id "
|
|
745
|
+
"(incident-response, onboarding, scope-change, external-integration, "
|
|
746
|
+
"design-review, existing-project-takeover) or null if none match. "
|
|
747
|
+
"The matched situation overrides persona primary_formulas."
|
|
748
|
+
),
|
|
749
|
+
)
|
|
750
|
+
@safe_tool
|
|
751
|
+
def cos_situation_detect(signals: str = "[]") -> str:
|
|
752
|
+
cog = _cog()
|
|
753
|
+
signal_set = set(json.loads(signals))
|
|
754
|
+
situations = cog.load_situation_registry()
|
|
755
|
+
|
|
756
|
+
for sit_id, sit in situations.items():
|
|
757
|
+
triggers = set(sit.get("trigger_signals", []))
|
|
758
|
+
matched = signal_set & triggers
|
|
759
|
+
if matched:
|
|
760
|
+
return ok(
|
|
761
|
+
{"situation_id": sit_id, "matched_signals": list(matched)},
|
|
762
|
+
meta={"layer": "routing"},
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
return ok({"situation_id": None, "matched_signals": []}, meta={"layer": "routing"})
|
|
766
|
+
|
|
767
|
+
return cos_situation_detect
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
# ---------------------------------------------------------------------------
|
|
771
|
+
# cos_takeover
|
|
772
|
+
# ---------------------------------------------------------------------------
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def register_cos_takeover(mcp, db_path):
|
|
776
|
+
@mcp.tool(
|
|
777
|
+
name="cos_takeover",
|
|
778
|
+
description=(
|
|
779
|
+
"Bootstrap an existing-project-takeover session: sets the situation "
|
|
780
|
+
"to existing-project-takeover, picks legacy-maintainer persona, "
|
|
781
|
+
"and returns the first dispatch action (Analyst in reverse mode). "
|
|
782
|
+
"Use when inheriting a legacy repo with no docs."
|
|
783
|
+
),
|
|
784
|
+
)
|
|
785
|
+
@safe_tool
|
|
786
|
+
def cos_takeover(
|
|
787
|
+
session_id: str,
|
|
788
|
+
task_marker: str,
|
|
789
|
+
repo_description: str = "", # reserved for Researcher pre-seeding in future slice
|
|
790
|
+
) -> str:
|
|
791
|
+
cog = _cog()
|
|
792
|
+
schemas = _schemas()
|
|
793
|
+
|
|
794
|
+
persona_id = "legacy-maintainer"
|
|
795
|
+
situation_id = "existing-project-takeover"
|
|
796
|
+
|
|
797
|
+
bundle = schemas.EvidenceBundle(
|
|
798
|
+
task_marker=task_marker,
|
|
799
|
+
persona_id=persona_id,
|
|
800
|
+
situation_id=situation_id,
|
|
801
|
+
intensity="full",
|
|
802
|
+
)
|
|
803
|
+
_save_bundle(session_id, bundle)
|
|
804
|
+
|
|
805
|
+
state = schemas.SupervisorState(
|
|
806
|
+
session_id=session_id,
|
|
807
|
+
task_marker=task_marker,
|
|
808
|
+
persona_id=persona_id,
|
|
809
|
+
intensity="full",
|
|
810
|
+
situation_id=situation_id,
|
|
811
|
+
phase="ROUTING",
|
|
812
|
+
)
|
|
813
|
+
first_action = cog.advance(state, bundle)
|
|
814
|
+
|
|
815
|
+
return ok(
|
|
816
|
+
{
|
|
817
|
+
"persona_id": persona_id,
|
|
818
|
+
"situation_id": situation_id,
|
|
819
|
+
"first_action": {
|
|
820
|
+
"action": first_action.action,
|
|
821
|
+
"formula": first_action.formula,
|
|
822
|
+
"agent_file": first_action.agent_file,
|
|
823
|
+
"reason": first_action.reason,
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
meta={"layer": "routing"},
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
return cos_takeover
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
# ---------------------------------------------------------------------------
|
|
833
|
+
# Registration entry point
|
|
834
|
+
# ---------------------------------------------------------------------------
|
|
835
|
+
|
|
836
|
+
# ---------------------------------------------------------------------------
|
|
837
|
+
# Role-based cognitive routing
|
|
838
|
+
# Spec: docs/phase-n-role-based-routing-plan.md §2.6
|
|
839
|
+
# ---------------------------------------------------------------------------
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def register_cos_analyze_task(mcp, db_path):
|
|
843
|
+
@mcp.tool(
|
|
844
|
+
name="cos_analyze_task",
|
|
845
|
+
description=(
|
|
846
|
+
"Extract TaskSignals (domain, action, novelty, urgency, scope, "
|
|
847
|
+
"external_dependency, is_takeover, breaking_change, ...) from a "
|
|
848
|
+
"prompt + optional memory/graph context. Replaces persona "
|
|
849
|
+
"keyword matching. Under 500ms; cached per task_marker."
|
|
850
|
+
),
|
|
851
|
+
)
|
|
852
|
+
@safe_tool
|
|
853
|
+
def cos_analyze_task(
|
|
854
|
+
prompt: str,
|
|
855
|
+
task_marker: str = "",
|
|
856
|
+
complexity: str = "COMPLICATED",
|
|
857
|
+
dimensions: int = 1,
|
|
858
|
+
project_dir: str = "",
|
|
859
|
+
session_id: str = "",
|
|
860
|
+
) -> str:
|
|
861
|
+
import task_analyzer # lazy
|
|
862
|
+
import tracing
|
|
863
|
+
|
|
864
|
+
agent_dir = _resolve_agent_dir()
|
|
865
|
+
pd = Path(project_dir) if project_dir else Path.cwd()
|
|
866
|
+
sid = session_id or "anon"
|
|
867
|
+
tracing.emit(
|
|
868
|
+
sid,
|
|
869
|
+
"analyze_start",
|
|
870
|
+
{
|
|
871
|
+
"prompt_len": len(prompt),
|
|
872
|
+
"task_marker": task_marker,
|
|
873
|
+
"complexity": complexity,
|
|
874
|
+
"dimensions": dimensions,
|
|
875
|
+
},
|
|
876
|
+
)
|
|
877
|
+
signals = task_analyzer.analyze_task(
|
|
878
|
+
prompt=prompt,
|
|
879
|
+
task_marker=task_marker or None,
|
|
880
|
+
complexity=complexity,
|
|
881
|
+
dimensions=dimensions,
|
|
882
|
+
agent_dir=agent_dir,
|
|
883
|
+
project_dir=pd,
|
|
884
|
+
)
|
|
885
|
+
tracing.emit(
|
|
886
|
+
sid,
|
|
887
|
+
"analyze_done",
|
|
888
|
+
{
|
|
889
|
+
"action": signals.action,
|
|
890
|
+
"domain": signals.domain,
|
|
891
|
+
"urgency": signals.urgency,
|
|
892
|
+
"scope_size": signals.scope_size,
|
|
893
|
+
"external_dependency": signals.external_dependency,
|
|
894
|
+
"is_takeover": signals.is_takeover,
|
|
895
|
+
"breaking_change": signals.breaking_change,
|
|
896
|
+
"extraction_ms": signals.extraction_ms,
|
|
897
|
+
"source_errors": signals.source_errors,
|
|
898
|
+
},
|
|
899
|
+
)
|
|
900
|
+
return ok(
|
|
901
|
+
signals.model_dump(),
|
|
902
|
+
meta={"layer": "routing", "source": "task_analyzer"},
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
return cos_analyze_task
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def register_cos_compose_chain(mcp, db_path):
|
|
909
|
+
@mcp.tool(
|
|
910
|
+
name="cos_compose_chain",
|
|
911
|
+
description=(
|
|
912
|
+
"Compose an ordered formula-role chain from TaskSignals. "
|
|
913
|
+
"Strategy: situation override > preset match > per-role scoring "
|
|
914
|
+
"composer > hard fallback. Returns ComposedChain with provenance "
|
|
915
|
+
"(preset_id, preset_version, effective_threshold, activations)."
|
|
916
|
+
),
|
|
917
|
+
)
|
|
918
|
+
@safe_tool
|
|
919
|
+
def cos_compose_chain(
|
|
920
|
+
signals_json: str,
|
|
921
|
+
situation_id: str = "",
|
|
922
|
+
preset_min_score: int = -1,
|
|
923
|
+
session_id: str = "",
|
|
924
|
+
) -> str:
|
|
925
|
+
import formula_composer # lazy
|
|
926
|
+
|
|
927
|
+
schemas = _schemas()
|
|
928
|
+
signals = schemas.TaskSignals.model_validate_json(signals_json)
|
|
929
|
+
chain = formula_composer.compose_chain(
|
|
930
|
+
signals=signals,
|
|
931
|
+
situation_id=situation_id or None,
|
|
932
|
+
preset_min_score=None if preset_min_score < 0 else preset_min_score,
|
|
933
|
+
)
|
|
934
|
+
sid = session_id or "anon"
|
|
935
|
+
# Emit branch + compose_done traces AND stamp the chain to the
|
|
936
|
+
# agent-scoped state files the Hub /api/roles panel reads. Both live in
|
|
937
|
+
# roles_state so this MCP path and the auto-compose hook
|
|
938
|
+
# (hooks/_helpers/auto_compose.py) never drift on what the panel reads
|
|
939
|
+
# — the drift that left the panel empty.
|
|
940
|
+
try:
|
|
941
|
+
import roles_state
|
|
942
|
+
|
|
943
|
+
roles_state.record_compose_traces(chain, sid)
|
|
944
|
+
roles_state.stamp_roles(chain.chain)
|
|
945
|
+
except Exception as exc: # fire-and-forget telemetry — a write/serialize
|
|
946
|
+
logger.debug("roles trace/state write failed: %s", exc) # error must never fail compose
|
|
947
|
+
|
|
948
|
+
# telemetry — persist the lead persona for the dispatch.
|
|
949
|
+
# Schema (migration v14): one row per compose_chain call.
|
|
950
|
+
try:
|
|
951
|
+
import sqlite3 as _sqlite
|
|
952
|
+
|
|
953
|
+
_conn = _sqlite.connect(db_path)
|
|
954
|
+
try:
|
|
955
|
+
lead_persona = chain.chain[0] if chain.chain else "unknown"
|
|
956
|
+
# Confidence proxy: preset_matched > situation_override >
|
|
957
|
+
# composer_fallback > hard_fallback (1.0 / 0.85 / 0.7 / 0.4).
|
|
958
|
+
conf_map = {"preset": 1.0, "situation": 0.85, "composer": 0.7, "hard": 0.4}
|
|
959
|
+
conf = conf_map.get(chain.source, 0.5)
|
|
960
|
+
# Intensity is per-role; use lead role's first step as the
|
|
961
|
+
# session-level signal (pragmatic — full breakdown lives in
|
|
962
|
+
# the trace event).
|
|
963
|
+
intensity = "default"
|
|
964
|
+
_conn.execute(
|
|
965
|
+
"INSERT INTO persona_selections "
|
|
966
|
+
"(session_id, task_marker, persona_id, confidence, reason, intensity) "
|
|
967
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
968
|
+
(sid, chain.preset_id or "", lead_persona, conf, chain.source, intensity),
|
|
969
|
+
)
|
|
970
|
+
_conn.commit()
|
|
971
|
+
finally:
|
|
972
|
+
_conn.close()
|
|
973
|
+
except Exception as exc:
|
|
974
|
+
logger.debug("persona_selections insert failed: %s", exc)
|
|
975
|
+
return ok(
|
|
976
|
+
chain.model_dump(),
|
|
977
|
+
meta={"layer": "routing", "source": "formula_composer"},
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
return cos_compose_chain
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def register_cos_role_info(mcp, db_path):
|
|
984
|
+
@mcp.tool(
|
|
985
|
+
name="cos_role_info",
|
|
986
|
+
description=(
|
|
987
|
+
"Return metadata for a formula-role (researcher..refactorer): prompt_prefix, "
|
|
988
|
+
"tools_budget, intensity_steps, backtrack_triggers, "
|
|
989
|
+
"criteria_required. Useful for the main agent before dispatch."
|
|
990
|
+
),
|
|
991
|
+
)
|
|
992
|
+
@safe_tool
|
|
993
|
+
def cos_role_info(role_id: str) -> str:
|
|
994
|
+
import formula_composer # lazy
|
|
995
|
+
|
|
996
|
+
roles = formula_composer.load_roles()
|
|
997
|
+
role = roles.get(role_id)
|
|
998
|
+
if role is None:
|
|
999
|
+
return fail("not_found", f"unknown role_id: {role_id}")
|
|
1000
|
+
keep = {
|
|
1001
|
+
k: role.get(k)
|
|
1002
|
+
for k in (
|
|
1003
|
+
"id",
|
|
1004
|
+
"role_name",
|
|
1005
|
+
"formula_ref",
|
|
1006
|
+
"agent_file",
|
|
1007
|
+
"intensity_steps",
|
|
1008
|
+
"tools_budget",
|
|
1009
|
+
"max_tokens_in",
|
|
1010
|
+
"max_tokens_out",
|
|
1011
|
+
"timeout_s",
|
|
1012
|
+
"model_pref",
|
|
1013
|
+
"backtrack_triggers",
|
|
1014
|
+
"criteria_required",
|
|
1015
|
+
"prompt_prefix",
|
|
1016
|
+
"parallel_dispatch",
|
|
1017
|
+
)
|
|
1018
|
+
if k in role
|
|
1019
|
+
}
|
|
1020
|
+
return ok(keep, meta={"layer": "routing", "source": "role_registry"})
|
|
1021
|
+
|
|
1022
|
+
return cos_role_info
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
# ---------------------------------------------------------------------------
|
|
1026
|
+
# cos_dispatch_formula_run — real-dispatch tool
|
|
1027
|
+
# ---------------------------------------------------------------------------
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def _persist_dispatch_output(
|
|
1031
|
+
*,
|
|
1032
|
+
session_id: str,
|
|
1033
|
+
task_marker: str,
|
|
1034
|
+
persona_id: str,
|
|
1035
|
+
formula_id: str,
|
|
1036
|
+
output_json: dict,
|
|
1037
|
+
status: str,
|
|
1038
|
+
latency_ms: int,
|
|
1039
|
+
db_path: str,
|
|
1040
|
+
raw_transcript: str | None = None,
|
|
1041
|
+
) -> int:
|
|
1042
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
1043
|
+
# Data-driven role → bundle field + Pydantic class resolution.
|
|
1044
|
+
# Role frontmatter declares `output_schema: cognition.<X>Output`;
|
|
1045
|
+
# `bundle_field` defaults to role id but can be overridden in frontmatter.
|
|
1046
|
+
field, cls = _resolve_role_persistence(formula_id)
|
|
1047
|
+
validation_failed = False
|
|
1048
|
+
if field and cls and status == "ok":
|
|
1049
|
+
try:
|
|
1050
|
+
parsed = cls.model_validate(output_json)
|
|
1051
|
+
setattr(bundle, field, parsed)
|
|
1052
|
+
except Exception as exc:
|
|
1053
|
+
# T1.6 — Pydantic validation runs BEFORE persistence.
|
|
1054
|
+
# On failure: mark degraded, skip the formula_dispatches INSERT
|
|
1055
|
+
# entirely (row would carry untrusted output_hash). Bundle still
|
|
1056
|
+
# saved with degraded_formulas marker so the supervisor can
|
|
1057
|
+
# backtrack.
|
|
1058
|
+
logger.warning(
|
|
1059
|
+
"Failed to validate %s output against schema: %s",
|
|
1060
|
+
formula_id,
|
|
1061
|
+
exc,
|
|
1062
|
+
)
|
|
1063
|
+
bundle.degraded_formulas.append(formula_id)
|
|
1064
|
+
status = "fail"
|
|
1065
|
+
validation_failed = True
|
|
1066
|
+
elif status == "timeout":
|
|
1067
|
+
bundle.degraded_formulas.append(formula_id)
|
|
1068
|
+
|
|
1069
|
+
_save_bundle(session_id, bundle)
|
|
1070
|
+
|
|
1071
|
+
# T1.6: skip the dispatch row INSERT when schema validation failed.
|
|
1072
|
+
# Returning the bundle field count keeps the caller signature stable.
|
|
1073
|
+
if validation_failed:
|
|
1074
|
+
return sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
|
|
1075
|
+
|
|
1076
|
+
raw = json.dumps(output_json, sort_keys=True, default=str).encode()
|
|
1077
|
+
output_hash = hashlib.sha256(raw).hexdigest()[:16]
|
|
1078
|
+
input_hash = hashlib.sha256(f"{session_id}:{formula_id}".encode()).hexdigest()[:16]
|
|
1079
|
+
# pull telemetry the Claude dispatcher stamps
|
|
1080
|
+
# into output_json["_meta"] (cost_usd, usage, model_usage, tool_calls,
|
|
1081
|
+
# tool_failures) and persist into the v23 columns. JSON-encoded so
|
|
1082
|
+
# the schema migration stays append-only — readers parse on demand.
|
|
1083
|
+
meta = output_json.get("_meta") if isinstance(output_json, dict) else None
|
|
1084
|
+
meta = meta if isinstance(meta, dict) else {}
|
|
1085
|
+
cost_usd_raw = meta.get("total_cost_usd")
|
|
1086
|
+
if isinstance(cost_usd_raw, (int, float)):
|
|
1087
|
+
cost_usd_val: float | None = float(cost_usd_raw)
|
|
1088
|
+
else:
|
|
1089
|
+
cost_usd_val = None
|
|
1090
|
+
|
|
1091
|
+
def _jsonb(value: Any) -> str | None:
|
|
1092
|
+
if value is None:
|
|
1093
|
+
return None
|
|
1094
|
+
try:
|
|
1095
|
+
return json.dumps(value, default=str, sort_keys=True)
|
|
1096
|
+
except (TypeError, ValueError):
|
|
1097
|
+
return None
|
|
1098
|
+
|
|
1099
|
+
try:
|
|
1100
|
+
with sqlite3.connect(db_path) as conn:
|
|
1101
|
+
conn.execute(
|
|
1102
|
+
"INSERT INTO formula_dispatches "
|
|
1103
|
+
"(session_id, task_marker, persona_id, formula_id, input_hash, "
|
|
1104
|
+
"output_hash, latency_ms, status, ts, "
|
|
1105
|
+
"cost_usd, budget_usd, usage_jsonb, model_usage_jsonb, "
|
|
1106
|
+
"tool_calls_jsonb, tool_failures_jsonb, "
|
|
1107
|
+
"sub_session_id, model, checkpoints_jsonb, error, raw_transcript) "
|
|
1108
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
1109
|
+
(
|
|
1110
|
+
session_id,
|
|
1111
|
+
task_marker,
|
|
1112
|
+
persona_id,
|
|
1113
|
+
formula_id,
|
|
1114
|
+
input_hash,
|
|
1115
|
+
output_hash,
|
|
1116
|
+
latency_ms,
|
|
1117
|
+
status,
|
|
1118
|
+
_now_iso(),
|
|
1119
|
+
cost_usd_val,
|
|
1120
|
+
meta.get("budget_usd"),
|
|
1121
|
+
_jsonb(meta.get("usage")),
|
|
1122
|
+
_jsonb(meta.get("model_usage")),
|
|
1123
|
+
_jsonb(meta.get("tool_calls")),
|
|
1124
|
+
_jsonb(meta.get("tool_failures")),
|
|
1125
|
+
meta.get("session_id"),
|
|
1126
|
+
meta.get("model"),
|
|
1127
|
+
_jsonb(meta.get("checkpoints")),
|
|
1128
|
+
str(meta.get("error"))[:1000] if meta.get("error") else None,
|
|
1129
|
+
raw_transcript[:50000] if raw_transcript else None,
|
|
1130
|
+
),
|
|
1131
|
+
)
|
|
1132
|
+
except Exception as exc:
|
|
1133
|
+
logger.debug("formula_dispatches insert failed: %s", exc)
|
|
1134
|
+
|
|
1135
|
+
return sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def _emit_dispatch_metrics_safe(
|
|
1139
|
+
*,
|
|
1140
|
+
db_path: str,
|
|
1141
|
+
formula_id: str,
|
|
1142
|
+
status: str,
|
|
1143
|
+
latency_ms: int,
|
|
1144
|
+
output_json: dict,
|
|
1145
|
+
) -> None:
|
|
1146
|
+
import sqlite3 as _sqlite3
|
|
1147
|
+
|
|
1148
|
+
try:
|
|
1149
|
+
from tools.metrics import metric_record # type: ignore[import]
|
|
1150
|
+
|
|
1151
|
+
_outcome_map = {
|
|
1152
|
+
"ok": "success",
|
|
1153
|
+
"timeout": "blocked",
|
|
1154
|
+
"error": "partial",
|
|
1155
|
+
"skipped": "partial",
|
|
1156
|
+
}
|
|
1157
|
+
outcome = _outcome_map.get(status, "partial")
|
|
1158
|
+
meta = output_json.get("_meta") if isinstance(output_json, dict) else {}
|
|
1159
|
+
meta = meta if isinstance(meta, dict) else {}
|
|
1160
|
+
model_used = meta.get("model") or None
|
|
1161
|
+
with _sqlite3.connect(db_path) as conn:
|
|
1162
|
+
metric_record(
|
|
1163
|
+
conn,
|
|
1164
|
+
agent_type="dispatch",
|
|
1165
|
+
outcome=outcome,
|
|
1166
|
+
duration_ms=latency_ms,
|
|
1167
|
+
domain=formula_id,
|
|
1168
|
+
model=model_used,
|
|
1169
|
+
complexity="CLEAR",
|
|
1170
|
+
)
|
|
1171
|
+
except Exception as exc:
|
|
1172
|
+
logger.debug("dispatch metric emit failed: %s", exc)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def _preset_role_hint(session_id: str, formula_id: str, db_path) -> dict:
|
|
1176
|
+
# Tier-2 lookup (claude-sdk.md §7.3): the session's composed preset is
|
|
1177
|
+
# read back from persona_selections (preset_id lives in its task_marker
|
|
1178
|
+
# column — see register_cos_compose_chain's INSERT), then that preset's
|
|
1179
|
+
# roles_adapter_hints[formula_id]. Fail-open: hints are advisory.
|
|
1180
|
+
if not db_path:
|
|
1181
|
+
return {}
|
|
1182
|
+
try:
|
|
1183
|
+
import sqlite3 as _sqlite3
|
|
1184
|
+
|
|
1185
|
+
conn = _sqlite3.connect(db_path)
|
|
1186
|
+
try:
|
|
1187
|
+
row = conn.execute(
|
|
1188
|
+
"SELECT task_marker FROM persona_selections WHERE session_id = ? "
|
|
1189
|
+
"ORDER BY rowid DESC LIMIT 1",
|
|
1190
|
+
(session_id,),
|
|
1191
|
+
).fetchone()
|
|
1192
|
+
finally:
|
|
1193
|
+
conn.close()
|
|
1194
|
+
preset_id = row[0] if row and row[0] else ""
|
|
1195
|
+
if not preset_id:
|
|
1196
|
+
return {}
|
|
1197
|
+
import formula_composer
|
|
1198
|
+
|
|
1199
|
+
presets, _version = formula_composer.load_presets()
|
|
1200
|
+
preset = next((p for p in presets if p.get("id") == preset_id), {})
|
|
1201
|
+
hint = (preset.get("roles_adapter_hints") or {}).get(formula_id) or {}
|
|
1202
|
+
return hint if isinstance(hint, dict) else {}
|
|
1203
|
+
except Exception as exc:
|
|
1204
|
+
logger.debug("preset hint lookup failed: %s", exc)
|
|
1205
|
+
return {}
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
def _empirical_model(complexity: str, db_path) -> str:
|
|
1209
|
+
# Tier-4: cos_route_model's recommendation, only when real outcome
|
|
1210
|
+
# history backs it (cold-start static defaults must not override the
|
|
1211
|
+
# SDK default for every dispatch).
|
|
1212
|
+
if not db_path or not complexity.strip():
|
|
1213
|
+
return ""
|
|
1214
|
+
try:
|
|
1215
|
+
import sqlite3 as _sqlite3
|
|
1216
|
+
|
|
1217
|
+
from tools.routing import route_model_bandit
|
|
1218
|
+
|
|
1219
|
+
conn = _sqlite3.connect(db_path)
|
|
1220
|
+
try:
|
|
1221
|
+
result = route_model_bandit(conn, complexity=complexity.strip().upper())
|
|
1222
|
+
finally:
|
|
1223
|
+
conn.close()
|
|
1224
|
+
if int(result.get("data_points") or 0) > 0:
|
|
1225
|
+
return str(result.get("recommended_model") or "")
|
|
1226
|
+
return ""
|
|
1227
|
+
except Exception as exc:
|
|
1228
|
+
logger.debug("empirical model lookup failed: %s", exc)
|
|
1229
|
+
return ""
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
def _resolve_dispatch_model(
|
|
1233
|
+
formula_id: str,
|
|
1234
|
+
session_id: str,
|
|
1235
|
+
meta: dict,
|
|
1236
|
+
model: str,
|
|
1237
|
+
complexity: str,
|
|
1238
|
+
db_path,
|
|
1239
|
+
) -> str:
|
|
1240
|
+
level = complexity.strip().lower()
|
|
1241
|
+
hint_pref = _preset_role_hint(session_id, formula_id, db_path).get("model_pref") or {}
|
|
1242
|
+
role_pref = meta.get("model_pref") or {}
|
|
1243
|
+
resolved = ""
|
|
1244
|
+
for candidate, source in (
|
|
1245
|
+
(model.strip(), "explicit"),
|
|
1246
|
+
(hint_pref.get(level, ""), "preset_hint"),
|
|
1247
|
+
(role_pref.get(level, ""), "role_pref"),
|
|
1248
|
+
):
|
|
1249
|
+
if candidate:
|
|
1250
|
+
logger.info("dispatch model resolved for %s: %s via %s", formula_id, candidate, source)
|
|
1251
|
+
resolved = candidate
|
|
1252
|
+
break
|
|
1253
|
+
if not resolved:
|
|
1254
|
+
# Empirical is the expensive tier (sqlite connect + bandit query/sampling)
|
|
1255
|
+
# — consult it lazily, only when the static tiers all miss.
|
|
1256
|
+
empirical = _empirical_model(complexity, db_path)
|
|
1257
|
+
if empirical:
|
|
1258
|
+
logger.info("dispatch model resolved for %s: %s via empirical", formula_id, empirical)
|
|
1259
|
+
resolved = empirical
|
|
1260
|
+
# Cost-routed independent reviewer: a review role runs one tier cheaper than
|
|
1261
|
+
# the generator. Gated by COS_ROUTER_REVIEWER_CHEAPER (default off, unchanged).
|
|
1262
|
+
if resolved and os.environ.get("COS_ROUTER_REVIEWER_CHEAPER"):
|
|
1263
|
+
from tools.routing import _REVIEW_ROLES, reviewer_model
|
|
1264
|
+
|
|
1265
|
+
if formula_id in _REVIEW_ROLES:
|
|
1266
|
+
cheaper = reviewer_model(resolved)
|
|
1267
|
+
if cheaper and cheaper != resolved:
|
|
1268
|
+
logger.info("reviewer %s downgraded to cheaper tier %s", formula_id, cheaper)
|
|
1269
|
+
resolved = cheaper
|
|
1270
|
+
return resolved
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
def _build_dispatch_request(
|
|
1274
|
+
formula_id: str,
|
|
1275
|
+
session_id: str,
|
|
1276
|
+
task_marker: str,
|
|
1277
|
+
persona_id: str,
|
|
1278
|
+
intensity: str,
|
|
1279
|
+
timeout_s: float | None,
|
|
1280
|
+
model: str = "",
|
|
1281
|
+
complexity: str = "",
|
|
1282
|
+
db_path=None,
|
|
1283
|
+
):
|
|
1284
|
+
"""Build a DispatchRequest from session state (shared by run-one and run-parallel)."""
|
|
1285
|
+
from thinking_os import dispatcher as _disp # lazy: avoid circular at import time
|
|
1286
|
+
|
|
1287
|
+
cog = _cog()
|
|
1288
|
+
agents = cog.load_agent_registry()
|
|
1289
|
+
meta = agents.get(formula_id) or {}
|
|
1290
|
+
agent_file_rel = f"src/core/thinking_os/agents/{meta.get('_file', f'{formula_id}.md')}"
|
|
1291
|
+
agent_path = Path(__file__).resolve().parent.parent / "agents" / meta.get("_file", "")
|
|
1292
|
+
prompt_text = agent_path.read_text() if agent_path.exists() else ""
|
|
1293
|
+
|
|
1294
|
+
bundle = _load_bundle(session_id, task_marker, persona_id)
|
|
1295
|
+
input_slice = cog.build_input_slice(formula_id, bundle)
|
|
1296
|
+
input_slice["intensity_steps"] = cog._intensity_steps(formula_id, intensity)
|
|
1297
|
+
|
|
1298
|
+
resolved_model = _resolve_dispatch_model(
|
|
1299
|
+
formula_id, session_id, meta, model, complexity, db_path
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
return _disp.DispatchRequest(
|
|
1303
|
+
formula_id=formula_id,
|
|
1304
|
+
agent_file=str(agent_path if agent_path.exists() else agent_file_rel),
|
|
1305
|
+
prompt=prompt_text,
|
|
1306
|
+
input_slice=input_slice,
|
|
1307
|
+
persona_id=persona_id,
|
|
1308
|
+
intensity=intensity if intensity in ("light", "standard", "full") else "standard",
|
|
1309
|
+
timeout_s=float(timeout_s) if timeout_s else float(meta.get("timeout_s", 300)),
|
|
1310
|
+
session_id=session_id,
|
|
1311
|
+
long_context=bool(meta.get("long_context", False)),
|
|
1312
|
+
model=resolved_model or None,
|
|
1313
|
+
)
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def register_cos_dispatch_formula_run(mcp, db_path):
|
|
1317
|
+
@mcp.tool(
|
|
1318
|
+
name="cos_dispatch_formula_run",
|
|
1319
|
+
description=(
|
|
1320
|
+
"EXPLICIT, OPT-IN sub-agent spawn for one role. Costs ~5k tokens "
|
|
1321
|
+
"per call (system prompt + input slice + completion) and rebuilds "
|
|
1322
|
+
"context inside the sub-agent. PREFER lazy-loading: read "
|
|
1323
|
+
"src/core/thinking_os/agents/<role>.md inline and produce the output "
|
|
1324
|
+
"schema yourself — same accuracy, far fewer tokens, no context "
|
|
1325
|
+
"rebuild penalty. Use this tool only when (a) the role's work is "
|
|
1326
|
+
"long-running and would dominate the main loop, or (b) you "
|
|
1327
|
+
"explicitly want a separate session for parallelism. If no SDK is "
|
|
1328
|
+
"available, returns status='skipped' and the main agent should "
|
|
1329
|
+
"execute the role's procedure inline."
|
|
1330
|
+
),
|
|
1331
|
+
)
|
|
1332
|
+
@safe_tool
|
|
1333
|
+
def cos_dispatch_formula_run(
|
|
1334
|
+
formula_id: str,
|
|
1335
|
+
session_id: str,
|
|
1336
|
+
task_marker: str,
|
|
1337
|
+
persona_id: str,
|
|
1338
|
+
intensity: str = "standard",
|
|
1339
|
+
timeout_s: float | None = None,
|
|
1340
|
+
model: str = "",
|
|
1341
|
+
complexity: str = "",
|
|
1342
|
+
) -> str:
|
|
1343
|
+
import asyncio as _asyncio
|
|
1344
|
+
|
|
1345
|
+
from thinking_os import budget as _budget, dispatcher as _disp
|
|
1346
|
+
|
|
1347
|
+
gate = _budget.check(db_path)
|
|
1348
|
+
if not gate.allowed:
|
|
1349
|
+
return fail("budget", gate.reason)
|
|
1350
|
+
chain_gate = _budget.chain_check(db_path, task_marker)
|
|
1351
|
+
if not chain_gate.allowed:
|
|
1352
|
+
return fail("budget", chain_gate.reason)
|
|
1353
|
+
|
|
1354
|
+
try:
|
|
1355
|
+
req = _build_dispatch_request(
|
|
1356
|
+
formula_id,
|
|
1357
|
+
session_id,
|
|
1358
|
+
task_marker,
|
|
1359
|
+
persona_id,
|
|
1360
|
+
intensity,
|
|
1361
|
+
timeout_s,
|
|
1362
|
+
model,
|
|
1363
|
+
complexity,
|
|
1364
|
+
db_path,
|
|
1365
|
+
)
|
|
1366
|
+
except Exception as exc:
|
|
1367
|
+
return fail("validation", f"failed to build request: {exc}")
|
|
1368
|
+
|
|
1369
|
+
d = _disp.get_dispatcher(request=req)
|
|
1370
|
+
|
|
1371
|
+
# Trace event — visible in cos cognition trace replay so the
|
|
1372
|
+
# flowchart shows the actual sub-agent execution span.
|
|
1373
|
+
try:
|
|
1374
|
+
import tracing
|
|
1375
|
+
|
|
1376
|
+
tracing.emit(
|
|
1377
|
+
session_id,
|
|
1378
|
+
"dispatch_started",
|
|
1379
|
+
{
|
|
1380
|
+
"formula_id": formula_id,
|
|
1381
|
+
"dispatcher_name": getattr(d, "name", "unknown"),
|
|
1382
|
+
"intensity": intensity,
|
|
1383
|
+
"model": req.model,
|
|
1384
|
+
"long_context": req.long_context,
|
|
1385
|
+
},
|
|
1386
|
+
role=formula_id,
|
|
1387
|
+
phase="EXECUTE",
|
|
1388
|
+
)
|
|
1389
|
+
except Exception as exc:
|
|
1390
|
+
logger.debug("dispatch_started trace emit failed: %s", exc)
|
|
1391
|
+
|
|
1392
|
+
try:
|
|
1393
|
+
result = _asyncio.run(d.dispatch(req))
|
|
1394
|
+
except RuntimeError as exc:
|
|
1395
|
+
# Nested loop — fall back to a fresh thread-owned loop
|
|
1396
|
+
if "already running" in str(exc):
|
|
1397
|
+
import threading
|
|
1398
|
+
|
|
1399
|
+
box: dict = {}
|
|
1400
|
+
|
|
1401
|
+
def _runner():
|
|
1402
|
+
loop = _asyncio.new_event_loop()
|
|
1403
|
+
try:
|
|
1404
|
+
box["result"] = loop.run_until_complete(d.dispatch(req))
|
|
1405
|
+
finally:
|
|
1406
|
+
loop.close()
|
|
1407
|
+
|
|
1408
|
+
t = threading.Thread(target=_runner, daemon=True)
|
|
1409
|
+
t.start()
|
|
1410
|
+
t.join(timeout=req.timeout_s + 10)
|
|
1411
|
+
if "result" not in box:
|
|
1412
|
+
return fail("transient", "dispatcher thread did not return")
|
|
1413
|
+
result = box["result"]
|
|
1414
|
+
else:
|
|
1415
|
+
return fail("internal", f"asyncio.run failed: {exc}")
|
|
1416
|
+
|
|
1417
|
+
filled = 0
|
|
1418
|
+
if result.status in ("ok", "timeout") and result.output_json:
|
|
1419
|
+
filled = _persist_dispatch_output(
|
|
1420
|
+
session_id=session_id,
|
|
1421
|
+
task_marker=task_marker,
|
|
1422
|
+
persona_id=persona_id,
|
|
1423
|
+
formula_id=formula_id,
|
|
1424
|
+
output_json=result.output_json,
|
|
1425
|
+
status=result.status,
|
|
1426
|
+
latency_ms=result.latency_ms,
|
|
1427
|
+
db_path=db_path,
|
|
1428
|
+
raw_transcript=result.raw_transcript,
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
# T2.5 + T8.4: emit dispatch cost and duration as coding-os metrics
|
|
1432
|
+
# so cos_metric_trend surfaces spend over time.
|
|
1433
|
+
_emit_dispatch_metrics_safe(
|
|
1434
|
+
db_path=db_path,
|
|
1435
|
+
formula_id=formula_id,
|
|
1436
|
+
status=result.status,
|
|
1437
|
+
latency_ms=result.latency_ms,
|
|
1438
|
+
output_json=result.output_json,
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
# Trace event — pairs with dispatch_started above so the cognition
|
|
1442
|
+
# replay shows the full sub-agent execution span (not just supervisor
|
|
1443
|
+
# routing decision).
|
|
1444
|
+
try:
|
|
1445
|
+
import tracing
|
|
1446
|
+
|
|
1447
|
+
_meta = result.output_json.get("_meta") if isinstance(result.output_json, dict) else {}
|
|
1448
|
+
_meta = _meta if isinstance(_meta, dict) else {}
|
|
1449
|
+
tracing.emit(
|
|
1450
|
+
session_id,
|
|
1451
|
+
"dispatch_completed",
|
|
1452
|
+
{
|
|
1453
|
+
"formula_id": formula_id,
|
|
1454
|
+
"status": result.status,
|
|
1455
|
+
"latency_ms": result.latency_ms,
|
|
1456
|
+
"cost_usd": _meta.get("total_cost_usd"),
|
|
1457
|
+
"sub_session_id": _meta.get("session_id"),
|
|
1458
|
+
"model": _meta.get("model"),
|
|
1459
|
+
"bundle_fields_filled": filled,
|
|
1460
|
+
"error": result.error,
|
|
1461
|
+
},
|
|
1462
|
+
role=formula_id,
|
|
1463
|
+
phase="EXECUTE",
|
|
1464
|
+
)
|
|
1465
|
+
except Exception as exc:
|
|
1466
|
+
logger.debug("dispatch_completed trace emit failed: %s", exc)
|
|
1467
|
+
|
|
1468
|
+
return ok(
|
|
1469
|
+
{
|
|
1470
|
+
"status": result.status,
|
|
1471
|
+
"formula_id": result.formula_id,
|
|
1472
|
+
"dispatcher_name": result.dispatcher_name,
|
|
1473
|
+
"latency_ms": result.latency_ms,
|
|
1474
|
+
"output_json": result.output_json,
|
|
1475
|
+
"error": result.error,
|
|
1476
|
+
"bundle_fields_filled": filled,
|
|
1477
|
+
},
|
|
1478
|
+
meta={"layer": "dispatch"},
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
return cos_dispatch_formula_run
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
def register_cos_dispatch_parallel_run(mcp, db_path):
|
|
1485
|
+
@mcp.tool(
|
|
1486
|
+
name="cos_dispatch_parallel_run",
|
|
1487
|
+
description=(
|
|
1488
|
+
"Spawn multiple formula-agents concurrently via asyncio.gather. "
|
|
1489
|
+
"Use when the supervisor returns action='dispatch_parallel' "
|
|
1490
|
+
"(e.g. security_auditor layers). Each output is persisted to the bundle. "
|
|
1491
|
+
"Returns list of DispatchResults in input order."
|
|
1492
|
+
),
|
|
1493
|
+
)
|
|
1494
|
+
@safe_tool
|
|
1495
|
+
def cos_dispatch_parallel_run(
|
|
1496
|
+
formula_ids: list[str],
|
|
1497
|
+
session_id: str,
|
|
1498
|
+
task_marker: str,
|
|
1499
|
+
persona_id: str,
|
|
1500
|
+
intensity: str = "standard",
|
|
1501
|
+
timeout_s: float | None = None,
|
|
1502
|
+
model: str = "",
|
|
1503
|
+
complexity: str = "",
|
|
1504
|
+
) -> str:
|
|
1505
|
+
import asyncio as _asyncio
|
|
1506
|
+
|
|
1507
|
+
from thinking_os import budget as _budget, dispatcher as _disp
|
|
1508
|
+
|
|
1509
|
+
if not formula_ids:
|
|
1510
|
+
return fail("validation", "formula_ids must be non-empty")
|
|
1511
|
+
|
|
1512
|
+
# One check authorizes all N concurrent spawns, so it must gate on the
|
|
1513
|
+
# projected total — spent-only lets the fan-out overrun the cap.
|
|
1514
|
+
estimate = _budget.estimate_dispatch_cost(db_path, len(formula_ids))
|
|
1515
|
+
gate = _budget.check(db_path, additional_estimate_usd=estimate)
|
|
1516
|
+
if not gate.allowed:
|
|
1517
|
+
return fail("budget", gate.reason)
|
|
1518
|
+
chain_gate = _budget.chain_check(db_path, task_marker, additional_estimate_usd=estimate)
|
|
1519
|
+
if not chain_gate.allowed:
|
|
1520
|
+
return fail("budget", chain_gate.reason)
|
|
1521
|
+
|
|
1522
|
+
try:
|
|
1523
|
+
requests = [
|
|
1524
|
+
_build_dispatch_request(
|
|
1525
|
+
fid,
|
|
1526
|
+
session_id,
|
|
1527
|
+
task_marker,
|
|
1528
|
+
persona_id,
|
|
1529
|
+
intensity,
|
|
1530
|
+
timeout_s,
|
|
1531
|
+
model,
|
|
1532
|
+
complexity,
|
|
1533
|
+
db_path,
|
|
1534
|
+
)
|
|
1535
|
+
for fid in formula_ids
|
|
1536
|
+
]
|
|
1537
|
+
except Exception as exc:
|
|
1538
|
+
return fail("validation", f"failed to build requests: {exc}")
|
|
1539
|
+
|
|
1540
|
+
d = _disp.get_dispatcher()
|
|
1541
|
+
|
|
1542
|
+
async def _gather_all():
|
|
1543
|
+
return await _asyncio.gather(
|
|
1544
|
+
*(d.dispatch(req) for req in requests),
|
|
1545
|
+
return_exceptions=True,
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
import time as _time
|
|
1549
|
+
|
|
1550
|
+
t0 = _time.monotonic()
|
|
1551
|
+
try:
|
|
1552
|
+
gathered = _asyncio.run(_gather_all())
|
|
1553
|
+
except RuntimeError:
|
|
1554
|
+
# Nested-loop fallback: run in a dedicated thread with fresh loop
|
|
1555
|
+
import threading
|
|
1556
|
+
|
|
1557
|
+
box: dict = {}
|
|
1558
|
+
|
|
1559
|
+
def _runner():
|
|
1560
|
+
loop = _asyncio.new_event_loop()
|
|
1561
|
+
try:
|
|
1562
|
+
box["result"] = loop.run_until_complete(_gather_all())
|
|
1563
|
+
finally:
|
|
1564
|
+
loop.close()
|
|
1565
|
+
|
|
1566
|
+
t = threading.Thread(target=_runner, daemon=True)
|
|
1567
|
+
t.start()
|
|
1568
|
+
deadline = max(req.timeout_s for req in requests) + 10
|
|
1569
|
+
t.join(timeout=deadline)
|
|
1570
|
+
gathered = box.get("result", [])
|
|
1571
|
+
wall_ms = int((_time.monotonic() - t0) * 1000)
|
|
1572
|
+
|
|
1573
|
+
results = []
|
|
1574
|
+
ok_count = 0
|
|
1575
|
+
for req, outcome in zip(requests, gathered):
|
|
1576
|
+
if isinstance(outcome, Exception):
|
|
1577
|
+
results.append(
|
|
1578
|
+
{
|
|
1579
|
+
"status": "error",
|
|
1580
|
+
"formula_id": req.formula_id,
|
|
1581
|
+
"error": f"{type(outcome).__name__}: {outcome}",
|
|
1582
|
+
"dispatcher_name": d.name,
|
|
1583
|
+
"latency_ms": 0,
|
|
1584
|
+
"output_json": {},
|
|
1585
|
+
"bundle_fields_filled": 0,
|
|
1586
|
+
}
|
|
1587
|
+
)
|
|
1588
|
+
continue
|
|
1589
|
+
filled = 0
|
|
1590
|
+
if outcome.status == "ok" and outcome.output_json:
|
|
1591
|
+
filled = _persist_dispatch_output(
|
|
1592
|
+
session_id=session_id,
|
|
1593
|
+
task_marker=task_marker,
|
|
1594
|
+
persona_id=persona_id,
|
|
1595
|
+
formula_id=outcome.formula_id,
|
|
1596
|
+
output_json=outcome.output_json,
|
|
1597
|
+
status=outcome.status,
|
|
1598
|
+
latency_ms=outcome.latency_ms,
|
|
1599
|
+
db_path=db_path,
|
|
1600
|
+
raw_transcript=outcome.raw_transcript,
|
|
1601
|
+
)
|
|
1602
|
+
ok_count += 1
|
|
1603
|
+
results.append(
|
|
1604
|
+
{
|
|
1605
|
+
"status": outcome.status,
|
|
1606
|
+
"formula_id": outcome.formula_id,
|
|
1607
|
+
"dispatcher_name": outcome.dispatcher_name,
|
|
1608
|
+
"latency_ms": outcome.latency_ms,
|
|
1609
|
+
"output_json": outcome.output_json,
|
|
1610
|
+
"error": outcome.error,
|
|
1611
|
+
"bundle_fields_filled": filled,
|
|
1612
|
+
}
|
|
1613
|
+
)
|
|
1614
|
+
|
|
1615
|
+
return ok(
|
|
1616
|
+
{
|
|
1617
|
+
"results": results,
|
|
1618
|
+
"parallel_wall_ms": wall_ms,
|
|
1619
|
+
"ok_count": ok_count,
|
|
1620
|
+
"total": len(results),
|
|
1621
|
+
},
|
|
1622
|
+
meta={"layer": "dispatch"},
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
return cos_dispatch_parallel_run
|
|
1626
|
+
|
|
1627
|
+
|
|
1628
|
+
def classify_prompt_heuristic(prompt: str) -> dict:
|
|
1629
|
+
"""Deterministic Cynefin + dimensions heuristic — shared by the
|
|
1630
|
+
cos_classify_prompt tool and the hub chat auto-router (no LLM call)."""
|
|
1631
|
+
import re as _re
|
|
1632
|
+
|
|
1633
|
+
text = (prompt or "").strip().lower()
|
|
1634
|
+
if not text:
|
|
1635
|
+
return {
|
|
1636
|
+
"complexity": "CLEAR",
|
|
1637
|
+
"dimensions": 1,
|
|
1638
|
+
"signals": [],
|
|
1639
|
+
"hit_domains": [],
|
|
1640
|
+
"reasoning": "empty prompt",
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
signals: list[str] = []
|
|
1644
|
+
complexity = "CLEAR"
|
|
1645
|
+
|
|
1646
|
+
chaotic_re = _re.compile(
|
|
1647
|
+
r"\b(p0|p1|outage|down|broken|crashed?|emergency|urgent|"
|
|
1648
|
+
r"fire|on[- ]call|paged|rollback (?:now|asap))\b"
|
|
1649
|
+
)
|
|
1650
|
+
if chaotic_re.search(text):
|
|
1651
|
+
complexity = "CHAOTIC"
|
|
1652
|
+
signals.append("incident-language")
|
|
1653
|
+
|
|
1654
|
+
complex_re = _re.compile(
|
|
1655
|
+
r"\b(best way|explore|experiment|optimi[sz]e|research|novel|"
|
|
1656
|
+
r"investigate|figure out|prototype|spike|trade[- ]off|benchmark)\b"
|
|
1657
|
+
)
|
|
1658
|
+
if complexity == "CLEAR" and complex_re.search(text):
|
|
1659
|
+
complexity = "COMPLEX"
|
|
1660
|
+
signals.append("exploratory-language")
|
|
1661
|
+
|
|
1662
|
+
complicated_re = _re.compile(
|
|
1663
|
+
r"\b(design|architect|integrate|refactor|implement|build|migrat\w*|"
|
|
1664
|
+
r"split|merge|extract|generali[sz]e|extend|orchestrat\w*|"
|
|
1665
|
+
r"normali[sz]e|denormali[sz]e)\b"
|
|
1666
|
+
)
|
|
1667
|
+
if complexity == "CLEAR" and complicated_re.search(text):
|
|
1668
|
+
complexity = "COMPLICATED"
|
|
1669
|
+
signals.append("design-language")
|
|
1670
|
+
|
|
1671
|
+
word_count = len(text.split())
|
|
1672
|
+
if complexity == "CLEAR" and word_count > 60:
|
|
1673
|
+
complexity = "COMPLICATED"
|
|
1674
|
+
signals.append(f"prompt-length={word_count}")
|
|
1675
|
+
|
|
1676
|
+
domain_patterns = {
|
|
1677
|
+
"backend": r"\b(api|backend|server|django|fastapi|fiber|endpoint|router|service)\b",
|
|
1678
|
+
"frontend": r"\b(frontend|react|next\.?js|nextjs|component|ui|client|page|jsx|tsx)\b",
|
|
1679
|
+
"mobile": r"\b(mobile|ios|android|react native|expo|swift|kotlin)\b",
|
|
1680
|
+
"ai": r"\b(llm|ai|prompt|embedding|rag|model|completion|token)\b",
|
|
1681
|
+
"security": r"\b(security|auth|permission|csrf|xss|sql injection|jwt|oauth|tls|encryption|secret)\b",
|
|
1682
|
+
"ops": r"\b(deploy|ci/cd|docker|kubernetes|k8s|infra|monitoring|alert|runbook|sre)\b",
|
|
1683
|
+
"docs": r"\b(doc|documentation|readme|spec|playbook|adr)\b",
|
|
1684
|
+
"db": r"\b(database|sql|sqlite|postgres|mysql|migration|schema|index|query)\b",
|
|
1685
|
+
"graph": r"\b(graph|neo4j|kuzu|node|edge|traversal)\b",
|
|
1686
|
+
"test": r"\b(test|testing|pytest|jest|coverage|fixture|mock)\b",
|
|
1687
|
+
}
|
|
1688
|
+
hit_domains: list[str] = []
|
|
1689
|
+
for name, pat in domain_patterns.items():
|
|
1690
|
+
if _re.search(pat, text):
|
|
1691
|
+
hit_domains.append(name)
|
|
1692
|
+
dimensions = max(1, len(hit_domains))
|
|
1693
|
+
if dimensions >= 5 and complexity == "CLEAR":
|
|
1694
|
+
complexity = "COMPLICATED"
|
|
1695
|
+
signals.append(f"multi-dimension={dimensions}")
|
|
1696
|
+
|
|
1697
|
+
trivial_re = _re.compile(r"^(fix typo|update doc(?:string)?|tweak (?:wording|comment))\b")
|
|
1698
|
+
if (
|
|
1699
|
+
trivial_re.search(text)
|
|
1700
|
+
and word_count < 15
|
|
1701
|
+
and len(hit_domains) <= 1
|
|
1702
|
+
and complexity != "CHAOTIC"
|
|
1703
|
+
):
|
|
1704
|
+
complexity = "CLEAR"
|
|
1705
|
+
dimensions = 1
|
|
1706
|
+
signals = ["trivial-edit-shortcut"]
|
|
1707
|
+
|
|
1708
|
+
reasoning = (
|
|
1709
|
+
f"Cynefin: {complexity} ({', '.join(signals) or 'no escalating signals'}); "
|
|
1710
|
+
f"dims={dimensions} from domains: {', '.join(hit_domains) or 'none'}"
|
|
1711
|
+
)
|
|
1712
|
+
return {
|
|
1713
|
+
"complexity": complexity,
|
|
1714
|
+
"dimensions": dimensions,
|
|
1715
|
+
"signals": signals,
|
|
1716
|
+
"hit_domains": hit_domains,
|
|
1717
|
+
"reasoning": reasoning,
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
def register_cos_classify_prompt(mcp, db_path):
|
|
1722
|
+
"""Register cos_classify_prompt — heuristic Cynefin + dimensions classifier.
|
|
1723
|
+
|
|
1724
|
+
Replaces the manual `bash write-state.sh .thinking_os-gate "COMPLICATED 3"`
|
|
1725
|
+
step. The agent calls this on the user's prompt and gets back a recorded
|
|
1726
|
+
gate without manually counting domains or evaluating Cynefin signals.
|
|
1727
|
+
"""
|
|
1728
|
+
|
|
1729
|
+
@mcp.tool(
|
|
1730
|
+
name="cos_classify_prompt",
|
|
1731
|
+
description=(
|
|
1732
|
+
"Heuristic Cynefin + dimensions classifier. Reads a user prompt "
|
|
1733
|
+
"and returns {complexity, dimensions, reasoning, signals}. "
|
|
1734
|
+
"Optionally writes the gate marker so enforce-task-start.sh "
|
|
1735
|
+
"passes. Replaces the manual `write-state.sh .thinking_os-gate` "
|
|
1736
|
+
"step. Sub-second; deterministic; no LLM call."
|
|
1737
|
+
),
|
|
1738
|
+
)
|
|
1739
|
+
@safe_tool
|
|
1740
|
+
def cos_classify_prompt(
|
|
1741
|
+
prompt: str,
|
|
1742
|
+
record: bool = True,
|
|
1743
|
+
agent_dir: str = "",
|
|
1744
|
+
) -> str:
|
|
1745
|
+
import os as _os
|
|
1746
|
+
|
|
1747
|
+
if not (prompt or "").strip():
|
|
1748
|
+
return ok(
|
|
1749
|
+
{
|
|
1750
|
+
"complexity": "CLEAR",
|
|
1751
|
+
"dimensions": 1,
|
|
1752
|
+
"reasoning": "empty prompt",
|
|
1753
|
+
"signals": [],
|
|
1754
|
+
"recorded": False,
|
|
1755
|
+
},
|
|
1756
|
+
meta={"layer": "routing"},
|
|
1757
|
+
)
|
|
1758
|
+
|
|
1759
|
+
heuristic = classify_prompt_heuristic(prompt)
|
|
1760
|
+
complexity = heuristic["complexity"]
|
|
1761
|
+
dimensions = heuristic["dimensions"]
|
|
1762
|
+
signals = heuristic["signals"]
|
|
1763
|
+
hit_domains = heuristic["hit_domains"]
|
|
1764
|
+
reasoning = heuristic["reasoning"]
|
|
1765
|
+
|
|
1766
|
+
# Record the gate so enforce-task-start.sh passes — but ONLY when a
|
|
1767
|
+
# panel session is resolvable. The MCP server has no per-call panel
|
|
1768
|
+
# env, so this succeeds mainly when COS_PANEL_DIR is set (or agent_dir
|
|
1769
|
+
# is a real panel dir). It writes the SAME session-prefixed format the
|
|
1770
|
+
# strict panel reader (check-state.sh) requires; a bare
|
|
1771
|
+
# value or an agent-dir write would be silently rejected and leave a
|
|
1772
|
+
# misleading fossil — so we do neither, reporting recorded=false + a
|
|
1773
|
+
# shell hint instead. (Fixes the prior wrong-dir/wrong-format
|
|
1774
|
+
# /no-trace bug.)
|
|
1775
|
+
recorded = False
|
|
1776
|
+
record_hint = ""
|
|
1777
|
+
if record:
|
|
1778
|
+
panel_dir = _os.environ.get("COS_PANEL_DIR") or agent_dir
|
|
1779
|
+
sid = ""
|
|
1780
|
+
if panel_dir:
|
|
1781
|
+
sid_file = Path(panel_dir) / "session-id"
|
|
1782
|
+
if sid_file.exists():
|
|
1783
|
+
try:
|
|
1784
|
+
sid = sid_file.read_text(encoding="utf-8").strip().split(" ")[0]
|
|
1785
|
+
except OSError:
|
|
1786
|
+
sid = ""
|
|
1787
|
+
if panel_dir and sid:
|
|
1788
|
+
try:
|
|
1789
|
+
gate_path = Path(panel_dir) / ".thinking_os-gate"
|
|
1790
|
+
gate_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1791
|
+
tmp_path = gate_path.with_name(".thinking_os-gate.tmp")
|
|
1792
|
+
tmp_path.write_text(f"{sid} {complexity} {dimensions}\n", encoding="utf-8")
|
|
1793
|
+
tmp_path.replace(gate_path)
|
|
1794
|
+
recorded = True
|
|
1795
|
+
try:
|
|
1796
|
+
import tracing
|
|
1797
|
+
|
|
1798
|
+
tracing.emit(
|
|
1799
|
+
sid,
|
|
1800
|
+
"classify",
|
|
1801
|
+
{"complexity": complexity, "dimensions": dimensions},
|
|
1802
|
+
)
|
|
1803
|
+
except Exception as exc:
|
|
1804
|
+
from core.logging_os import swallow_safe
|
|
1805
|
+
|
|
1806
|
+
swallow_safe("thinking_os.cognition", "classify trace emit failed", exc=exc)
|
|
1807
|
+
except OSError:
|
|
1808
|
+
recorded = False
|
|
1809
|
+
if not recorded:
|
|
1810
|
+
record_hint = (
|
|
1811
|
+
"gate not recorded (no panel session context from MCP); "
|
|
1812
|
+
"record it in your shell: write-state.sh .thinking_os-gate "
|
|
1813
|
+
f'"{complexity} {dimensions}"'
|
|
1814
|
+
)
|
|
1815
|
+
|
|
1816
|
+
# Discoverability nudge (TASK-509): a COMPLICATED+ gate needs the
|
|
1817
|
+
# cognition surface (role composition / formula dispatch, Rule 15), but a
|
|
1818
|
+
# lean profile may have disabled it. Surface the one-liner to re-enable
|
|
1819
|
+
# rather than letting the agent hit a module_disabled wall mid-plan.
|
|
1820
|
+
nudge = ""
|
|
1821
|
+
if (
|
|
1822
|
+
complexity in ("COMPLICATED", "COMPLEX")
|
|
1823
|
+
and _gated_module("cos_compose_chain") == "cognition"
|
|
1824
|
+
):
|
|
1825
|
+
nudge = (
|
|
1826
|
+
f"{complexity} work but the cognition module is OFF — role "
|
|
1827
|
+
"composition / formula dispatch are unavailable. Enable it with "
|
|
1828
|
+
"`cos module enable cognition` (or scaffold with `--profile full`)."
|
|
1829
|
+
)
|
|
1830
|
+
|
|
1831
|
+
return ok(
|
|
1832
|
+
{
|
|
1833
|
+
"complexity": complexity,
|
|
1834
|
+
"dimensions": dimensions,
|
|
1835
|
+
"reasoning": reasoning,
|
|
1836
|
+
"signals": signals,
|
|
1837
|
+
"domains": hit_domains,
|
|
1838
|
+
"recorded": recorded,
|
|
1839
|
+
"record_hint": record_hint,
|
|
1840
|
+
"nudge": nudge,
|
|
1841
|
+
},
|
|
1842
|
+
meta={"layer": "routing"},
|
|
1843
|
+
)
|
|
1844
|
+
|
|
1845
|
+
return cos_classify_prompt
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
def register_all(mcp, db_path: str) -> None:
|
|
1849
|
+
"""Register all 15 cognition tools with the MCP server."""
|
|
1850
|
+
register_cos_supervise(mcp, db_path)
|
|
1851
|
+
register_cos_supervise_record_output(mcp, db_path)
|
|
1852
|
+
register_cos_dispatch_formula(mcp, db_path)
|
|
1853
|
+
register_cos_ambiguity_check(mcp, db_path)
|
|
1854
|
+
register_cos_traceability(mcp, db_path)
|
|
1855
|
+
register_cos_backtrack_log(mcp, db_path)
|
|
1856
|
+
register_cos_discovery(mcp, db_path)
|
|
1857
|
+
register_cos_situation_detect(mcp, db_path)
|
|
1858
|
+
register_cos_takeover(mcp, db_path)
|
|
1859
|
+
# Additions
|
|
1860
|
+
register_cos_analyze_task(mcp, db_path)
|
|
1861
|
+
register_cos_compose_chain(mcp, db_path)
|
|
1862
|
+
register_cos_role_info(mcp, db_path)
|
|
1863
|
+
# real dispatch (opt-in, costly)
|
|
1864
|
+
register_cos_dispatch_formula_run(mcp, db_path)
|
|
1865
|
+
register_cos_dispatch_parallel_run(mcp, db_path)
|
|
1866
|
+
# auto-Classify (eliminates manual gate recording)
|
|
1867
|
+
register_cos_classify_prompt(mcp, db_path)
|