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,947 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Coding OS — Shared MCP tool envelope helpers.
|
|
3
|
+
|
|
4
|
+
Canonical response contract: docs/engineering/mcp-error-envelope.md.
|
|
5
|
+
|
|
6
|
+
Every `cos_*` tool registered in `server.py` MUST route its return value
|
|
7
|
+
through `ok()` / `fail()` so consuming agents can distinguish success from
|
|
8
|
+
failure, categorize errors, and decide whether to retry.
|
|
9
|
+
|
|
10
|
+
Uniform `meta` block:
|
|
11
|
+
Every success payload carries `data.meta` with at minimum:
|
|
12
|
+
- layer ("memory"|"docs"|"tasks"|"metrics"|"routing"|
|
|
13
|
+
"graph"|"health"|"learning")
|
|
14
|
+
- tokens_estimated (script-aware token estimate of the serialized response)
|
|
15
|
+
- truncated (bool — set True when token budget forced a cut)
|
|
16
|
+
|
|
17
|
+
Callers supply `meta={"layer": ..., "source": ..., "query": ...}` via
|
|
18
|
+
the `ok(data, meta=...)` kwarg. `tokens_estimated` and `truncated` are
|
|
19
|
+
computed by this helper — callers must not set them manually.
|
|
20
|
+
|
|
21
|
+
Token-budget enforcement:
|
|
22
|
+
Serialized responses above TOKEN_BUDGET_CHARS are trimmed. Strategy:
|
|
23
|
+
shrink `data.results` from the tail until the payload fits. Meta
|
|
24
|
+
records both the original and kept sizes so the agent knows it asked
|
|
25
|
+
for too much and can page with a smaller limit.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import logging
|
|
32
|
+
import os
|
|
33
|
+
import sqlite3
|
|
34
|
+
import threading
|
|
35
|
+
from collections.abc import Callable
|
|
36
|
+
from functools import wraps
|
|
37
|
+
from typing import Any, Literal
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger("coding_os.tools._shared")
|
|
40
|
+
|
|
41
|
+
ErrorCategory = Literal[
|
|
42
|
+
"transient",
|
|
43
|
+
"validation",
|
|
44
|
+
"permission",
|
|
45
|
+
"not_found",
|
|
46
|
+
"unavailable",
|
|
47
|
+
"internal",
|
|
48
|
+
"module_disabled",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
_RETRYABLE_BY_DEFAULT: frozenset[str] = frozenset({"transient", "unavailable"})
|
|
52
|
+
|
|
53
|
+
# 32 KB ≈ 8 000 tokens. Sized to fit 4× the largest typical cos_doc_search
|
|
54
|
+
# response (≈2k tokens) so normal traffic never hits the budget, while
|
|
55
|
+
# catastrophic payloads (e.g. 1000-row metric queries) get trimmed.
|
|
56
|
+
TOKEN_BUDGET_CHARS = 32_000
|
|
57
|
+
|
|
58
|
+
# OOM-safety ceiling for graph-export-shaped responses ({nodes, edges}).
|
|
59
|
+
# Rationale: structural snapshots aren't textual prose — they describe
|
|
60
|
+
# a whole subgraph. The agent-context budget (32 KB) is the wrong cap;
|
|
61
|
+
# the caller already constrained volume via max_nodes/max_hops (G35
|
|
62
|
+
# hard-caps max_nodes at 2000). UI consumers (/api/graph/export) need
|
|
63
|
+
# the full tree to render the CONTAINS spine. Set ceiling at ~5 MB —
|
|
64
|
+
# any browser fetches that comfortably, MCP transports tolerate it, and
|
|
65
|
+
# the full repo tree (1094 nodes + 1444 edges ≈ 1 MB with indent=2)
|
|
66
|
+
# never trips it under normal operation. Above the ceiling we fall
|
|
67
|
+
# back to coherent-subgraph trim (top-K nodes by degree, edges between
|
|
68
|
+
# kept nodes) so a pathological agent request never returns zero edges
|
|
69
|
+
# or an incoherent slice.
|
|
70
|
+
GRAPH_SUBGRAPH_BUDGET_CHARS = 5_000_000
|
|
71
|
+
|
|
72
|
+
# Layer names — enumerated so tests can pin the contract and agents can
|
|
73
|
+
# filter (Three-Layer Retrieval in CLAUDE.md).
|
|
74
|
+
VALID_LAYERS: frozenset[str] = frozenset(
|
|
75
|
+
{
|
|
76
|
+
"memory",
|
|
77
|
+
"docs",
|
|
78
|
+
"tasks",
|
|
79
|
+
"metrics",
|
|
80
|
+
"routing",
|
|
81
|
+
"graph",
|
|
82
|
+
"health",
|
|
83
|
+
"learning",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# Success envelope
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ok(data: Any, *, meta: dict | None = None, apply_budget: bool = True) -> str:
|
|
94
|
+
"""Wrap a successful tool result in the canonical envelope."""
|
|
95
|
+
if not isinstance(data, dict):
|
|
96
|
+
return json.dumps({"ok": True, "data": data}, indent=2, default=str)
|
|
97
|
+
|
|
98
|
+
# Merge caller-supplied meta on top of any meta already in data.
|
|
99
|
+
# Diagnostic keys (tokens_estimated, truncated, truncated_results_*) are
|
|
100
|
+
# strictly computed here — caller-supplied values are stripped so agents
|
|
101
|
+
# cannot spoof "this response wasn't trimmed" in logs/audit.
|
|
102
|
+
existing_meta = dict(data.get("meta") or {})
|
|
103
|
+
if meta:
|
|
104
|
+
existing_meta.update(meta)
|
|
105
|
+
# Strip diagnostic keys the trimmer owns — caller meta can't spoof
|
|
106
|
+
# them. `tokens_estimated` + `truncated` always; every `truncated_*`
|
|
107
|
+
# key (added by _trim_list_key + _trim_edges_by_type) reserved too.
|
|
108
|
+
for _diag in ("tokens_estimated", "truncated"):
|
|
109
|
+
existing_meta.pop(_diag, None)
|
|
110
|
+
for _k in [k for k in existing_meta if k.startswith("truncated_")]:
|
|
111
|
+
existing_meta.pop(_k, None)
|
|
112
|
+
|
|
113
|
+
# Strip `meta` from the data dict so we can re-attach with diagnostics
|
|
114
|
+
body = {k: v for k, v in data.items() if k != "meta"}
|
|
115
|
+
|
|
116
|
+
# First serialization to estimate tokens (meta is the in-progress dict
|
|
117
|
+
# referenced below; JSON reflects current state each dump).
|
|
118
|
+
serialized = json.dumps(
|
|
119
|
+
{"ok": True, "data": {**body, "meta": existing_meta}},
|
|
120
|
+
indent=2,
|
|
121
|
+
default=str,
|
|
122
|
+
)
|
|
123
|
+
existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
|
|
124
|
+
existing_meta["truncated"] = False
|
|
125
|
+
|
|
126
|
+
# Graph-export-shaped responses ({nodes:[...], edges:[...]}) describe a
|
|
127
|
+
# whole subgraph. Two-tier budget:
|
|
128
|
+
# 1. Under GRAPH_SUBGRAPH_BUDGET_CHARS (≈500 KB): pass through
|
|
129
|
+
# untouched — UI Hub needs full tree to render CONTAINS spine.
|
|
130
|
+
# 2. Over the larger ceiling: coherent-subgraph trim (top-K nodes
|
|
131
|
+
# by degree + edges between kept nodes). Never zero out edges
|
|
132
|
+
# and never return an incoherent slice.
|
|
133
|
+
# Agent consumers can use max_nodes/max_hops to stay under the agent
|
|
134
|
+
# context window; the envelope is the safety net, not the primary cap.
|
|
135
|
+
_is_graph_subgraph = (
|
|
136
|
+
isinstance(body.get("nodes"), list)
|
|
137
|
+
and isinstance(body.get("edges"), list)
|
|
138
|
+
and bool(body.get("nodes"))
|
|
139
|
+
and bool(body.get("edges"))
|
|
140
|
+
)
|
|
141
|
+
# Enforce token budget. `truncated` only flips when the body was
|
|
142
|
+
# actually shrunk — a no-op (shape not matched) leaves the flag
|
|
143
|
+
# False so agents trust the signal. Web/browser callers pass
|
|
144
|
+
# apply_budget=False: a browser is not token-limited, so the 32KB
|
|
145
|
+
# agent-context cap (and its envelope_unshrinkable fall-through when a
|
|
146
|
+
# payload's shape isn't in the trim ladder) must not apply to it — that
|
|
147
|
+
# cap is an agent-context concept, not a wire limit. Mirrors the
|
|
148
|
+
# apply_budget param cos_task_board already exposes (the board's browser
|
|
149
|
+
# path threads it straight through here).
|
|
150
|
+
if not apply_budget:
|
|
151
|
+
pass
|
|
152
|
+
elif _is_graph_subgraph:
|
|
153
|
+
if len(serialized) > GRAPH_SUBGRAPH_BUDGET_CHARS:
|
|
154
|
+
body, existing_meta, did_trim = _trim_coherent_subgraph(
|
|
155
|
+
body, existing_meta, budget_chars=GRAPH_SUBGRAPH_BUDGET_CHARS
|
|
156
|
+
)
|
|
157
|
+
if did_trim:
|
|
158
|
+
existing_meta["truncated"] = True
|
|
159
|
+
serialized = json.dumps(
|
|
160
|
+
{"ok": True, "data": {**body, "meta": existing_meta}},
|
|
161
|
+
indent=2,
|
|
162
|
+
default=str,
|
|
163
|
+
)
|
|
164
|
+
existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
|
|
165
|
+
elif _budget_size(serialized) > TOKEN_BUDGET_CHARS:
|
|
166
|
+
body, existing_meta, did_trim = _apply_token_budget(body, existing_meta)
|
|
167
|
+
if did_trim:
|
|
168
|
+
existing_meta["truncated"] = True
|
|
169
|
+
# Audit fix: a list-dropping token trim makes the
|
|
170
|
+
# answer incomplete, so the coverage flag must agree. Tools
|
|
171
|
+
# that publish `result_truncated` (contracts/references/...)
|
|
172
|
+
# had it stay False while items were silently dropped, so an
|
|
173
|
+
# agent reading only result_truncated assumed completeness.
|
|
174
|
+
if "result_truncated" in existing_meta:
|
|
175
|
+
existing_meta["result_truncated"] = True
|
|
176
|
+
serialized = json.dumps(
|
|
177
|
+
{"ok": True, "data": {**body, "meta": existing_meta}},
|
|
178
|
+
indent=2,
|
|
179
|
+
default=str,
|
|
180
|
+
)
|
|
181
|
+
existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
|
|
182
|
+
|
|
183
|
+
payload = {"ok": True, "data": {**body, "meta": existing_meta}}
|
|
184
|
+
return json.dumps(payload, indent=2, default=str)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# keys that hold list payloads across cos_* tools. Trimmer
|
|
188
|
+
# walks them in order — biggest payload first when there's a choice.
|
|
189
|
+
# `results` stays first for legacy callers; context/references/impact
|
|
190
|
+
# emit the rest. `edges_by_type` is a dict-of-lists handled separately.
|
|
191
|
+
_TRIMMABLE_LIST_KEYS: tuple[str, ...] = (
|
|
192
|
+
"results",
|
|
193
|
+
"neighbours",
|
|
194
|
+
"references",
|
|
195
|
+
# Export-shaped responses — trim `edges` BEFORE `nodes`.
|
|
196
|
+
# Caller asked for nodes; edges are the high-volume tail.
|
|
197
|
+
"edges",
|
|
198
|
+
"nodes",
|
|
199
|
+
"processes",
|
|
200
|
+
"call_sites",
|
|
201
|
+
"import_sites",
|
|
202
|
+
"doc_references",
|
|
203
|
+
"test_references",
|
|
204
|
+
"string_literals",
|
|
205
|
+
"external_targets",
|
|
206
|
+
"branches",
|
|
207
|
+
"steps",
|
|
208
|
+
# W6.2: cos_graph_contracts top-level buckets.
|
|
209
|
+
"http_routes",
|
|
210
|
+
"mcp_tools",
|
|
211
|
+
"grpc_endpoints",
|
|
212
|
+
"event_handlers",
|
|
213
|
+
"websocket",
|
|
214
|
+
# Common list payloads in other tools.
|
|
215
|
+
"nodes_top",
|
|
216
|
+
"samples",
|
|
217
|
+
# cos_graph_detect_changes bulk arrays.
|
|
218
|
+
"symbols",
|
|
219
|
+
"downstream_consumers",
|
|
220
|
+
"downstream_tasks",
|
|
221
|
+
# S11 (TASK-259): wide-payload tool list keys that were OUTSIDE the ladder,
|
|
222
|
+
# so an oversized agent call produced envelope_unshrinkable — the same bug
|
|
223
|
+
# class as the board 186KB ERROR, generalized. Adding a new wide tool? Add
|
|
224
|
+
# its list key here (and to test_envelope.py::TestTrimLadderCoverage).
|
|
225
|
+
"rows", # cos_log_query, cos_metric_query
|
|
226
|
+
"entries", # cos_timeline
|
|
227
|
+
"cycles", # cos_graph_cycles
|
|
228
|
+
"untested", # cos_graph_test_gap
|
|
229
|
+
"dead", # cos_graph_dead_code
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# W6.2: dict-of-lists buckets (parent_key → {sub_key: [items]}). Trimmer
|
|
233
|
+
# halves the biggest sub-list until envelope fits, like _trim_edges_by_type.
|
|
234
|
+
# Covers cos_graph_impact.tiers + cos_graph_contracts.* bucketed outputs.
|
|
235
|
+
_TRIMMABLE_NESTED_BUCKETS: tuple[str, ...] = (
|
|
236
|
+
"tiers",
|
|
237
|
+
"http_routes_by_method",
|
|
238
|
+
"mcp_tools_by_module",
|
|
239
|
+
"event_handlers_by_source",
|
|
240
|
+
"contracts",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# W6.2: list[dict] containers whose members carry a nested list to trim.
|
|
244
|
+
# (parent_key, member_list_key) — e.g. ("processes", "members") for
|
|
245
|
+
# cos_graph_communities so we shrink members per-process before dropping
|
|
246
|
+
# whole processes.
|
|
247
|
+
_TRIMMABLE_NESTED_MEMBERS: tuple[tuple[str, str], ...] = (("processes", "members"),)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _estimate_tokens(text: str) -> int:
|
|
251
|
+
# Heuristic, NOT a tokenizer. chars/4 holds for ASCII but undercounts non-Latin
|
|
252
|
+
# ~2-3x — BPE emits more tokens per char for CJK / Arabic / Cyrillic, so the old
|
|
253
|
+
# chars/4 let oversized non-Latin payloads slip under the budget with
|
|
254
|
+
# truncated=False (the coverage signal the graph-first contract trusts). Model:
|
|
255
|
+
# ASCII ~4 chars/token; each non-ASCII char weighted ~1 token — heavier than
|
|
256
|
+
# chars/4, which closes most of the gap. Not exact: dense CJK can still exceed
|
|
257
|
+
# 1 tok/char (mild residual undercount) and Arabic/Cyrillic run lighter (mild
|
|
258
|
+
# over-trim). The goal is removing the silent undercount, not matching a tokenizer.
|
|
259
|
+
ascii_chars = len(text.encode("ascii", "ignore"))
|
|
260
|
+
non_ascii = len(text) - ascii_chars
|
|
261
|
+
return max(1, int(ascii_chars / 4 + non_ascii))
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _budget_size(text: str) -> int:
|
|
265
|
+
# Token-normalised size for the char-denominated budgets: identical to len()
|
|
266
|
+
# for ASCII (zero behaviour change), inflated for non-Latin so the trimmers
|
|
267
|
+
# shrink to the real token budget instead of a char proxy.
|
|
268
|
+
return max(len(text), _estimate_tokens(text) * 4)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _probe_size(body: dict, meta: dict) -> int:
|
|
272
|
+
return _budget_size(
|
|
273
|
+
json.dumps(
|
|
274
|
+
{"ok": True, "data": {**body, "meta": meta}},
|
|
275
|
+
indent=2,
|
|
276
|
+
default=str,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _trim_list_key(body: dict, meta: dict, key: str) -> tuple[dict, dict, bool]:
|
|
282
|
+
# Binary-search shrink the list at body[key] until envelope ≤ budget.
|
|
283
|
+
# Returns (body, meta, fits). O(log N) probes.
|
|
284
|
+
items = body.get(key)
|
|
285
|
+
if not isinstance(items, list) or not items:
|
|
286
|
+
return body, meta, False
|
|
287
|
+
original_n = len(items)
|
|
288
|
+
lo, hi = 0, original_n
|
|
289
|
+
best_keep = 0
|
|
290
|
+
while lo < hi:
|
|
291
|
+
mid = (lo + hi + 1) // 2
|
|
292
|
+
trial = {**body, key: items[:mid]}
|
|
293
|
+
if _probe_size(trial, meta) <= TOKEN_BUDGET_CHARS:
|
|
294
|
+
best_keep = mid
|
|
295
|
+
lo = mid
|
|
296
|
+
else:
|
|
297
|
+
hi = mid - 1
|
|
298
|
+
if best_keep < original_n:
|
|
299
|
+
meta[f"truncated_{key}_from"] = original_n
|
|
300
|
+
meta[f"truncated_{key}_to"] = best_keep
|
|
301
|
+
# N5: a sibling `count` that mirrored the pre-trim list length would now
|
|
302
|
+
# lie (count=100 while the array holds 60). Reconcile it through every
|
|
303
|
+
# shrink step — but ONLY when it provably tracked THIS list (== original
|
|
304
|
+
# length), never a count describing a different key.
|
|
305
|
+
count_tracks_key = body.get("count") == original_n
|
|
306
|
+
body = {**body, key: items[:best_keep]}
|
|
307
|
+
if count_tracks_key:
|
|
308
|
+
body["count"] = best_keep
|
|
309
|
+
# The binary search probed fit with `meta` BEFORE these two marker
|
|
310
|
+
# keys existed; committing them grows the envelope by ~50 bytes and
|
|
311
|
+
# can push a borderline best_keep back over budget. Re-check the
|
|
312
|
+
# committed body and shrink one element further until it truly fits,
|
|
313
|
+
# so the ladder never returns a marginally-over body that then falls
|
|
314
|
+
# through and mauls load-bearing scalars (scope/risk_level).
|
|
315
|
+
while best_keep > 0 and _probe_size(body, meta) > TOKEN_BUDGET_CHARS:
|
|
316
|
+
best_keep -= 1
|
|
317
|
+
meta[f"truncated_{key}_to"] = best_keep
|
|
318
|
+
body = {**body, key: items[:best_keep]}
|
|
319
|
+
if count_tracks_key:
|
|
320
|
+
body["count"] = best_keep
|
|
321
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _trim_coherent_subgraph(
|
|
325
|
+
body: dict, meta: dict, *, budget_chars: int = TOKEN_BUDGET_CHARS
|
|
326
|
+
) -> tuple[dict, dict, bool]:
|
|
327
|
+
# Drop nodes proportionally (top-K by degree + edges between kept nodes),
|
|
328
|
+
# not edges-first, so the subgraph stays connected — the Hub UI must get a
|
|
329
|
+
# renderable tree, never 0 edges/0 nodes.
|
|
330
|
+
nodes = body.get("nodes")
|
|
331
|
+
edges = body.get("edges")
|
|
332
|
+
if not isinstance(nodes, list) or not isinstance(edges, list):
|
|
333
|
+
return body, meta, _probe_size(body, meta) <= budget_chars
|
|
334
|
+
if not nodes or not edges:
|
|
335
|
+
return body, meta, _probe_size(body, meta) <= budget_chars
|
|
336
|
+
|
|
337
|
+
# Degree map: edges that reference each uid.
|
|
338
|
+
degree: dict[str, int] = {}
|
|
339
|
+
for e in edges:
|
|
340
|
+
if isinstance(e, dict):
|
|
341
|
+
src = e.get("source_uid") or e.get("src_uid") or e.get("source")
|
|
342
|
+
dst = e.get("target_uid") or e.get("dst_uid") or e.get("target")
|
|
343
|
+
if src:
|
|
344
|
+
degree[src] = degree.get(src, 0) + 1
|
|
345
|
+
if dst:
|
|
346
|
+
degree[dst] = degree.get(dst, 0) + 1
|
|
347
|
+
|
|
348
|
+
def _uid_of(n):
|
|
349
|
+
return n.get("uid") if isinstance(n, dict) else None
|
|
350
|
+
|
|
351
|
+
# Sort nodes once: highest degree first, stable order for ties.
|
|
352
|
+
nodes_sorted = sorted(
|
|
353
|
+
nodes,
|
|
354
|
+
key=lambda n: (-degree.get(_uid_of(n) or "", 0), nodes.index(n)),
|
|
355
|
+
)
|
|
356
|
+
original_n = len(nodes_sorted)
|
|
357
|
+
original_e = len(edges)
|
|
358
|
+
|
|
359
|
+
def _probe(k_nodes: int) -> tuple[list, list, int]:
|
|
360
|
+
kept_nodes = nodes_sorted[:k_nodes]
|
|
361
|
+
kept_uids = {_uid_of(n) for n in kept_nodes if _uid_of(n)}
|
|
362
|
+
kept_edges = [
|
|
363
|
+
e
|
|
364
|
+
for e in edges
|
|
365
|
+
if isinstance(e, dict)
|
|
366
|
+
and (
|
|
367
|
+
(e.get("source_uid") or e.get("src_uid") or e.get("source")) in kept_uids
|
|
368
|
+
and (e.get("target_uid") or e.get("dst_uid") or e.get("target")) in kept_uids
|
|
369
|
+
)
|
|
370
|
+
]
|
|
371
|
+
trial = {**body, "nodes": kept_nodes, "edges": kept_edges}
|
|
372
|
+
return kept_nodes, kept_edges, _probe_size(trial, meta)
|
|
373
|
+
|
|
374
|
+
lo, hi = 0, original_n
|
|
375
|
+
best_k = 0
|
|
376
|
+
while lo < hi:
|
|
377
|
+
mid = (lo + hi + 1) // 2
|
|
378
|
+
_, _, size = _probe(mid)
|
|
379
|
+
if size <= budget_chars:
|
|
380
|
+
best_k = mid
|
|
381
|
+
lo = mid
|
|
382
|
+
else:
|
|
383
|
+
hi = mid - 1
|
|
384
|
+
|
|
385
|
+
kept_nodes, kept_edges, _ = _probe(best_k)
|
|
386
|
+
did_trim = best_k < original_n or len(kept_edges) < original_e
|
|
387
|
+
if did_trim:
|
|
388
|
+
meta["truncated_subgraph"] = True
|
|
389
|
+
meta["truncated_nodes_from"] = original_n
|
|
390
|
+
meta["truncated_nodes_to"] = best_k
|
|
391
|
+
meta["truncated_edges_from"] = original_e
|
|
392
|
+
meta["truncated_edges_to"] = len(kept_edges)
|
|
393
|
+
body = {**body, "nodes": kept_nodes, "edges": kept_edges}
|
|
394
|
+
return body, meta, did_trim
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _trim_edges_by_type(body: dict, meta: dict) -> tuple[dict, dict, bool]:
|
|
398
|
+
# edges_by_type is dict-of-lists; greedily halve biggest bucket until fits.
|
|
399
|
+
edges_by_type = body.get("edges_by_type")
|
|
400
|
+
if not isinstance(edges_by_type, dict) or not edges_by_type:
|
|
401
|
+
return body, meta, False
|
|
402
|
+
# F#6: separate list buckets (trimmable) from non-list values (preserved).
|
|
403
|
+
list_buckets = {k: list(v) for k, v in edges_by_type.items() if isinstance(v, list)}
|
|
404
|
+
non_list = {k: v for k, v in edges_by_type.items() if not isinstance(v, list)}
|
|
405
|
+
if not list_buckets:
|
|
406
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
407
|
+
trim_record: dict[str, dict[str, int]] = {}
|
|
408
|
+
while (
|
|
409
|
+
_probe_size({**body, "edges_by_type": {**non_list, **list_buckets}}, meta)
|
|
410
|
+
> TOKEN_BUDGET_CHARS
|
|
411
|
+
):
|
|
412
|
+
biggest = max(list_buckets, key=lambda k: len(list_buckets[k]), default=None)
|
|
413
|
+
if biggest is None or not list_buckets[biggest]:
|
|
414
|
+
break
|
|
415
|
+
before = trim_record.get(biggest, {}).get("from", len(edges_by_type[biggest]))
|
|
416
|
+
new_len = max(0, len(list_buckets[biggest]) // 2)
|
|
417
|
+
trim_record[biggest] = {"from": before, "to": new_len}
|
|
418
|
+
list_buckets[biggest] = list_buckets[biggest][:new_len]
|
|
419
|
+
if all(len(v) == 0 for v in list_buckets.values()):
|
|
420
|
+
break
|
|
421
|
+
body = {**body, "edges_by_type": {**non_list, **list_buckets}}
|
|
422
|
+
if trim_record:
|
|
423
|
+
meta["truncated_edges_by_type"] = trim_record
|
|
424
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _trim_nested_buckets(body: dict, meta: dict, parent_key: str) -> tuple[dict, dict, bool]:
|
|
428
|
+
# W6.2: parent_key holds a dict mapping sub_key → list[items]
|
|
429
|
+
# (impact.tiers, contracts.http_routes_by_method, etc). Same halve-
|
|
430
|
+
# biggest strategy as _trim_edges_by_type.
|
|
431
|
+
parent = body.get(parent_key)
|
|
432
|
+
if not isinstance(parent, dict) or not parent:
|
|
433
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
434
|
+
list_buckets = {k: list(v) for k, v in parent.items() if isinstance(v, list)}
|
|
435
|
+
non_list = {k: v for k, v in parent.items() if not isinstance(v, list)}
|
|
436
|
+
if not list_buckets:
|
|
437
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
438
|
+
trim_record: dict[str, dict[str, int]] = {}
|
|
439
|
+
original_lens = {k: len(v) for k, v in list_buckets.items()}
|
|
440
|
+
while (
|
|
441
|
+
_probe_size({**body, parent_key: {**non_list, **list_buckets}}, meta) > TOKEN_BUDGET_CHARS
|
|
442
|
+
):
|
|
443
|
+
biggest = max(list_buckets, key=lambda k: len(list_buckets[k]), default=None)
|
|
444
|
+
if biggest is None or not list_buckets[biggest]:
|
|
445
|
+
break
|
|
446
|
+
new_len = max(0, len(list_buckets[biggest]) // 2)
|
|
447
|
+
trim_record[biggest] = {"from": original_lens[biggest], "to": new_len}
|
|
448
|
+
list_buckets[biggest] = list_buckets[biggest][:new_len]
|
|
449
|
+
if all(len(v) == 0 for v in list_buckets.values()):
|
|
450
|
+
break
|
|
451
|
+
body = {**body, parent_key: {**non_list, **list_buckets}}
|
|
452
|
+
if trim_record:
|
|
453
|
+
meta[f"truncated_{parent_key}"] = trim_record
|
|
454
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _trim_nested_member_lists(
|
|
458
|
+
body: dict, meta: dict, parent_key: str, member_key: str, *, floor: int = 3
|
|
459
|
+
) -> tuple[dict, dict, bool]:
|
|
460
|
+
# W6.2: shrink list-of-dict members[*]. e.g. processes[*].members.
|
|
461
|
+
# Halve member-lists per-entry until envelope fits or floor reached;
|
|
462
|
+
# then drop whole entries from the tail.
|
|
463
|
+
items = body.get(parent_key)
|
|
464
|
+
if not isinstance(items, list) or not items:
|
|
465
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
466
|
+
members_orig = [len(it.get(member_key) or []) if isinstance(it, dict) else 0 for it in items]
|
|
467
|
+
new_items = [dict(it) if isinstance(it, dict) else it for it in items]
|
|
468
|
+
record: dict[str, int] = {}
|
|
469
|
+
iter_guard = 0
|
|
470
|
+
# iter_guard caps the halving loop. Each iteration halves the
|
|
471
|
+
# biggest member list above `floor`, so at most ceil(log2(max_len))
|
|
472
|
+
# passes per entry × N entries. 64 covers up to ~2^64 starting size
|
|
473
|
+
# while keeping the loop bounded against pathological inputs.
|
|
474
|
+
_ITER_GUARD_MAX = 64
|
|
475
|
+
while (
|
|
476
|
+
_probe_size({**body, parent_key: new_items}, meta) > TOKEN_BUDGET_CHARS
|
|
477
|
+
and iter_guard < _ITER_GUARD_MAX
|
|
478
|
+
):
|
|
479
|
+
iter_guard += 1
|
|
480
|
+
# find entry with biggest member list above floor
|
|
481
|
+
candidate_idx = -1
|
|
482
|
+
biggest = floor
|
|
483
|
+
for i, it in enumerate(new_items):
|
|
484
|
+
if not isinstance(it, dict):
|
|
485
|
+
continue
|
|
486
|
+
n = len(it.get(member_key) or [])
|
|
487
|
+
if n > biggest:
|
|
488
|
+
biggest = n
|
|
489
|
+
candidate_idx = i
|
|
490
|
+
if candidate_idx == -1:
|
|
491
|
+
break
|
|
492
|
+
entry = new_items[candidate_idx]
|
|
493
|
+
mlist = entry.get(member_key) or []
|
|
494
|
+
new_len = max(floor, len(mlist) // 2)
|
|
495
|
+
entry[member_key] = mlist[:new_len]
|
|
496
|
+
record[f"entry_{candidate_idx}_{member_key}"] = new_len
|
|
497
|
+
# If still over budget, drop tail entries.
|
|
498
|
+
while (
|
|
499
|
+
_probe_size({**body, parent_key: new_items}, meta) > TOKEN_BUDGET_CHARS
|
|
500
|
+
and len(new_items) > 1
|
|
501
|
+
):
|
|
502
|
+
new_items.pop()
|
|
503
|
+
record["entries_dropped_tail"] = record.get("entries_dropped_tail", 0) + 1
|
|
504
|
+
body = {**body, parent_key: new_items}
|
|
505
|
+
if record:
|
|
506
|
+
meta[f"truncated_{parent_key}_{member_key}"] = record
|
|
507
|
+
meta[f"{parent_key}_original_member_counts"] = members_orig
|
|
508
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# Scalars shorter than this are load-bearing (scope, risk_level, status,
|
|
512
|
+
# …) and can never recover meaningful budget — truncating a 4-char "high"
|
|
513
|
+
# is pure signal loss. The F#5 safety-net skips them and only shortens
|
|
514
|
+
# genuinely large scalars (signatures, generated text, big blobs).
|
|
515
|
+
_SCALAR_TRIM_FLOOR_CHARS = 200
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _trim_huge_string_fields(body: dict, meta: dict) -> tuple[dict, dict, bool]:
|
|
519
|
+
# F#5 safety net: after list trims, if envelope still over budget the
|
|
520
|
+
# culprit is a huge top-level scalar (large signature, generated text).
|
|
521
|
+
# Truncate biggest scalars first until envelope fits. Never touch
|
|
522
|
+
# `node` (caller's root identity). W6.2: ONLY touch str fields —
|
|
523
|
+
# numeric scalars (int/float/bool) preserve typed contract (e.g.
|
|
524
|
+
# impacted_count must stay int). Strings get a cut-to-length prefix
|
|
525
|
+
# instead of a sentinel so partial content remains useful.
|
|
526
|
+
if _probe_size(body, meta) <= TOKEN_BUDGET_CHARS:
|
|
527
|
+
return body, meta, True
|
|
528
|
+
sizes: list[tuple[int, str]] = []
|
|
529
|
+
for k, v in body.items():
|
|
530
|
+
if k == "node" or isinstance(v, (list, dict)):
|
|
531
|
+
continue
|
|
532
|
+
if not isinstance(v, str):
|
|
533
|
+
# W6.2 (T4/F7): never stringify int/bool/float scalars.
|
|
534
|
+
continue
|
|
535
|
+
if len(v) < _SCALAR_TRIM_FLOOR_CHARS:
|
|
536
|
+
# Tiny load-bearing scalar (scope/risk_level/status) — truncating
|
|
537
|
+
# it can't recover budget and destroys signal. Leave intact.
|
|
538
|
+
continue
|
|
539
|
+
sizes.append((len(v), k))
|
|
540
|
+
if not sizes:
|
|
541
|
+
return body, meta, False
|
|
542
|
+
sizes.sort(reverse=True)
|
|
543
|
+
truncated_fields: list[str] = []
|
|
544
|
+
new_body = dict(body)
|
|
545
|
+
for _size, key in sizes:
|
|
546
|
+
if _probe_size(new_body, meta) <= TOKEN_BUDGET_CHARS:
|
|
547
|
+
break
|
|
548
|
+
original = new_body[key]
|
|
549
|
+
# Aggressive halving until fits or string drops below 80 chars.
|
|
550
|
+
cut = max(80, len(original) // 2)
|
|
551
|
+
while cut > 0 and _probe_size(new_body, meta) > TOKEN_BUDGET_CHARS:
|
|
552
|
+
new_body[key] = original[:cut] + "…[truncated]"
|
|
553
|
+
cut //= 2
|
|
554
|
+
truncated_fields.append(key)
|
|
555
|
+
if truncated_fields:
|
|
556
|
+
meta["truncated_string_fields"] = truncated_fields
|
|
557
|
+
return new_body, meta, _probe_size(new_body, meta) <= TOKEN_BUDGET_CHARS
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _trim_lists_balanced(body: dict, meta: dict) -> tuple[dict, dict, bool]:
|
|
561
|
+
# F1: balanced multi-bucket trim. When >=2 trimmable list keys are
|
|
562
|
+
# present, shrink the LARGEST remaining list repeatedly (never below 1
|
|
563
|
+
# item) until the envelope fits — so no bucket is silently zeroed while
|
|
564
|
+
# a sibling bucket keeps items. Root cause of cos_graph_contracts(
|
|
565
|
+
# kinds=http,mcp) returning http_routes=[] while mcp_tools kept 70:
|
|
566
|
+
# the sequential ladder drained http_routes (earlier key) to 0 because
|
|
567
|
+
# mcp_tools (later key) alone exceeded the budget. Uses the same
|
|
568
|
+
# truncated_<key>_from/to markers as the per-key ladder so coverage
|
|
569
|
+
# flags stay consistent. No-op (and identical to the old path) when
|
|
570
|
+
# fewer than two list buckets are present.
|
|
571
|
+
present = [
|
|
572
|
+
k for k in _TRIMMABLE_LIST_KEYS if isinstance(body.get(k), list) and len(body[k]) > 0
|
|
573
|
+
]
|
|
574
|
+
if len(present) < 2:
|
|
575
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
576
|
+
originals = {k: len(body[k]) for k in present}
|
|
577
|
+
while _probe_size(body, meta) > TOKEN_BUDGET_CHARS:
|
|
578
|
+
biggest = max(present, key=lambda x: len(body[x]))
|
|
579
|
+
cur_n = len(body[biggest])
|
|
580
|
+
if cur_n <= 1:
|
|
581
|
+
break # every bucket down to its last survivor — fall through
|
|
582
|
+
new_n = max(1, int(cur_n * 0.85))
|
|
583
|
+
if new_n >= cur_n:
|
|
584
|
+
new_n = cur_n - 1
|
|
585
|
+
body = {**body, biggest: body[biggest][:new_n]}
|
|
586
|
+
for k in present:
|
|
587
|
+
kept = len(body[k])
|
|
588
|
+
if kept < originals[k]:
|
|
589
|
+
meta[f"truncated_{k}_from"] = originals[k]
|
|
590
|
+
meta[f"truncated_{k}_to"] = kept
|
|
591
|
+
return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _apply_token_budget(body: dict, meta: dict) -> tuple[dict, dict, bool]:
|
|
595
|
+
# Trim every list-shaped field + edges_by_type dict-of-lists + (F#5
|
|
596
|
+
# final safety net) huge non-list scalars. did_trim=True when any
|
|
597
|
+
# field got cut. Contract: post-trim body MUST fit budget — if even
|
|
598
|
+
# scalar truncation fails (extreme pathological case), surface via
|
|
599
|
+
# meta.envelope_unshrinkable=True so caller can log/alert.
|
|
600
|
+
# Graph-subgraph shape ({nodes,edges}) is handled by ok() ahead of
|
|
601
|
+
# this call via _trim_coherent_subgraph, so the per-key trim ladder
|
|
602
|
+
# below never zeroes out edges.
|
|
603
|
+
did_any = False
|
|
604
|
+
fits = _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
|
|
605
|
+
# W6.2: shrink nested members (processes[*].members) BEFORE dropping
|
|
606
|
+
# whole entries — preserves community/process count signal.
|
|
607
|
+
for parent_key, member_key in _TRIMMABLE_NESTED_MEMBERS:
|
|
608
|
+
if fits:
|
|
609
|
+
break
|
|
610
|
+
body, meta, fits_after = _trim_nested_member_lists(body, meta, parent_key, member_key)
|
|
611
|
+
if f"truncated_{parent_key}_{member_key}" in meta:
|
|
612
|
+
did_any = True
|
|
613
|
+
fits = fits or fits_after
|
|
614
|
+
# W6.2: shrink dict-of-list buckets (impact.tiers, contracts.*) BEFORE
|
|
615
|
+
# toplevel list trims so we preserve top-level metadata (impacted_count,
|
|
616
|
+
# http_routes count etc).
|
|
617
|
+
for parent_key in _TRIMMABLE_NESTED_BUCKETS:
|
|
618
|
+
if fits:
|
|
619
|
+
break
|
|
620
|
+
body, meta, fits_after = _trim_nested_buckets(body, meta, parent_key)
|
|
621
|
+
if f"truncated_{parent_key}" in meta:
|
|
622
|
+
did_any = True
|
|
623
|
+
fits = fits or fits_after
|
|
624
|
+
# F1: balanced multi-bucket trim BEFORE the sequential per-key ladder,
|
|
625
|
+
# so an earlier bucket isn't zeroed while a larger later bucket keeps
|
|
626
|
+
# items. No-op for single-bucket payloads (the ladder below handles
|
|
627
|
+
# those unchanged).
|
|
628
|
+
if not fits:
|
|
629
|
+
_markers_before = sum(1 for k in meta if k.startswith("truncated_") and k.endswith("_to"))
|
|
630
|
+
body, meta, fits = _trim_lists_balanced(body, meta)
|
|
631
|
+
_markers_after = sum(1 for k in meta if k.startswith("truncated_") and k.endswith("_to"))
|
|
632
|
+
if _markers_after > _markers_before:
|
|
633
|
+
did_any = True
|
|
634
|
+
for key in _TRIMMABLE_LIST_KEYS:
|
|
635
|
+
if fits:
|
|
636
|
+
break
|
|
637
|
+
body, meta, fits_after = _trim_list_key(body, meta, key)
|
|
638
|
+
if f"truncated_{key}_to" in meta:
|
|
639
|
+
did_any = True
|
|
640
|
+
fits = fits or fits_after
|
|
641
|
+
if not fits:
|
|
642
|
+
body, meta, fits_after = _trim_edges_by_type(body, meta)
|
|
643
|
+
if "truncated_edges_by_type" in meta:
|
|
644
|
+
did_any = True
|
|
645
|
+
fits = fits or fits_after
|
|
646
|
+
if not fits:
|
|
647
|
+
body, meta, fits_after = _trim_huge_string_fields(body, meta)
|
|
648
|
+
if "truncated_string_fields" in meta:
|
|
649
|
+
did_any = True
|
|
650
|
+
fits = fits or fits_after
|
|
651
|
+
if not fits:
|
|
652
|
+
meta["envelope_unshrinkable"] = True
|
|
653
|
+
# Debug-level here; the @safe_tool layer re-logs this at ERROR WITH the
|
|
654
|
+
# tool name so the eye gets an actionable, deduplicable row
|
|
655
|
+
# instead of an anonymous one. Tools not wrapped by safe_tool are rare.
|
|
656
|
+
logger.debug(
|
|
657
|
+
"envelope %d chars > budget %d after all trims",
|
|
658
|
+
_probe_size(body, meta),
|
|
659
|
+
TOKEN_BUDGET_CHARS,
|
|
660
|
+
)
|
|
661
|
+
did_any = True
|
|
662
|
+
return body, meta, did_any
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
# ---------------------------------------------------------------------------
|
|
666
|
+
# Error envelope
|
|
667
|
+
# ---------------------------------------------------------------------------
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def fail(
|
|
671
|
+
category: ErrorCategory,
|
|
672
|
+
message: str,
|
|
673
|
+
*,
|
|
674
|
+
retryable: bool | None = None,
|
|
675
|
+
) -> str:
|
|
676
|
+
"""Wrap an error in the canonical envelope.
|
|
677
|
+
|
|
678
|
+
If `retryable` is not given it is inferred from the category:
|
|
679
|
+
`transient` and `unavailable` default to True; everything else to False.
|
|
680
|
+
Override when a specific call has better information.
|
|
681
|
+
"""
|
|
682
|
+
if retryable is None:
|
|
683
|
+
retryable = category in _RETRYABLE_BY_DEFAULT
|
|
684
|
+
return json.dumps(
|
|
685
|
+
{
|
|
686
|
+
"ok": False,
|
|
687
|
+
"error": {
|
|
688
|
+
"category": category,
|
|
689
|
+
"retryable": retryable,
|
|
690
|
+
"message": message,
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
indent=2,
|
|
694
|
+
default=str,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
# ---------------------------------------------------------------------------
|
|
699
|
+
# G36: SSOT validation helpers — uniform shape across all cos_* tools.
|
|
700
|
+
# Return None when valid, a fail-envelope string when invalid. Caller
|
|
701
|
+
# pattern is `err = validate_X(...); if err: return err`.
|
|
702
|
+
# ---------------------------------------------------------------------------
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def validate_enum(value: Any, allowed: tuple[str, ...], field: str) -> str | None:
|
|
706
|
+
# None when valid; fail envelope when value not in allowed.
|
|
707
|
+
if value not in allowed:
|
|
708
|
+
return fail(
|
|
709
|
+
"validation",
|
|
710
|
+
f"{field} must be one of {allowed} (got {value!r})",
|
|
711
|
+
)
|
|
712
|
+
return None
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def validate_positive_int(value: Any, field: str) -> str | None:
|
|
716
|
+
# Reject non-int / <=0; otherwise None.
|
|
717
|
+
if not isinstance(value, int) or value <= 0:
|
|
718
|
+
return fail("validation", f"{field} must be a positive int (got {value!r})")
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def validate_non_empty_str(value: Any, field: str) -> str | None:
|
|
723
|
+
if not isinstance(value, str) or not value.strip():
|
|
724
|
+
return fail("validation", f"{field} must be a non-empty string")
|
|
725
|
+
return None
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def validate_min_chars(value: Any, field: str, *, min_chars: int = 2) -> str | None:
|
|
729
|
+
# W7.1 / R4-09: parity between cos_graph_query (already enforces 2)
|
|
730
|
+
# and cos_graph_resolve (silently accepted single-char fuzzy).
|
|
731
|
+
if not isinstance(value, str):
|
|
732
|
+
return fail("validation", f"{field} must be a string (got {type(value).__name__})")
|
|
733
|
+
if len(value.strip()) < min_chars:
|
|
734
|
+
return fail("validation", f"{field} must be at least {min_chars} chars (got {value!r})")
|
|
735
|
+
return None
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def validate_confidence(value: Any, field: str) -> str | None:
|
|
739
|
+
# W7.1 / R4-19/R4-26: confidence scores live in [0.0, 1.0]; the
|
|
740
|
+
# cos_graph_impact / cos_graph_query tools silently accepted 999
|
|
741
|
+
# and filtered everything.
|
|
742
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
743
|
+
return fail("validation", f"{field} must be a number (got {type(value).__name__})")
|
|
744
|
+
if value < 0.0 or value > 1.0:
|
|
745
|
+
return fail("validation", f"{field} must be in [0.0, 1.0] (got {value})")
|
|
746
|
+
return None
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def clamp_int(value: int, *, min_v: int, max_v: int) -> tuple[int, bool]:
|
|
750
|
+
# Return (clamped, was_clamped). Caller surfaces was_clamped in meta.
|
|
751
|
+
clamped = max(min_v, min(value, max_v))
|
|
752
|
+
return clamped, clamped != value
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _sqlite_db_identity(args: tuple[Any, ...]) -> str:
|
|
756
|
+
# Multi-process forensics (mcp-error-envelope.md § Internal-error
|
|
757
|
+
# forensics): every server process appends to the same .mcp.log, so a
|
|
758
|
+
# sqlite failure must name the DB file its connection was attached to.
|
|
759
|
+
conn = args[0] if args and isinstance(args[0], sqlite3.Connection) else None
|
|
760
|
+
if conn is None:
|
|
761
|
+
return ""
|
|
762
|
+
try:
|
|
763
|
+
rows = conn.execute("PRAGMA database_list").fetchall()
|
|
764
|
+
return " db=" + ";".join(str(row[2]) for row in rows)
|
|
765
|
+
except sqlite3.Error:
|
|
766
|
+
return " db=<database_list unavailable>"
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def _log_tool_failure(tool_name: str, exc: Exception, args: tuple[Any, ...]) -> None:
|
|
770
|
+
db_identity = _sqlite_db_identity(args) if isinstance(exc, sqlite3.Error) else ""
|
|
771
|
+
logger.exception(
|
|
772
|
+
"tool %s raised %s [pid=%d thread=%s]%s",
|
|
773
|
+
tool_name,
|
|
774
|
+
type(exc).__name__,
|
|
775
|
+
os.getpid(),
|
|
776
|
+
threading.current_thread().name,
|
|
777
|
+
db_identity,
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
# ---------------------------------------------------------------------------
|
|
782
|
+
# Module gating (TASK-354) — tools owned by a disabled subsystem module fail
|
|
783
|
+
# with category=module_disabled instead of behaving as if the tool vanished.
|
|
784
|
+
# Reads src/core/subsystems.yaml (tool entries may end in '*' for a prefix
|
|
785
|
+
# family) + $COS_STATE_DIR/subsystems-state.json. Fail-open: any read error
|
|
786
|
+
# means no gating.
|
|
787
|
+
|
|
788
|
+
_MODULE_GATE_CACHE: dict[str, Any] = {"map": None}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _tool_module_map() -> list[tuple[str, str]]:
|
|
792
|
+
if _MODULE_GATE_CACHE["map"] is not None:
|
|
793
|
+
return _MODULE_GATE_CACHE["map"]
|
|
794
|
+
pairs: list[tuple[str, str]] = []
|
|
795
|
+
try:
|
|
796
|
+
from pathlib import Path
|
|
797
|
+
|
|
798
|
+
import yaml
|
|
799
|
+
|
|
800
|
+
manifest = Path(__file__).resolve().parents[2] / "subsystems.yaml"
|
|
801
|
+
data = yaml.safe_load(manifest.read_text(encoding="utf-8")) or {}
|
|
802
|
+
for module in data.get("modules") or []:
|
|
803
|
+
if module.get("kernel"):
|
|
804
|
+
continue
|
|
805
|
+
for tool in module.get("tools") or []:
|
|
806
|
+
pairs.append((str(tool), str(module["id"])))
|
|
807
|
+
except Exception as exc:
|
|
808
|
+
logger.debug("module gate map unavailable: %s", exc)
|
|
809
|
+
_MODULE_GATE_CACHE["map"] = pairs
|
|
810
|
+
return pairs
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _disabled_modules() -> set[str]:
|
|
814
|
+
try:
|
|
815
|
+
from pathlib import Path
|
|
816
|
+
|
|
817
|
+
from thinking_os.database import get_active_project_root, resolve_db_path
|
|
818
|
+
|
|
819
|
+
# A bound Hub request scope must win over the ambient $COS_STATE_DIR
|
|
820
|
+
# (the launch project's), else every /api/p/<slug>/* call is gated by
|
|
821
|
+
# the launch project's enabled-modules set instead of the slug's.
|
|
822
|
+
if get_active_project_root() is not None:
|
|
823
|
+
state_dir = resolve_db_path().parent
|
|
824
|
+
else:
|
|
825
|
+
state_dir = Path(os.environ.get("COS_STATE_DIR") or ".coding-os")
|
|
826
|
+
state_file = state_dir / "subsystems-state.json"
|
|
827
|
+
if not state_file.is_file():
|
|
828
|
+
return set()
|
|
829
|
+
data = json.loads(state_file.read_text(encoding="utf-8"))
|
|
830
|
+
return {str(x) for x in data.get("disabled") or []}
|
|
831
|
+
except Exception as exc:
|
|
832
|
+
logger.debug("subsystems state unreadable (%s) — gating off", exc)
|
|
833
|
+
return set()
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def _gated_module(tool_name: str) -> str | None:
|
|
837
|
+
disabled = _disabled_modules()
|
|
838
|
+
if not disabled:
|
|
839
|
+
return None
|
|
840
|
+
for pattern, module_id in _tool_module_map():
|
|
841
|
+
if module_id not in disabled:
|
|
842
|
+
continue
|
|
843
|
+
if pattern.endswith("*"):
|
|
844
|
+
if tool_name.startswith(pattern[:-1]):
|
|
845
|
+
return module_id
|
|
846
|
+
elif tool_name == pattern:
|
|
847
|
+
return module_id
|
|
848
|
+
return None
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
# Startup-time surface removal (TASK-476): a disabled module's tools must
|
|
852
|
+
# VANISH from the served list_tools, not merely fail when called — else the
|
|
853
|
+
# agent still sees them and hallucinates calls to a dead tool. The per-call
|
|
854
|
+
# safe_tool gate above stays as defense-in-depth (a client holding a cached
|
|
855
|
+
# tool list, or a module toggled mid-session). Fail-open: any error leaves the
|
|
856
|
+
# full surface intact rather than serving a half-surface.
|
|
857
|
+
def apply_module_tool_gating(mcp: Any) -> dict:
|
|
858
|
+
"""Remove disabled-module-owned tools from the live MCP tool surface."""
|
|
859
|
+
disabled = _disabled_modules()
|
|
860
|
+
if not disabled:
|
|
861
|
+
return {"removed": [], "disabled_modules": []}
|
|
862
|
+
removed: list[str] = []
|
|
863
|
+
try:
|
|
864
|
+
for name in [tool.name for tool in mcp._tool_manager.list_tools()]:
|
|
865
|
+
if _gated_module(name):
|
|
866
|
+
mcp.remove_tool(name)
|
|
867
|
+
removed.append(name)
|
|
868
|
+
except Exception as exc:
|
|
869
|
+
logger.debug("module tool-surface gating skipped (%s) — full surface served", exc)
|
|
870
|
+
return {"removed": sorted(removed), "disabled_modules": sorted(disabled)}
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def safe_tool(
|
|
874
|
+
fn: Callable[..., str] | None = None,
|
|
875
|
+
*,
|
|
876
|
+
name: str | None = None,
|
|
877
|
+
) -> Callable[..., str] | Callable[[Callable[..., str]], Callable[..., str]]:
|
|
878
|
+
"""Decorator: convert unhandled exceptions inside a tool to `fail()`.
|
|
879
|
+
|
|
880
|
+
Mapping is conservative — specific Python exception types map to the
|
|
881
|
+
closest envelope category; anything unrecognized becomes `internal`.
|
|
882
|
+
Place this INSIDE `@mcp.tool(...)` so MCP still sees a string return:
|
|
883
|
+
|
|
884
|
+
@mcp.tool(name="cos_example", ...)
|
|
885
|
+
@safe_tool
|
|
886
|
+
def cos_example(...) -> str:
|
|
887
|
+
...
|
|
888
|
+
|
|
889
|
+
Pass `name=` when the Python function name differs from the registered MCP
|
|
890
|
+
tool name (e.g. `thinking_os_search` registered as `cos_search`) so the
|
|
891
|
+
subsystems module gate keys on the name `subsystems.yaml` lists, not the
|
|
892
|
+
internal function name. Without it the gate silently never fires for that
|
|
893
|
+
tool.
|
|
894
|
+
"""
|
|
895
|
+
|
|
896
|
+
def decorate(target: Callable[..., str]) -> Callable[..., str]:
|
|
897
|
+
gate_name = name or target.__name__
|
|
898
|
+
|
|
899
|
+
@wraps(target)
|
|
900
|
+
def wrapper(*args: Any, **kwargs: Any) -> str:
|
|
901
|
+
gated_by = _gated_module(gate_name)
|
|
902
|
+
if gated_by:
|
|
903
|
+
return fail(
|
|
904
|
+
"module_disabled",
|
|
905
|
+
f"tool '{gate_name}' belongs to the disabled '{gated_by}' module — "
|
|
906
|
+
f"enable it with `cos module enable {gated_by}`",
|
|
907
|
+
retryable=False,
|
|
908
|
+
)
|
|
909
|
+
try:
|
|
910
|
+
result = target(*args, **kwargs)
|
|
911
|
+
except PermissionError as exc:
|
|
912
|
+
_log_tool_failure(gate_name, exc, args)
|
|
913
|
+
return fail("permission", str(exc) or "permission denied", retryable=False)
|
|
914
|
+
except FileNotFoundError as exc:
|
|
915
|
+
_log_tool_failure(gate_name, exc, args)
|
|
916
|
+
return fail("not_found", str(exc) or "resource not found", retryable=False)
|
|
917
|
+
except (TimeoutError, ConnectionError) as exc:
|
|
918
|
+
_log_tool_failure(gate_name, exc, args)
|
|
919
|
+
return fail("transient", str(exc) or type(exc).__name__, retryable=True)
|
|
920
|
+
except ValueError as exc:
|
|
921
|
+
_log_tool_failure(gate_name, exc, args)
|
|
922
|
+
return fail("validation", str(exc) or "invalid input", retryable=False)
|
|
923
|
+
except ImportError as exc:
|
|
924
|
+
_log_tool_failure(gate_name, exc, args)
|
|
925
|
+
return fail(
|
|
926
|
+
"unavailable", str(exc) or "optional dependency missing", retryable=True
|
|
927
|
+
)
|
|
928
|
+
except Exception as exc:
|
|
929
|
+
_log_tool_failure(gate_name, exc, args)
|
|
930
|
+
return fail("internal", f"{type(exc).__name__}: {exc}", retryable=False)
|
|
931
|
+
|
|
932
|
+
# Name the offending tool when ok() flagged the envelope unshrinkable.
|
|
933
|
+
# ok() logs the size from a context without the tool name, leaving the
|
|
934
|
+
# eye with an unactionable "something is 265KB" error.
|
|
935
|
+
if isinstance(result, str) and "envelope_unshrinkable" in result:
|
|
936
|
+
logger.error(
|
|
937
|
+
"tool %s returned an unshrinkable envelope (%d chars > %d budget)",
|
|
938
|
+
gate_name,
|
|
939
|
+
len(result),
|
|
940
|
+
TOKEN_BUDGET_CHARS,
|
|
941
|
+
)
|
|
942
|
+
return result
|
|
943
|
+
|
|
944
|
+
return wrapper
|
|
945
|
+
|
|
946
|
+
# Bare `@safe_tool` → fn is the target. `@safe_tool(name=...)` → fn is None.
|
|
947
|
+
return decorate(fn) if fn is not None else decorate
|