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
core/web/routes/hub.py
ADDED
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
"""core.web.routes.hub — global Hub registry endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import uuid
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from fastapi import APIRouter, Body, Query
|
|
17
|
+
from fastapi.responses import JSONResponse
|
|
18
|
+
|
|
19
|
+
_REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
|
|
20
|
+
if str(_REPO_ROOT) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(_REPO_ROOT))
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("coding_os.web.hub")
|
|
24
|
+
router = APIRouter(prefix="/api/hub", tags=["hub"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Shared helpers
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _looks_like_cos_project(path: Path) -> bool:
|
|
33
|
+
"""Quick heuristic: directory exists and has .coding-os/ inside."""
|
|
34
|
+
try:
|
|
35
|
+
return path.is_dir() and (path / ".coding-os").is_dir()
|
|
36
|
+
except OSError:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_hub_state_dir(coding_os: Path) -> bool:
|
|
41
|
+
"""True when this .coding-os/ is the GLOBAL hub state dir, not a project's.
|
|
42
|
+
|
|
43
|
+
The global ~/.coding-os/ carries the hub registry + pid; a project's
|
|
44
|
+
.coding-os/ never does (hub-architecture.md § address spaces). Lets the
|
|
45
|
+
home/global dir be rejected as a phantom "runtime-cwd" project.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
return (coding_os / "registry.json").is_file() or (coding_os / "hub.pid").is_file()
|
|
49
|
+
except OSError:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_meta_repo(path: Path) -> bool:
|
|
54
|
+
"""True when `path` is the coding-os meta-repo checkout itself.
|
|
55
|
+
|
|
56
|
+
Recognises the dogfood case: the meta-repo lives at
|
|
57
|
+
`<somewhere>/coding-os/` AND ships its own `.coding-os/`
|
|
58
|
+
(dogfood — Principle P5). It must never be flagged as "nested
|
|
59
|
+
inside another coding-os project" just because a higher-up
|
|
60
|
+
ancestor (e.g. `~/.coding-os/` scratch dir) happens to have a
|
|
61
|
+
`.coding-os/`.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
return (
|
|
65
|
+
(path / "src" / "cli" / "main.py").is_file()
|
|
66
|
+
and (path / "src" / "core" / "thinking_os" / "server.py").is_file()
|
|
67
|
+
and (path / "pyproject.toml").is_file()
|
|
68
|
+
)
|
|
69
|
+
except OSError:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_registered_project(path: Path) -> bool:
|
|
74
|
+
"""True iff `path` (resolved) is recorded in the cli registry.
|
|
75
|
+
|
|
76
|
+
The nested-project check should only fire when the enclosing
|
|
77
|
+
ancestor is an *actual registered project*. A stray
|
|
78
|
+
`~/.coding-os/` (left over from a test run, or the user's
|
|
79
|
+
scratch dir) is not a project — blocking on it has rejected
|
|
80
|
+
legitimate contributor checkouts (issue reported 2026-05-23).
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
from cli.registry import load_registry # type: ignore
|
|
84
|
+
|
|
85
|
+
reg = load_registry()
|
|
86
|
+
except Exception:
|
|
87
|
+
return False
|
|
88
|
+
target = str(path.resolve())
|
|
89
|
+
for p in reg.projects:
|
|
90
|
+
try:
|
|
91
|
+
if str(Path(p.path).resolve()) == target:
|
|
92
|
+
return True
|
|
93
|
+
except (OSError, RuntimeError):
|
|
94
|
+
continue
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _ancestor_with_coding_os(path: Path) -> Path | None:
|
|
99
|
+
"""Walk parents looking for an enclosing **registered** coding-os
|
|
100
|
+
project root.
|
|
101
|
+
|
|
102
|
+
Returns the first ancestor (strictly above `path`) that has a
|
|
103
|
+
`.coding-os/` directory AND is recorded in the cli registry —
|
|
104
|
+
a true nesting violation. An ancestor with just `.coding-os/`
|
|
105
|
+
on disk but not in the registry is treated as noise (scratch,
|
|
106
|
+
leftover) and ignored.
|
|
107
|
+
|
|
108
|
+
Also skips the check entirely when `path` is the meta-repo
|
|
109
|
+
itself (dogfood — see `_is_meta_repo`).
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
resolved = path.resolve()
|
|
113
|
+
except (OSError, RuntimeError):
|
|
114
|
+
return None
|
|
115
|
+
if _is_meta_repo(resolved):
|
|
116
|
+
return None
|
|
117
|
+
for parent in resolved.parents:
|
|
118
|
+
try:
|
|
119
|
+
if (parent / ".coding-os").is_dir() and _is_registered_project(parent):
|
|
120
|
+
return parent
|
|
121
|
+
except OSError:
|
|
122
|
+
continue
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _resolve_slug_from_registry(cwd: Path) -> str:
|
|
127
|
+
"""Match cli.registry._derive_slug so UI and API agree on spelling."""
|
|
128
|
+
try:
|
|
129
|
+
from cli.registry import _derive_slug # type: ignore
|
|
130
|
+
|
|
131
|
+
return _derive_slug(cwd)
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
logger.debug("cli.registry._derive_slug unavailable: %s", exc)
|
|
134
|
+
return cwd.name.lower().strip() or "project"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _derive_runtime_entry() -> dict | None:
|
|
138
|
+
"""Return an in-memory entry for the cwd project when it's a cos repo."""
|
|
139
|
+
cwd = Path(os.environ.get("COS_PROJECT_ROOT") or os.getcwd()).resolve()
|
|
140
|
+
# $HOME hosts the GLOBAL hub state at ~/.coding-os/ — never a project.
|
|
141
|
+
try:
|
|
142
|
+
if cwd == Path.home().resolve():
|
|
143
|
+
return None
|
|
144
|
+
except (OSError, RuntimeError):
|
|
145
|
+
pass
|
|
146
|
+
if not _looks_like_cos_project(cwd):
|
|
147
|
+
return None
|
|
148
|
+
# A .coding-os/ carrying the hub registry/pid is the global state dir,
|
|
149
|
+
# not a project root (e.g. the Hub booted from a non-home COS_HOME).
|
|
150
|
+
if _is_hub_state_dir(cwd / ".coding-os"):
|
|
151
|
+
return None
|
|
152
|
+
# .coding-os/ only exists at the project root. A nested .coding-os/
|
|
153
|
+
# inside another project (e.g. src/core/web/ui/.coding-os/ left over
|
|
154
|
+
# from a test run) must NEVER surface as a separate project entry —
|
|
155
|
+
# we'd hijack the Hub's "default" slot with a stray dir.
|
|
156
|
+
if _ancestor_with_coding_os(cwd) is not None:
|
|
157
|
+
return None
|
|
158
|
+
return {
|
|
159
|
+
"slug": _resolve_slug_from_registry(cwd),
|
|
160
|
+
"path": str(cwd),
|
|
161
|
+
"created_at": "",
|
|
162
|
+
"source": "runtime-cwd",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _err(category: str, message: str, *, status: int = 400) -> JSONResponse:
|
|
167
|
+
"""Shared error-envelope shape matching the rest of /api/*."""
|
|
168
|
+
return JSONResponse(
|
|
169
|
+
status_code=status,
|
|
170
|
+
content={
|
|
171
|
+
"error": {
|
|
172
|
+
"category": category,
|
|
173
|
+
"retryable": False,
|
|
174
|
+
"message": message,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _validate_project_path(raw: str) -> tuple[Path | None, JSONResponse | None]:
|
|
181
|
+
"""Sanitize an incoming project-path string for registry mutations."""
|
|
182
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
183
|
+
return None, _err("validation", "path is required and must be a non-empty string")
|
|
184
|
+
try:
|
|
185
|
+
path = Path(raw).expanduser().resolve()
|
|
186
|
+
except (OSError, RuntimeError) as exc:
|
|
187
|
+
return None, _err("validation", f"invalid path: {exc}")
|
|
188
|
+
if not path.is_dir():
|
|
189
|
+
return None, _err("not_found", f"path is not a directory: {path}", status=404)
|
|
190
|
+
if not _looks_like_cos_project(path):
|
|
191
|
+
return None, _err(
|
|
192
|
+
"validation",
|
|
193
|
+
f"{path} has no .coding-os/ — run `cos init` there first",
|
|
194
|
+
)
|
|
195
|
+
ancestor = _ancestor_with_coding_os(path)
|
|
196
|
+
if ancestor is not None:
|
|
197
|
+
return None, _err(
|
|
198
|
+
"validation",
|
|
199
|
+
(
|
|
200
|
+
f"{path} sits inside {ancestor} which is already a coding-os "
|
|
201
|
+
"project — .coding-os/ must only exist at the project root. "
|
|
202
|
+
"Remove the nested .coding-os/ directory."
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
return path, None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
# GET /api/hub/projects
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@router.get("/projects")
|
|
214
|
+
def hub_projects() -> dict:
|
|
215
|
+
"""List every registered coding-os project whose directory still exists."""
|
|
216
|
+
projects: list[dict] = []
|
|
217
|
+
seen_paths: set[str] = set()
|
|
218
|
+
try:
|
|
219
|
+
from cli.registry import load_registry # type: ignore
|
|
220
|
+
|
|
221
|
+
reg = load_registry()
|
|
222
|
+
except Exception as exc:
|
|
223
|
+
logger.debug("load_registry failed: %s", exc)
|
|
224
|
+
reg = None
|
|
225
|
+
|
|
226
|
+
if reg is not None:
|
|
227
|
+
for p in reg.projects:
|
|
228
|
+
path = Path(p.path)
|
|
229
|
+
if not _looks_like_cos_project(path):
|
|
230
|
+
continue
|
|
231
|
+
resolved = str(path.resolve())
|
|
232
|
+
if resolved in seen_paths:
|
|
233
|
+
continue
|
|
234
|
+
seen_paths.add(resolved)
|
|
235
|
+
projects.append(
|
|
236
|
+
{
|
|
237
|
+
"slug": p.slug,
|
|
238
|
+
"path": p.path,
|
|
239
|
+
"created_at": p.created_at,
|
|
240
|
+
"source": "registry",
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
runtime = _derive_runtime_entry()
|
|
245
|
+
if runtime is not None:
|
|
246
|
+
resolved = str(Path(runtime["path"]).resolve())
|
|
247
|
+
if resolved not in seen_paths:
|
|
248
|
+
projects.insert(0, runtime)
|
|
249
|
+
|
|
250
|
+
return {"projects": projects, "count": len(projects)}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@router.get("/agents")
|
|
254
|
+
def hub_agents() -> dict:
|
|
255
|
+
"""Cross-project live-agent roster — one group per registered project (TASK-437)."""
|
|
256
|
+
from web.routes.presence import cross_project_agents # type: ignore
|
|
257
|
+
|
|
258
|
+
groups = cross_project_agents()
|
|
259
|
+
return {"projects": groups, "count": sum(len(g["agents"]) for g in groups)}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# POST /api/hub/registry/add
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@router.post("/registry/add")
|
|
268
|
+
def hub_registry_add(
|
|
269
|
+
path: str = Body(..., embed=True),
|
|
270
|
+
slug: str | None = Body(None, embed=True),
|
|
271
|
+
):
|
|
272
|
+
"""Register an existing `.coding-os/` directory with the Hub."""
|
|
273
|
+
resolved, err = _validate_project_path(path)
|
|
274
|
+
if err is not None:
|
|
275
|
+
return err
|
|
276
|
+
if slug is not None and not isinstance(slug, str):
|
|
277
|
+
return _err("validation", "slug must be a string when provided")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
from cli.registry import add_project # type: ignore
|
|
281
|
+
except Exception as exc:
|
|
282
|
+
return _err(
|
|
283
|
+
"unavailable",
|
|
284
|
+
f"cli.registry unavailable: {exc}",
|
|
285
|
+
status=503,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
entry = add_project(resolved, slug=(slug or "").strip() or None)
|
|
290
|
+
except Exception as exc:
|
|
291
|
+
return _err("validation", str(exc))
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
"data": {
|
|
295
|
+
"slug": entry.slug,
|
|
296
|
+
"path": entry.path,
|
|
297
|
+
"created_at": entry.created_at,
|
|
298
|
+
},
|
|
299
|
+
"meta": {"layer": "hub", "source": "hub.registry_add"},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
# GET /api/hub/stacks + POST /api/hub/registry/init (create-from-UI, TASK-249)
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
_PROJECT_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@router.get("/stacks")
|
|
311
|
+
def hub_stacks() -> dict:
|
|
312
|
+
"""List installable stack templates (data-driven from src/templates/*/stack.yaml)."""
|
|
313
|
+
try:
|
|
314
|
+
from cli.list_stacks import TEMPLATES_DIR # type: ignore
|
|
315
|
+
from cli.stack_registry import load_stack_registry # type: ignore
|
|
316
|
+
|
|
317
|
+
reg = load_stack_registry(TEMPLATES_DIR)
|
|
318
|
+
except Exception as exc:
|
|
319
|
+
return _err("unavailable", f"stack registry unavailable: {exc}", status=503)
|
|
320
|
+
stacks = [
|
|
321
|
+
{"id": s.id, "label": s.label, "category": s.category, "language": s.language}
|
|
322
|
+
for s in sorted(reg.values(), key=lambda p: p.id)
|
|
323
|
+
if s.id != "_base"
|
|
324
|
+
]
|
|
325
|
+
return {
|
|
326
|
+
"data": {"stacks": stacks, "count": len(stacks)},
|
|
327
|
+
"meta": {"layer": "hub", "source": "hub.stacks"},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@router.get("/adapters")
|
|
332
|
+
def hub_adapters() -> dict:
|
|
333
|
+
"""List installable agent adapters (data-driven from src/adapters/*/adapter.yaml)."""
|
|
334
|
+
try:
|
|
335
|
+
from cli._resources import adapters_dir # type: ignore
|
|
336
|
+
from cli.adapter_registry import load_adapter_registry # type: ignore
|
|
337
|
+
|
|
338
|
+
reg = load_adapter_registry(adapters_dir())
|
|
339
|
+
except Exception as exc:
|
|
340
|
+
return _err("unavailable", f"adapter registry unavailable: {exc}", status=503)
|
|
341
|
+
adapters = [{"id": a.id, "label": a.label} for a in sorted(reg.values(), key=lambda a: a.id)]
|
|
342
|
+
return {
|
|
343
|
+
"data": {"adapters": adapters, "count": len(adapters)},
|
|
344
|
+
"meta": {"layer": "hub", "source": "hub.adapters"},
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@router.get("/modules")
|
|
349
|
+
def hub_modules() -> dict:
|
|
350
|
+
"""List subsystem modules (data-driven from src/core/subsystems.yaml) for the Composer."""
|
|
351
|
+
try:
|
|
352
|
+
from cli.subsystems import ( # type: ignore
|
|
353
|
+
load_profiles,
|
|
354
|
+
load_subsystems,
|
|
355
|
+
resolve_profile,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
registry = load_subsystems()
|
|
359
|
+
_, default_profile = load_profiles()
|
|
360
|
+
default_disabled = resolve_profile(default_profile)
|
|
361
|
+
except Exception as exc:
|
|
362
|
+
return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503)
|
|
363
|
+
modules = [
|
|
364
|
+
{
|
|
365
|
+
"id": m.id,
|
|
366
|
+
"label": m.label,
|
|
367
|
+
"kernel": m.kernel,
|
|
368
|
+
"depends_on": list(m.depends_on),
|
|
369
|
+
}
|
|
370
|
+
for m in registry.values()
|
|
371
|
+
if not m.hidden
|
|
372
|
+
]
|
|
373
|
+
visible = {m["id"] for m in modules}
|
|
374
|
+
return {
|
|
375
|
+
"data": {
|
|
376
|
+
"modules": modules,
|
|
377
|
+
"count": len(modules),
|
|
378
|
+
"default_profile": default_profile,
|
|
379
|
+
"default_disabled": [m for m in default_disabled if m in visible],
|
|
380
|
+
},
|
|
381
|
+
"meta": {"layer": "hub", "source": "hub.modules"},
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@router.get("/presets")
|
|
386
|
+
def hub_presets() -> dict:
|
|
387
|
+
"""List project presets (data-driven from src/templates/_presets/*.yaml)."""
|
|
388
|
+
try:
|
|
389
|
+
from cli.list_stacks import TEMPLATES_DIR # type: ignore
|
|
390
|
+
from cli.preset_registry import load_preset_registry # type: ignore
|
|
391
|
+
from cli.stack_registry import load_stack_registry # type: ignore
|
|
392
|
+
|
|
393
|
+
known = set(load_stack_registry(TEMPLATES_DIR).keys())
|
|
394
|
+
registry = load_preset_registry(TEMPLATES_DIR, known_stacks=known)
|
|
395
|
+
except Exception as exc:
|
|
396
|
+
return _err("unavailable", f"preset registry unavailable: {exc}", status=503)
|
|
397
|
+
templates_root = TEMPLATES_DIR.resolve()
|
|
398
|
+
presets = [
|
|
399
|
+
{
|
|
400
|
+
"id": p.id,
|
|
401
|
+
"label": p.label,
|
|
402
|
+
"description": p.description,
|
|
403
|
+
"stacks": list(p.stacks),
|
|
404
|
+
"provenance": (
|
|
405
|
+
"core" if p.source_path.resolve().is_relative_to(templates_root) else "user"
|
|
406
|
+
),
|
|
407
|
+
}
|
|
408
|
+
for p in sorted(registry.values(), key=lambda p: p.id)
|
|
409
|
+
]
|
|
410
|
+
return {
|
|
411
|
+
"data": {"presets": presets, "count": len(presets)},
|
|
412
|
+
"meta": {"layer": "hub", "source": "hub.presets"},
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@router.get("/skills")
|
|
417
|
+
def hub_skills() -> dict:
|
|
418
|
+
"""Global core+stack skill catalog with provenance + validation status."""
|
|
419
|
+
try:
|
|
420
|
+
from cli.skills_list import collect_skill_catalog # type: ignore
|
|
421
|
+
|
|
422
|
+
catalog = collect_skill_catalog()
|
|
423
|
+
except Exception as exc:
|
|
424
|
+
return _err("unavailable", f"skill catalog unavailable: {exc}", status=503)
|
|
425
|
+
return {"data": catalog, "meta": {"layer": "hub", "source": "hub.skills"}}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@router.get("/stacks/{stack_id}/skills")
|
|
429
|
+
def hub_stack_skills(stack_id: str) -> dict:
|
|
430
|
+
"""Required/recommended/optional skill groups for one stack (onboarding preview)."""
|
|
431
|
+
try:
|
|
432
|
+
from cli.skills_list import collect_stack_skill_groups # type: ignore
|
|
433
|
+
|
|
434
|
+
payload = collect_stack_skill_groups(stack_id)
|
|
435
|
+
except KeyError:
|
|
436
|
+
return _err("not_found", f"stack '{stack_id}' not found", status=404)
|
|
437
|
+
except Exception as exc:
|
|
438
|
+
return _err("unavailable", f"skill groups unavailable: {exc}", status=503)
|
|
439
|
+
return {"data": payload, "meta": {"layer": "hub", "source": "hub.stack_skills"}}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _cos_init_command() -> list[str]:
|
|
443
|
+
"""Resolve how to invoke `cos` — the installed bin, else `python -m cli.main`."""
|
|
444
|
+
found = shutil.which("cos")
|
|
445
|
+
return [found] if found else [sys.executable, "-m", "cli.main"]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _resolve_agents(agent: str, agents: list[str] | None) -> list[str]:
|
|
449
|
+
"""Merge the single `agent` (back-compat) + `agents` list into a deduped,
|
|
450
|
+
order-preserving list; defaults to ['claude'] when both are empty.
|
|
451
|
+
|
|
452
|
+
A project may legitimately host several adapters (.claude/ + .codex/) —
|
|
453
|
+
the CLI already supports `--agent claude,codex` (main.py::_parse_agents)."""
|
|
454
|
+
merged: list[str] = []
|
|
455
|
+
for candidate in [*(agents or []), agent]:
|
|
456
|
+
token = (candidate or "").strip()
|
|
457
|
+
if token and token not in merged:
|
|
458
|
+
merged.append(token)
|
|
459
|
+
return merged or ["claude"]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _validate_init_inputs(
|
|
463
|
+
name: str,
|
|
464
|
+
parent_dir: str,
|
|
465
|
+
stacks: list[str],
|
|
466
|
+
preset: str,
|
|
467
|
+
agents: list[str],
|
|
468
|
+
extra_skills: list[str] | None = None,
|
|
469
|
+
disabled_modules: list[str] | None = None,
|
|
470
|
+
) -> tuple[JSONResponse | dict | None, dict]:
|
|
471
|
+
"""Shared dry-run validation for validate-init AND registry/init (SSOT).
|
|
472
|
+
|
|
473
|
+
Returns (error_response, info). info carries name/auto_named/parent/
|
|
474
|
+
target/templates once every check passes. No filesystem writes."""
|
|
475
|
+
info: dict = {}
|
|
476
|
+
name = (name or "").strip()
|
|
477
|
+
if name:
|
|
478
|
+
if not _PROJECT_NAME_RE.match(name):
|
|
479
|
+
return (
|
|
480
|
+
_err(
|
|
481
|
+
"validation",
|
|
482
|
+
"name must match ^[a-z0-9][a-z0-9._-]{0,63}$ (lowercase, no spaces)",
|
|
483
|
+
),
|
|
484
|
+
{},
|
|
485
|
+
)
|
|
486
|
+
info["name"], info["auto_named"] = name, False
|
|
487
|
+
else:
|
|
488
|
+
# "don't know yet" — temp slug, renameable later via registry rename.
|
|
489
|
+
info["name"], info["auto_named"] = f"proj-{uuid.uuid4().hex[:6]}", True
|
|
490
|
+
|
|
491
|
+
if not isinstance(parent_dir, str) or not parent_dir.strip():
|
|
492
|
+
return _err("validation", "parent_dir is required"), {}
|
|
493
|
+
try:
|
|
494
|
+
parent = Path(parent_dir).expanduser().resolve()
|
|
495
|
+
except (OSError, RuntimeError) as exc:
|
|
496
|
+
return _err("validation", f"invalid parent_dir: {exc}"), {}
|
|
497
|
+
if not parent.is_dir():
|
|
498
|
+
return _err("not_found", f"parent_dir is not a directory: {parent}", status=404), {}
|
|
499
|
+
if not os.access(parent, os.W_OK):
|
|
500
|
+
return _err("validation", f"parent_dir is not writable: {parent}"), {}
|
|
501
|
+
if _is_meta_repo(parent):
|
|
502
|
+
return _err("validation", "cannot scaffold inside the coding-os meta-repo checkout"), {}
|
|
503
|
+
target = parent / info["name"]
|
|
504
|
+
if target.exists():
|
|
505
|
+
return _err("conflict", f"{target} already exists", status=409), {}
|
|
506
|
+
nested = _ancestor_with_coding_os(parent)
|
|
507
|
+
if nested is not None:
|
|
508
|
+
return (
|
|
509
|
+
_err(
|
|
510
|
+
"validation",
|
|
511
|
+
f"{parent} is inside the registered project {nested} — pick a parent outside it",
|
|
512
|
+
),
|
|
513
|
+
{},
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
from cli.list_stacks import TEMPLATES_DIR # type: ignore
|
|
518
|
+
from cli.stack_registry import load_stack_registry # type: ignore
|
|
519
|
+
|
|
520
|
+
stack_reg = load_stack_registry(TEMPLATES_DIR)
|
|
521
|
+
except Exception as exc:
|
|
522
|
+
return _err("unavailable", f"stack registry unavailable: {exc}", status=503), {}
|
|
523
|
+
stacks = [s for s in (stacks or []) if s]
|
|
524
|
+
unknown = [s for s in stacks if s not in stack_reg]
|
|
525
|
+
if unknown:
|
|
526
|
+
return _err("validation", f"unknown stack(s): {unknown}"), {}
|
|
527
|
+
if preset:
|
|
528
|
+
if stacks:
|
|
529
|
+
return _err("validation", "preset and stacks are mutually exclusive"), {}
|
|
530
|
+
from cli.preset_registry import load_preset_registry # type: ignore
|
|
531
|
+
|
|
532
|
+
presets = load_preset_registry(TEMPLATES_DIR, known_stacks=set(stack_reg.keys()))
|
|
533
|
+
if preset not in presets:
|
|
534
|
+
return _err("not_found", f"preset '{preset}' not found", status=404), {}
|
|
535
|
+
info["templates"] = list(presets[preset].stacks)
|
|
536
|
+
else:
|
|
537
|
+
info["templates"] = stacks
|
|
538
|
+
|
|
539
|
+
try:
|
|
540
|
+
from cli._resources import adapters_dir # type: ignore
|
|
541
|
+
from cli.adapter_registry import load_adapter_registry # type: ignore
|
|
542
|
+
|
|
543
|
+
adapter_reg = load_adapter_registry(adapters_dir())
|
|
544
|
+
except Exception as exc:
|
|
545
|
+
return _err("unavailable", f"adapter registry unavailable: {exc}", status=503), {}
|
|
546
|
+
if not agents:
|
|
547
|
+
return _err("validation", "at least one agent is required"), {}
|
|
548
|
+
unknown_agents = [a for a in agents if a not in adapter_reg]
|
|
549
|
+
if unknown_agents:
|
|
550
|
+
return (
|
|
551
|
+
_err(
|
|
552
|
+
"validation",
|
|
553
|
+
f"unknown agent(s) {unknown_agents} — available: {sorted(adapter_reg)}",
|
|
554
|
+
),
|
|
555
|
+
{},
|
|
556
|
+
)
|
|
557
|
+
info["agents"] = list(agents)
|
|
558
|
+
|
|
559
|
+
# Module toggles (TASK-421): validate against the subsystem registry —
|
|
560
|
+
# kernel is non-disableable, and the set is closed over dependents before
|
|
561
|
+
# it reaches init: the scaffold REFUSES (never cascades) an unclosed one,
|
|
562
|
+
# so an unclosed set would return ok for a project contradicting the request.
|
|
563
|
+
disabled_modules = [m for m in (disabled_modules or []) if m]
|
|
564
|
+
if disabled_modules:
|
|
565
|
+
try:
|
|
566
|
+
from cli.subsystems import load_subsystems # type: ignore
|
|
567
|
+
|
|
568
|
+
registry_modules = load_subsystems()
|
|
569
|
+
except Exception as exc:
|
|
570
|
+
return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503), {}
|
|
571
|
+
unknown_mods = [m for m in disabled_modules if m not in registry_modules]
|
|
572
|
+
if unknown_mods:
|
|
573
|
+
return _err("validation", f"unknown module(s): {unknown_mods}"), {}
|
|
574
|
+
kernel_mods = [m for m in disabled_modules if registry_modules[m].kernel]
|
|
575
|
+
if kernel_mods:
|
|
576
|
+
return _err(
|
|
577
|
+
"validation", f"module(s) {kernel_mods} are kernel and cannot be disabled"
|
|
578
|
+
), {}
|
|
579
|
+
# Same closure the CLI applies, so an identical payload produces an
|
|
580
|
+
# identical project through either entrypoint.
|
|
581
|
+
try:
|
|
582
|
+
from cli.subsystems import close_over_dependents # type: ignore
|
|
583
|
+
|
|
584
|
+
disabled_modules = close_over_dependents(disabled_modules, registry_modules)
|
|
585
|
+
except Exception as exc:
|
|
586
|
+
return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503), {}
|
|
587
|
+
info["disabled_modules"] = disabled_modules
|
|
588
|
+
|
|
589
|
+
# Argv allowlist (TASK-363): every value that reaches the subprocess argv
|
|
590
|
+
# is validated against a registry — skills included.
|
|
591
|
+
if extra_skills:
|
|
592
|
+
try:
|
|
593
|
+
from cli.skill_registry import load_skill_registry # type: ignore
|
|
594
|
+
from cli.skills_list import CORE_SKILLS_DIR # type: ignore
|
|
595
|
+
|
|
596
|
+
known_skills = set(load_skill_registry(CORE_SKILLS_DIR).skills.keys())
|
|
597
|
+
except Exception as exc:
|
|
598
|
+
return _err("unavailable", f"skill registry unavailable: {exc}", status=503), {}
|
|
599
|
+
unknown_skills = [s for s in extra_skills if s not in known_skills]
|
|
600
|
+
if unknown_skills:
|
|
601
|
+
return _err("validation", f"unknown skill(s): {unknown_skills}"), {}
|
|
602
|
+
|
|
603
|
+
info["parent"], info["target"] = parent, target
|
|
604
|
+
return None, info
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@router.post("/registry/validate-init")
|
|
608
|
+
def hub_registry_validate_init(
|
|
609
|
+
name: str = Body("", embed=True),
|
|
610
|
+
parent_dir: str = Body(..., embed=True),
|
|
611
|
+
stacks: list[str] = Body(default_factory=list, embed=True),
|
|
612
|
+
preset: str = Body("", embed=True),
|
|
613
|
+
agent: str = Body("", embed=True),
|
|
614
|
+
agents: list[str] = Body(default_factory=list, embed=True),
|
|
615
|
+
extra_skills: list[str] = Body(default_factory=list, embed=True),
|
|
616
|
+
disabled_modules: list[str] = Body(default_factory=list, embed=True),
|
|
617
|
+
):
|
|
618
|
+
"""Dry-run validation + merged-config preview for the onboarding wizard (TASK-358)."""
|
|
619
|
+
resolved_agents = _resolve_agents(agent, agents)
|
|
620
|
+
error, info = _validate_init_inputs(
|
|
621
|
+
name,
|
|
622
|
+
parent_dir,
|
|
623
|
+
stacks,
|
|
624
|
+
preset,
|
|
625
|
+
resolved_agents,
|
|
626
|
+
extra_skills=extra_skills,
|
|
627
|
+
disabled_modules=disabled_modules,
|
|
628
|
+
)
|
|
629
|
+
if error is not None:
|
|
630
|
+
return error
|
|
631
|
+
swimlanes: list[str] = []
|
|
632
|
+
conflicts: list[str] = []
|
|
633
|
+
try:
|
|
634
|
+
from cli.config_composer import preview_coding_os_configs # type: ignore
|
|
635
|
+
from cli.list_stacks import TEMPLATES_DIR # type: ignore
|
|
636
|
+
|
|
637
|
+
merged, conflicts = preview_coding_os_configs(
|
|
638
|
+
info["templates"], templates_dir=TEMPLATES_DIR
|
|
639
|
+
)
|
|
640
|
+
scrumban = merged.get("scrumban-config.yaml") or {}
|
|
641
|
+
swimlanes = [
|
|
642
|
+
lane.get("id") for lane in scrumban.get("swimlanes") or [] if isinstance(lane, dict)
|
|
643
|
+
]
|
|
644
|
+
except Exception as exc:
|
|
645
|
+
logger.debug("dry-config preview failed: %s", exc)
|
|
646
|
+
return {
|
|
647
|
+
"data": {
|
|
648
|
+
"valid": True,
|
|
649
|
+
"name": info["name"],
|
|
650
|
+
"auto_named": info["auto_named"],
|
|
651
|
+
"target": str(info["target"]),
|
|
652
|
+
"templates": info["templates"],
|
|
653
|
+
"agents": info["agents"],
|
|
654
|
+
"disabled_modules": info["disabled_modules"],
|
|
655
|
+
"swimlanes": swimlanes,
|
|
656
|
+
"conflicts": conflicts,
|
|
657
|
+
},
|
|
658
|
+
"meta": {"layer": "hub", "source": "hub.registry_validate_init"},
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _default_profile_reenables(disabled_modules: list[str]) -> list[str]:
|
|
663
|
+
# Modules the chips kept ON that the default profile would disable —
|
|
664
|
+
# emitted as --enable-module so the payload stays authoritative without
|
|
665
|
+
# pinning a profile. Hidden modules follow the profile: the chips never
|
|
666
|
+
# show them, so the caller expressed no intent about them.
|
|
667
|
+
try:
|
|
668
|
+
from cli.subsystems import load_profiles, load_subsystems, resolve_profile # type: ignore
|
|
669
|
+
|
|
670
|
+
modules = load_subsystems()
|
|
671
|
+
_, default_name = load_profiles()
|
|
672
|
+
default_disabled = resolve_profile(default_name)
|
|
673
|
+
except Exception as exc:
|
|
674
|
+
logger.debug("profile lookup failed: %s", exc)
|
|
675
|
+
return []
|
|
676
|
+
off = set(disabled_modules)
|
|
677
|
+
return sorted(
|
|
678
|
+
module_id
|
|
679
|
+
for module_id in default_disabled
|
|
680
|
+
if module_id not in off and module_id in modules and not modules[module_id].hidden
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _build_cos_init_cmd(
|
|
685
|
+
name: str,
|
|
686
|
+
parent_dir: str,
|
|
687
|
+
stacks: list[str],
|
|
688
|
+
agents: list[str],
|
|
689
|
+
preset: str = "",
|
|
690
|
+
description: str = "",
|
|
691
|
+
extra_skills: list[str] | None = None,
|
|
692
|
+
disabled_modules: list[str] | None = None,
|
|
693
|
+
) -> list[str]:
|
|
694
|
+
"""One argv builder for sync AND job-based init (parity stays in the CLI)."""
|
|
695
|
+
cmd = _cos_init_command() + [
|
|
696
|
+
"init",
|
|
697
|
+
"--name",
|
|
698
|
+
name,
|
|
699
|
+
"--project-dir",
|
|
700
|
+
parent_dir,
|
|
701
|
+
"--agent",
|
|
702
|
+
",".join(agents),
|
|
703
|
+
"--yes",
|
|
704
|
+
"--no-index",
|
|
705
|
+
# Skip the heavy doc-RAG embedding but still build the knowledge graph
|
|
706
|
+
# (AST walk, no model) so the new project's Graph tab is never empty.
|
|
707
|
+
"--graph-index",
|
|
708
|
+
"--format",
|
|
709
|
+
"json",
|
|
710
|
+
]
|
|
711
|
+
if disabled_modules:
|
|
712
|
+
# init UNIONS the profile's disabled set with --disable-module, so the
|
|
713
|
+
# chips a user left ON must ride as --enable-module or the default
|
|
714
|
+
# profile silently turns them off. With no explicit set, stay silent so
|
|
715
|
+
# a bare API create matches a hand-typed `cos init`.
|
|
716
|
+
for module_id in _default_profile_reenables(disabled_modules):
|
|
717
|
+
cmd += ["--enable-module", module_id]
|
|
718
|
+
if preset:
|
|
719
|
+
cmd += ["--preset", preset]
|
|
720
|
+
for stack in stacks:
|
|
721
|
+
cmd += ["--template", stack]
|
|
722
|
+
if description.strip():
|
|
723
|
+
cmd += ["--summary", description.strip()]
|
|
724
|
+
if extra_skills:
|
|
725
|
+
cmd += ["--skills", ",".join(extra_skills)]
|
|
726
|
+
for module_id in disabled_modules or []:
|
|
727
|
+
cmd += ["--disable-module", module_id]
|
|
728
|
+
return cmd
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _run_cos_init(
|
|
732
|
+
name: str,
|
|
733
|
+
parent_dir: str,
|
|
734
|
+
stacks: list[str],
|
|
735
|
+
agents: list[str],
|
|
736
|
+
preset: str = "",
|
|
737
|
+
description: str = "",
|
|
738
|
+
extra_skills: list[str] | None = None,
|
|
739
|
+
disabled_modules: list[str] | None = None,
|
|
740
|
+
timeout: int = 300,
|
|
741
|
+
):
|
|
742
|
+
"""Run `cos init` in a subprocess → (ok, payload, error).
|
|
743
|
+
|
|
744
|
+
Default timeout has headroom over the in-init graph-build cap
|
|
745
|
+
(COS_INIT_GRAPH_TIMEOUT, default 180s) so a slow graph build degrades to an
|
|
746
|
+
empty graph inside init rather than the create subprocess being killed.
|
|
747
|
+
|
|
748
|
+
Module-level so a test can monkeypatch it without a real scaffold.
|
|
749
|
+
Description/extra-skills ride the CLI flags (--summary/--skills) so the
|
|
750
|
+
wizard and a hand-typed `cos init` produce byte-identical projects."""
|
|
751
|
+
cmd = _build_cos_init_cmd(
|
|
752
|
+
name,
|
|
753
|
+
parent_dir,
|
|
754
|
+
stacks,
|
|
755
|
+
agents,
|
|
756
|
+
preset=preset,
|
|
757
|
+
description=description,
|
|
758
|
+
extra_skills=extra_skills,
|
|
759
|
+
disabled_modules=disabled_modules,
|
|
760
|
+
)
|
|
761
|
+
try:
|
|
762
|
+
proc = subprocess.run( # noqa: S603 — fixed argv list, never shell=True
|
|
763
|
+
cmd, capture_output=True, text=True, timeout=timeout, cwd=parent_dir
|
|
764
|
+
)
|
|
765
|
+
except subprocess.TimeoutExpired:
|
|
766
|
+
return False, None, f"init timed out after {timeout}s"
|
|
767
|
+
except OSError as exc:
|
|
768
|
+
return False, None, f"could not launch cos init: {exc}"
|
|
769
|
+
if proc.returncode != 0:
|
|
770
|
+
return False, None, (proc.stderr or proc.stdout or "init failed").strip()[-400:]
|
|
771
|
+
return True, _parse_init_payload((proc.stdout or "").splitlines()), ""
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _parse_init_payload(stdout_lines: list[str]) -> dict:
|
|
775
|
+
# init pretty-prints the summary, and the job runner merges stderr progress
|
|
776
|
+
# into the same pipe, so the object can be preceded AND followed by noise.
|
|
777
|
+
# raw_decode stops at the end of the first complete value.
|
|
778
|
+
text = "\n".join(stdout_lines)
|
|
779
|
+
decoder = json.JSONDecoder()
|
|
780
|
+
start = text.find("{")
|
|
781
|
+
while start != -1:
|
|
782
|
+
try:
|
|
783
|
+
parsed, _ = decoder.raw_decode(text[start:])
|
|
784
|
+
except json.JSONDecodeError:
|
|
785
|
+
start = text.find("{", start + 1)
|
|
786
|
+
continue
|
|
787
|
+
if isinstance(parsed, dict):
|
|
788
|
+
return parsed
|
|
789
|
+
start = text.find("{", start + 1)
|
|
790
|
+
return {}
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
@router.post("/registry/init")
|
|
794
|
+
def hub_registry_init(
|
|
795
|
+
name: str = Body("", embed=True),
|
|
796
|
+
parent_dir: str = Body(..., embed=True),
|
|
797
|
+
stack: str = Body("", embed=True),
|
|
798
|
+
stacks: list[str] = Body(default_factory=list, embed=True),
|
|
799
|
+
preset: str = Body("", embed=True),
|
|
800
|
+
agent: str = Body("", embed=True),
|
|
801
|
+
agents: list[str] = Body(default_factory=list, embed=True),
|
|
802
|
+
description: str = Body("", embed=True),
|
|
803
|
+
extra_skills: list[str] = Body(default_factory=list, embed=True),
|
|
804
|
+
disabled_modules: list[str] = Body(default_factory=list, embed=True),
|
|
805
|
+
background: bool = Body(False, embed=True),
|
|
806
|
+
):
|
|
807
|
+
"""Scaffold a NEW project via `cos init` and register it (security-gated, TASK-249/358/362)."""
|
|
808
|
+
all_stacks = [s for s in ((stacks or []) + ([stack] if stack else [])) if s]
|
|
809
|
+
resolved_agents = _resolve_agents(agent, agents)
|
|
810
|
+
error, info = _validate_init_inputs(
|
|
811
|
+
name,
|
|
812
|
+
parent_dir,
|
|
813
|
+
all_stacks,
|
|
814
|
+
preset,
|
|
815
|
+
resolved_agents,
|
|
816
|
+
extra_skills=extra_skills,
|
|
817
|
+
disabled_modules=disabled_modules,
|
|
818
|
+
)
|
|
819
|
+
if error is not None:
|
|
820
|
+
return error
|
|
821
|
+
target: Path = info["target"]
|
|
822
|
+
if background:
|
|
823
|
+
# Job-based create (TASK-362): returns a job_id immediately; phases +
|
|
824
|
+
# log stream over GET /api/hub/init-jobs/{id}/events.
|
|
825
|
+
from web import init_jobs # type: ignore
|
|
826
|
+
|
|
827
|
+
cmd = _build_cos_init_cmd(
|
|
828
|
+
info["name"],
|
|
829
|
+
str(info["parent"]),
|
|
830
|
+
all_stacks,
|
|
831
|
+
resolved_agents,
|
|
832
|
+
preset=preset,
|
|
833
|
+
description=description or "",
|
|
834
|
+
extra_skills=extra_skills or [],
|
|
835
|
+
disabled_modules=info["disabled_modules"],
|
|
836
|
+
)
|
|
837
|
+
job = init_jobs.start_job(cmd, target, str(info["parent"]), _parse_init_payload)
|
|
838
|
+
return {
|
|
839
|
+
"data": {
|
|
840
|
+
"job_id": job.job_id,
|
|
841
|
+
"name": info["name"],
|
|
842
|
+
"auto_named": info["auto_named"],
|
|
843
|
+
"target": str(target),
|
|
844
|
+
},
|
|
845
|
+
"meta": {"layer": "hub", "source": "hub.registry_init_job"},
|
|
846
|
+
}
|
|
847
|
+
ok, payload, err = _run_cos_init(
|
|
848
|
+
info["name"],
|
|
849
|
+
str(info["parent"]),
|
|
850
|
+
all_stacks,
|
|
851
|
+
resolved_agents,
|
|
852
|
+
preset=preset,
|
|
853
|
+
description=description or "",
|
|
854
|
+
extra_skills=extra_skills or [],
|
|
855
|
+
disabled_modules=info["disabled_modules"],
|
|
856
|
+
)
|
|
857
|
+
if not ok:
|
|
858
|
+
# A failed init must leave nothing — remove the partial scaffold.
|
|
859
|
+
if target.exists():
|
|
860
|
+
shutil.rmtree(target, ignore_errors=True)
|
|
861
|
+
return _err("internal", f"init failed: {err}", status=500)
|
|
862
|
+
slug = (payload or {}).get("slug") or _resolve_slug_from_registry(target)
|
|
863
|
+
return {
|
|
864
|
+
"data": {
|
|
865
|
+
"slug": slug,
|
|
866
|
+
"path": str(target),
|
|
867
|
+
"stack": (all_stacks[0] if all_stacks else None),
|
|
868
|
+
"stacks": all_stacks,
|
|
869
|
+
"agents": resolved_agents,
|
|
870
|
+
"disabled_modules": info["disabled_modules"],
|
|
871
|
+
"preset": preset or None,
|
|
872
|
+
"auto_named": info["auto_named"],
|
|
873
|
+
},
|
|
874
|
+
"meta": {"layer": "hub", "source": "hub.registry_init"},
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
@router.get("/init-jobs/{job_id}")
|
|
879
|
+
def hub_init_job_snapshot(job_id: str):
|
|
880
|
+
"""Current phase + status + log tail for a tracked init job (TASK-362)."""
|
|
881
|
+
from web import init_jobs # type: ignore
|
|
882
|
+
|
|
883
|
+
job = init_jobs.get_job(job_id)
|
|
884
|
+
if job is None:
|
|
885
|
+
return _err("not_found", f"no init job {job_id!r}", status=404)
|
|
886
|
+
return {"data": job.snapshot(), "meta": {"layer": "hub", "source": "hub.init_job"}}
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
@router.post("/init-jobs/{job_id}/cancel")
|
|
890
|
+
def hub_init_job_cancel(job_id: str):
|
|
891
|
+
"""Cancel a running init job; the partial scaffold is cleaned up (TASK-362)."""
|
|
892
|
+
from web import init_jobs # type: ignore
|
|
893
|
+
|
|
894
|
+
job = init_jobs.cancel_job(job_id)
|
|
895
|
+
if job is None:
|
|
896
|
+
return _err("not_found", f"no init job {job_id!r}", status=404)
|
|
897
|
+
return {
|
|
898
|
+
"data": {"job_id": job.job_id, "status": job.snapshot()["status"]},
|
|
899
|
+
"meta": {"layer": "hub", "source": "hub.init_job_cancel"},
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
@router.get("/init-jobs/{job_id}/events")
|
|
904
|
+
async def hub_init_job_events(job_id: str):
|
|
905
|
+
"""SSE: buffered log replay then live phase/log events until terminal.
|
|
906
|
+
|
|
907
|
+
Reconnect-safe — a browser refresh re-attaches with the same job_id and
|
|
908
|
+
replays the buffered log before following (TASK-362)."""
|
|
909
|
+
import asyncio
|
|
910
|
+
|
|
911
|
+
from fastapi.responses import StreamingResponse
|
|
912
|
+
|
|
913
|
+
from web import init_jobs # type: ignore
|
|
914
|
+
|
|
915
|
+
job = init_jobs.get_job(job_id)
|
|
916
|
+
if job is None:
|
|
917
|
+
return _err("not_found", f"no init job {job_id!r}", status=404)
|
|
918
|
+
|
|
919
|
+
def _frame(event: str, payload: dict) -> bytes:
|
|
920
|
+
return f"event: {event}\ndata: {json.dumps(payload)}\n\n".encode("utf-8")
|
|
921
|
+
|
|
922
|
+
async def _gen():
|
|
923
|
+
offset = 0
|
|
924
|
+
last_phase = None
|
|
925
|
+
while True:
|
|
926
|
+
snap = job.snapshot(log_tail=0)
|
|
927
|
+
lines, offset = await asyncio.to_thread(job.log_slice, offset)
|
|
928
|
+
for line in lines:
|
|
929
|
+
yield _frame("log", {"line": line})
|
|
930
|
+
if snap["phase"] != last_phase:
|
|
931
|
+
last_phase = snap["phase"]
|
|
932
|
+
yield _frame("phase", {"phase": last_phase, "phases": snap["phases"]})
|
|
933
|
+
if snap["status"] != "running":
|
|
934
|
+
yield _frame(
|
|
935
|
+
snap["status"],
|
|
936
|
+
{
|
|
937
|
+
"status": snap["status"],
|
|
938
|
+
"error": snap["error"],
|
|
939
|
+
"result": snap["result"],
|
|
940
|
+
"cleanup": snap["cleanup"],
|
|
941
|
+
},
|
|
942
|
+
)
|
|
943
|
+
return
|
|
944
|
+
await asyncio.sleep(0.25)
|
|
945
|
+
|
|
946
|
+
return StreamingResponse(
|
|
947
|
+
_gen(),
|
|
948
|
+
media_type="text/event-stream",
|
|
949
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
@router.patch("/registry/{slug}")
|
|
954
|
+
def hub_registry_rename(slug: str, new_slug: str = Body(..., embed=True)):
|
|
955
|
+
"""Rename a project's slug (temp-slug → real name; path untouched)."""
|
|
956
|
+
if not _PROJECT_NAME_RE.match((new_slug or "").strip()):
|
|
957
|
+
return _err("validation", "new_slug must match ^[a-z0-9][a-z0-9._-]{0,63}$")
|
|
958
|
+
try:
|
|
959
|
+
from cli.registry import rename_project # type: ignore
|
|
960
|
+
|
|
961
|
+
renamed = rename_project(slug, new_slug.strip())
|
|
962
|
+
except Exception as exc:
|
|
963
|
+
return _err("conflict", str(exc), status=409)
|
|
964
|
+
if renamed is None:
|
|
965
|
+
return _err("not_found", f"no project with slug {slug!r}", status=404)
|
|
966
|
+
return {
|
|
967
|
+
"data": {"slug": renamed.slug, "path": renamed.path},
|
|
968
|
+
"meta": {"layer": "hub", "source": "hub.registry_rename"},
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
# ---------------------------------------------------------------------------
|
|
973
|
+
# DELETE /api/hub/registry/{slug}
|
|
974
|
+
# ---------------------------------------------------------------------------
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@router.delete("/registry/{slug}")
|
|
978
|
+
def hub_registry_remove(slug: str):
|
|
979
|
+
"""Unregister a project by slug. Does NOT touch the project on disk."""
|
|
980
|
+
if not slug or not slug.strip():
|
|
981
|
+
return _err("validation", "slug is required")
|
|
982
|
+
try:
|
|
983
|
+
from cli.registry import remove_project # type: ignore
|
|
984
|
+
except Exception as exc:
|
|
985
|
+
return _err("unavailable", f"cli.registry unavailable: {exc}", status=503)
|
|
986
|
+
|
|
987
|
+
try:
|
|
988
|
+
removed = remove_project(slug.strip())
|
|
989
|
+
except Exception as exc:
|
|
990
|
+
return _err("internal", str(exc), status=500)
|
|
991
|
+
|
|
992
|
+
if removed is None:
|
|
993
|
+
return _err("not_found", f"no project with slug {slug!r}", status=404)
|
|
994
|
+
|
|
995
|
+
return {
|
|
996
|
+
"data": {"slug": removed.slug, "path": removed.path},
|
|
997
|
+
"meta": {"layer": "hub", "source": "hub.registry_remove"},
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
# POST /api/hub/registry/scan
|
|
1003
|
+
# ---------------------------------------------------------------------------
|
|
1004
|
+
|
|
1005
|
+
# Hard caps so a malicious/misconfigured scan can't lock the process.
|
|
1006
|
+
_SCAN_MAX_DEPTH = 6
|
|
1007
|
+
_SCAN_MAX_VISITED_DIRS = 5000
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
@router.post("/registry/scan")
|
|
1011
|
+
def hub_registry_scan(
|
|
1012
|
+
root: str = Body(..., embed=True),
|
|
1013
|
+
max_depth: int = Body(_SCAN_MAX_DEPTH, embed=True),
|
|
1014
|
+
limit: int = Body(50, embed=True),
|
|
1015
|
+
):
|
|
1016
|
+
"""Walk a filesystem root and return every `.coding-os/` project found."""
|
|
1017
|
+
if not isinstance(root, str) or not root.strip():
|
|
1018
|
+
return _err("validation", "root is required")
|
|
1019
|
+
try:
|
|
1020
|
+
root_path = Path(root).expanduser().resolve()
|
|
1021
|
+
except (OSError, RuntimeError) as exc:
|
|
1022
|
+
return _err("validation", f"invalid root: {exc}")
|
|
1023
|
+
if not root_path.is_dir():
|
|
1024
|
+
return _err("not_found", f"root is not a directory: {root_path}", status=404)
|
|
1025
|
+
|
|
1026
|
+
max_depth = max(1, min(_SCAN_MAX_DEPTH, int(max_depth) if max_depth else _SCAN_MAX_DEPTH))
|
|
1027
|
+
limit = max(1, min(500, int(limit) if limit else 50))
|
|
1028
|
+
|
|
1029
|
+
_SKIP_DIR_NAMES = {
|
|
1030
|
+
".git",
|
|
1031
|
+
".hg",
|
|
1032
|
+
".svn",
|
|
1033
|
+
"node_modules",
|
|
1034
|
+
".venv",
|
|
1035
|
+
"venv",
|
|
1036
|
+
"__pycache__",
|
|
1037
|
+
".pytest_cache",
|
|
1038
|
+
".mypy_cache",
|
|
1039
|
+
".ruff_cache",
|
|
1040
|
+
".tox",
|
|
1041
|
+
".nox",
|
|
1042
|
+
".coding-os", # never recurse INTO a cos state dir
|
|
1043
|
+
"dist",
|
|
1044
|
+
"build",
|
|
1045
|
+
".next",
|
|
1046
|
+
".turbo",
|
|
1047
|
+
"Library",
|
|
1048
|
+
"Trash",
|
|
1049
|
+
".Trash",
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
# Snapshot registered paths for the "already_registered" annotation.
|
|
1053
|
+
registered_paths: set[str] = set()
|
|
1054
|
+
try:
|
|
1055
|
+
from cli.registry import load_registry # type: ignore
|
|
1056
|
+
|
|
1057
|
+
for p in load_registry().projects:
|
|
1058
|
+
try:
|
|
1059
|
+
registered_paths.add(str(Path(p.path).resolve()))
|
|
1060
|
+
except (OSError, RuntimeError):
|
|
1061
|
+
continue
|
|
1062
|
+
except Exception as exc:
|
|
1063
|
+
logger.debug("scan: could not snapshot registry: %s", exc)
|
|
1064
|
+
|
|
1065
|
+
hits: list[dict] = []
|
|
1066
|
+
visited = 0
|
|
1067
|
+
hit_limit_reached = False
|
|
1068
|
+
depth_limit_reached = False
|
|
1069
|
+
|
|
1070
|
+
# BFS so shallow hits surface first (a consumer usually cares about
|
|
1071
|
+
# "~/code/my-app" before "~/code/my-app/backend/vendor/old/...").
|
|
1072
|
+
from collections import deque
|
|
1073
|
+
|
|
1074
|
+
queue: deque[tuple[Path, int]] = deque([(root_path, 0)])
|
|
1075
|
+
while queue:
|
|
1076
|
+
if len(hits) >= limit:
|
|
1077
|
+
hit_limit_reached = True
|
|
1078
|
+
break
|
|
1079
|
+
if visited >= _SCAN_MAX_VISITED_DIRS:
|
|
1080
|
+
break
|
|
1081
|
+
current, depth = queue.popleft()
|
|
1082
|
+
visited += 1
|
|
1083
|
+
if not current.is_dir():
|
|
1084
|
+
continue
|
|
1085
|
+
if _looks_like_cos_project(current):
|
|
1086
|
+
resolved = str(current.resolve())
|
|
1087
|
+
hits.append(
|
|
1088
|
+
{
|
|
1089
|
+
"path": resolved,
|
|
1090
|
+
"slug": _resolve_slug_from_registry(current),
|
|
1091
|
+
"already_registered": resolved in registered_paths,
|
|
1092
|
+
}
|
|
1093
|
+
)
|
|
1094
|
+
# Don't recurse into a cos project; nested cos repos are
|
|
1095
|
+
# extremely rare and the skip keeps scans snappy.
|
|
1096
|
+
continue
|
|
1097
|
+
if depth >= max_depth:
|
|
1098
|
+
depth_limit_reached = True
|
|
1099
|
+
continue
|
|
1100
|
+
try:
|
|
1101
|
+
children = list(current.iterdir())
|
|
1102
|
+
except (OSError, PermissionError):
|
|
1103
|
+
continue
|
|
1104
|
+
for child in children:
|
|
1105
|
+
if not child.is_dir():
|
|
1106
|
+
continue
|
|
1107
|
+
if child.name in _SKIP_DIR_NAMES:
|
|
1108
|
+
continue
|
|
1109
|
+
if child.name.startswith("."):
|
|
1110
|
+
# Don't descend into dotfiles directories (browser caches,
|
|
1111
|
+
# editor state); .coding-os is explicitly skipped above.
|
|
1112
|
+
continue
|
|
1113
|
+
queue.append((child, depth + 1))
|
|
1114
|
+
|
|
1115
|
+
return {
|
|
1116
|
+
"data": {
|
|
1117
|
+
"root": str(root_path),
|
|
1118
|
+
"hits": hits,
|
|
1119
|
+
"count": len(hits),
|
|
1120
|
+
"visited_dirs": visited,
|
|
1121
|
+
"hit_limit_reached": hit_limit_reached,
|
|
1122
|
+
"depth_limit_reached": depth_limit_reached,
|
|
1123
|
+
},
|
|
1124
|
+
"meta": {
|
|
1125
|
+
"layer": "hub",
|
|
1126
|
+
"source": "hub.registry_scan",
|
|
1127
|
+
"max_depth": max_depth,
|
|
1128
|
+
"limit": limit,
|
|
1129
|
+
},
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
# ---------------------------------------------------------------------------
|
|
1134
|
+
# POST /api/hub/registry/gc
|
|
1135
|
+
# ---------------------------------------------------------------------------
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
@router.post("/registry/gc")
|
|
1139
|
+
def hub_registry_gc(
|
|
1140
|
+
dry_run: bool = Body(False, embed=True),
|
|
1141
|
+
):
|
|
1142
|
+
"""Remove registry entries whose directory no longer exists."""
|
|
1143
|
+
try:
|
|
1144
|
+
from cli.registry import Registry, load_registry, save_registry # type: ignore
|
|
1145
|
+
except Exception as exc:
|
|
1146
|
+
return _err("unavailable", f"cli.registry unavailable: {exc}", status=503)
|
|
1147
|
+
|
|
1148
|
+
try:
|
|
1149
|
+
reg = load_registry()
|
|
1150
|
+
except Exception as exc:
|
|
1151
|
+
return _err("internal", f"load_registry failed: {exc}", status=500)
|
|
1152
|
+
|
|
1153
|
+
kept: list[dict] = []
|
|
1154
|
+
removed: list[dict] = []
|
|
1155
|
+
for entry in reg.projects:
|
|
1156
|
+
path = Path(entry.path)
|
|
1157
|
+
alive = _looks_like_cos_project(path)
|
|
1158
|
+
item = {"slug": entry.slug, "path": entry.path, "created_at": entry.created_at}
|
|
1159
|
+
(kept if alive else removed).append(item)
|
|
1160
|
+
|
|
1161
|
+
if not dry_run and removed:
|
|
1162
|
+
reg.projects = [p for p in reg.projects if _looks_like_cos_project(Path(p.path))]
|
|
1163
|
+
try:
|
|
1164
|
+
save_registry(reg)
|
|
1165
|
+
except Exception as exc:
|
|
1166
|
+
return _err("internal", f"save_registry failed: {exc}", status=500)
|
|
1167
|
+
|
|
1168
|
+
return {
|
|
1169
|
+
"data": {
|
|
1170
|
+
"kept": kept,
|
|
1171
|
+
"removed": removed,
|
|
1172
|
+
"dry_run": bool(dry_run),
|
|
1173
|
+
"kept_count": len(kept),
|
|
1174
|
+
"removed_count": len(removed),
|
|
1175
|
+
},
|
|
1176
|
+
"meta": {"layer": "hub", "source": "hub.registry_gc"},
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
# ---------------------------------------------------------------------------
|
|
1181
|
+
# GET /api/hub/suggest-roots — surface likely scan roots for the UI
|
|
1182
|
+
# ---------------------------------------------------------------------------
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
@router.get("/suggest-roots")
|
|
1186
|
+
def hub_suggest_roots(depth: int = Query(0)):
|
|
1187
|
+
"""Return sensible default scan roots for the UI's import wizard."""
|
|
1188
|
+
_ = depth # reserved — currently unused; keeps the route parameter list stable
|
|
1189
|
+
candidates: list[Path] = [
|
|
1190
|
+
Path(os.environ.get("COS_PROJECT_ROOT") or os.getcwd()).resolve(),
|
|
1191
|
+
Path.home() / "code",
|
|
1192
|
+
Path.home() / "Projects",
|
|
1193
|
+
Path.home() / "Developer",
|
|
1194
|
+
Path.home(),
|
|
1195
|
+
]
|
|
1196
|
+
seen: set[str] = set()
|
|
1197
|
+
suggestions: list[str] = []
|
|
1198
|
+
for c in candidates:
|
|
1199
|
+
try:
|
|
1200
|
+
resolved = str(c.resolve())
|
|
1201
|
+
except (OSError, RuntimeError):
|
|
1202
|
+
continue
|
|
1203
|
+
if resolved in seen:
|
|
1204
|
+
continue
|
|
1205
|
+
if not c.is_dir():
|
|
1206
|
+
continue
|
|
1207
|
+
seen.add(resolved)
|
|
1208
|
+
suggestions.append(resolved)
|
|
1209
|
+
# The Composer scaffolds INTO the picked root, so it needs the subset that
|
|
1210
|
+
# `_validate_init_inputs` would accept; import/scan happily target the rest.
|
|
1211
|
+
scaffoldable = [
|
|
1212
|
+
s
|
|
1213
|
+
for s in suggestions
|
|
1214
|
+
if not _is_meta_repo(Path(s)) and _ancestor_with_coding_os(Path(s)) is None
|
|
1215
|
+
]
|
|
1216
|
+
return {
|
|
1217
|
+
"data": {"suggestions": suggestions, "scaffoldable": scaffoldable},
|
|
1218
|
+
"meta": {"layer": "hub", "source": "hub.suggest_roots"},
|
|
1219
|
+
}
|