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
cli/graph_commands.py
ADDED
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
"""graph_os CLI subcommands.
|
|
2
|
+
|
|
3
|
+
Registers the `cos graph-*` family on the root `cli` group:
|
|
4
|
+
|
|
5
|
+
cos graph-reindex [--path DIR] [--no-docs]
|
|
6
|
+
cos graph-query "<phrase>" [--limit N] [--kinds ...]
|
|
7
|
+
cos graph-context <uid>
|
|
8
|
+
cos graph-impact <uid> [--downstream|--upstream]
|
|
9
|
+
cos graph-references <uid>
|
|
10
|
+
cos graph-path <src> <dst>
|
|
11
|
+
cos graph-contracts [--kind http,mcp,...]
|
|
12
|
+
cos graph-rename-plan <uid> <new-name>
|
|
13
|
+
cos graph-export [--format json|mermaid|dot] [--out FILE]
|
|
14
|
+
cos graph-viz [--path DIR] [--out FILE] [--serve] [--port N]
|
|
15
|
+
cos graph-stats
|
|
16
|
+
cos graph-index-local <path>
|
|
17
|
+
cos graph-index-github <url> [--auth TOKEN]
|
|
18
|
+
cos graph-index-zip <archive>
|
|
19
|
+
cos graph-group create|add|remove|list|status|sync|query|contracts|viz ...
|
|
20
|
+
|
|
21
|
+
Every subcommand prints JSON by default so scripts and agents can parse
|
|
22
|
+
consistently. `--pretty` or the absence of `--json` picks a readable
|
|
23
|
+
form.
|
|
24
|
+
|
|
25
|
+
All commands go through the same envelope shape as the MCP tools
|
|
26
|
+
(Rule 14) so agents running `cos` in a shell get the same signal they
|
|
27
|
+
would via MCP.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import tempfile
|
|
36
|
+
from collections.abc import Iterable
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
import click
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Lazy bootstrap — push core/ + src/core/thinking_os onto sys.path.
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _bootstrap_paths() -> None:
|
|
48
|
+
from cli._resources import core_dir as _core_dir
|
|
49
|
+
|
|
50
|
+
core_path = _core_dir()
|
|
51
|
+
tos_dir = core_path / "thinking_os"
|
|
52
|
+
for candidate in (core_path, tos_dir):
|
|
53
|
+
if candidate.exists() and str(candidate) not in sys.path:
|
|
54
|
+
sys.path.insert(0, str(candidate))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _json_echo(payload: Any, *, pretty: bool = False) -> None:
|
|
58
|
+
if isinstance(payload, str):
|
|
59
|
+
# Already a JSON envelope from a `cos_graph_*` tool.
|
|
60
|
+
try:
|
|
61
|
+
payload = json.loads(payload)
|
|
62
|
+
except json.JSONDecodeError:
|
|
63
|
+
click.echo(payload)
|
|
64
|
+
return
|
|
65
|
+
if pretty:
|
|
66
|
+
click.echo(json.dumps(payload, indent=2, default=str))
|
|
67
|
+
else:
|
|
68
|
+
click.echo(json.dumps(payload, default=str))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _open_backend():
|
|
72
|
+
_bootstrap_paths()
|
|
73
|
+
from database import init_db # type: ignore
|
|
74
|
+
|
|
75
|
+
from graph_os.backends.sqlite_backend import SqliteBackend # type: ignore
|
|
76
|
+
from graph_os.tools import graph as graph_tools # type: ignore
|
|
77
|
+
|
|
78
|
+
conn = init_db()
|
|
79
|
+
backend = SqliteBackend(conn=conn)
|
|
80
|
+
graph_tools._BACKEND_SINGLETON = backend
|
|
81
|
+
return backend, graph_tools
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Parallel reindex worker — must be module-level so ProcessPoolExecutor
|
|
86
|
+
# can pickle it. Each worker re-imports graph_os.tools.reindex_dispatch
|
|
87
|
+
# inside its own process; init_db() inside dispatch() opens a fresh
|
|
88
|
+
# SQLite WAL connection. The dispatcher's busy-retry loop handles
|
|
89
|
+
# concurrent writers without the CLI having to coordinate.
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _report_failure_reason(report: dict) -> str | None:
|
|
94
|
+
graph_layer = (report.get("layers") or {}).get("graph") or {}
|
|
95
|
+
if report.get("status") == "error" or graph_layer.get("status") == "error":
|
|
96
|
+
return str(graph_layer.get("reason") or report.get("reason") or "unknown")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_lock_shaped(reason: str) -> bool:
|
|
101
|
+
lowered = reason.lower()
|
|
102
|
+
return "locked" in lowered or "busy" in lowered
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _parallel_dispatch(
|
|
106
|
+
file_path: str,
|
|
107
|
+
project_root: str,
|
|
108
|
+
include_docs: bool,
|
|
109
|
+
force: bool,
|
|
110
|
+
) -> dict:
|
|
111
|
+
_bootstrap_paths()
|
|
112
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
113
|
+
|
|
114
|
+
# defer per-file stub linking — graph_reindex runs ONE global
|
|
115
|
+
# link after the whole walk, so mid-walk resolutions aren't orphaned by a
|
|
116
|
+
# later file's prune-before-reindex.
|
|
117
|
+
return dispatch(
|
|
118
|
+
file_path,
|
|
119
|
+
project_root=project_root,
|
|
120
|
+
include_docs=include_docs,
|
|
121
|
+
force=force,
|
|
122
|
+
link_stubs=False,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Query / context / impact / etc.
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def register(cli: click.Group) -> None:
|
|
132
|
+
"""Attach every `cos graph-*` subcommand onto the parent `cli` group."""
|
|
133
|
+
|
|
134
|
+
@cli.command(name="graph-query")
|
|
135
|
+
@click.argument("query", nargs=-1)
|
|
136
|
+
@click.option("--limit", default=10, show_default=True, type=int)
|
|
137
|
+
@click.option("--kinds", default="", help="Comma-separated node kinds.")
|
|
138
|
+
@click.option("--max-hops", default=2, type=int)
|
|
139
|
+
@click.option("--confidence-min", default=0.3, type=float)
|
|
140
|
+
@click.option("--pretty", is_flag=True)
|
|
141
|
+
def graph_query(query, limit, kinds, max_hops, confidence_min, pretty):
|
|
142
|
+
"""Hybrid search over node labels + docstrings."""
|
|
143
|
+
q = " ".join(query).strip()
|
|
144
|
+
if not q:
|
|
145
|
+
raise click.BadParameter("query argument required")
|
|
146
|
+
_, tools = _open_backend()
|
|
147
|
+
result = tools.cos_graph_query(
|
|
148
|
+
q,
|
|
149
|
+
kinds=[k.strip() for k in kinds.split(",") if k.strip()] or None,
|
|
150
|
+
limit=limit,
|
|
151
|
+
max_hops=max_hops,
|
|
152
|
+
confidence_min=confidence_min,
|
|
153
|
+
)
|
|
154
|
+
_json_echo(result, pretty=pretty)
|
|
155
|
+
|
|
156
|
+
@cli.command(name="graph-search")
|
|
157
|
+
@click.argument("query", nargs=-1)
|
|
158
|
+
@click.option("--top-k", default=10, show_default=True, type=int)
|
|
159
|
+
@click.option("--pretty", is_flag=True)
|
|
160
|
+
def graph_search(query, top_k, pretty):
|
|
161
|
+
"""Hybrid semantic + lexical + centrality search over indexed code by free text."""
|
|
162
|
+
q = " ".join(query).strip()
|
|
163
|
+
if not q:
|
|
164
|
+
raise click.BadParameter("query argument required")
|
|
165
|
+
_, tools = _open_backend()
|
|
166
|
+
_json_echo(tools.cos_graph_search(q, top_k=top_k), pretty=pretty)
|
|
167
|
+
|
|
168
|
+
@cli.command(name="graph-context")
|
|
169
|
+
@click.argument("uid")
|
|
170
|
+
@click.option("--direction", default="both", type=click.Choice(["in", "out", "both"]))
|
|
171
|
+
@click.option("--depth", default=1, type=int)
|
|
172
|
+
@click.option("--include-content", is_flag=True)
|
|
173
|
+
@click.option("--include-evidence", is_flag=True)
|
|
174
|
+
@click.option("--pretty", is_flag=True)
|
|
175
|
+
def graph_context(uid, direction, depth, include_content, include_evidence, pretty):
|
|
176
|
+
"""Neighbourhood around a symbol (callers + callees + refs)."""
|
|
177
|
+
_, tools = _open_backend()
|
|
178
|
+
_json_echo(
|
|
179
|
+
tools.cos_graph_context(
|
|
180
|
+
uid,
|
|
181
|
+
direction=direction,
|
|
182
|
+
depth=depth,
|
|
183
|
+
include_content=include_content,
|
|
184
|
+
include_evidence=include_evidence,
|
|
185
|
+
),
|
|
186
|
+
pretty=pretty,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
@cli.command(name="graph-impact")
|
|
190
|
+
@click.argument("uid")
|
|
191
|
+
@click.option(
|
|
192
|
+
"--direction", default="downstream", type=click.Choice(["downstream", "upstream", "both"])
|
|
193
|
+
)
|
|
194
|
+
@click.option("--depth", default=3, type=int)
|
|
195
|
+
@click.option("--confidence-min", default=0.5, type=float)
|
|
196
|
+
@click.option("--pretty", is_flag=True)
|
|
197
|
+
def graph_impact(uid, direction, depth, confidence_min, pretty):
|
|
198
|
+
"""Blast-radius of a change — risk-tiered."""
|
|
199
|
+
_, tools = _open_backend()
|
|
200
|
+
_json_echo(
|
|
201
|
+
tools.cos_graph_impact(
|
|
202
|
+
uid,
|
|
203
|
+
direction=direction,
|
|
204
|
+
depth=depth,
|
|
205
|
+
confidence_min=confidence_min,
|
|
206
|
+
),
|
|
207
|
+
pretty=pretty,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@cli.command(name="graph-references")
|
|
211
|
+
@click.argument("uid")
|
|
212
|
+
@click.option("--kinds", default="")
|
|
213
|
+
@click.option("--limit", default=100, type=int)
|
|
214
|
+
@click.option("--pretty", is_flag=True)
|
|
215
|
+
def graph_references(uid, kinds, limit, pretty):
|
|
216
|
+
"""Inbound edges — who references this."""
|
|
217
|
+
_, tools = _open_backend()
|
|
218
|
+
# Empty --kinds → None so the tool auto-picks the right default edge
|
|
219
|
+
# kinds PER node kind (class→constructs/inherits, fn→calls/imports).
|
|
220
|
+
# A hardcoded CSV default returned 0 for class nodes (MCP parity bug).
|
|
221
|
+
kset = tuple(k.strip() for k in kinds.split(",") if k.strip())
|
|
222
|
+
_json_echo(
|
|
223
|
+
tools.cos_graph_references(
|
|
224
|
+
uid,
|
|
225
|
+
kinds=kset or None,
|
|
226
|
+
limit=limit,
|
|
227
|
+
),
|
|
228
|
+
pretty=pretty,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
@cli.command(name="graph-path")
|
|
232
|
+
@click.argument("source")
|
|
233
|
+
@click.argument("target")
|
|
234
|
+
@click.option("--max-hops", default=5, type=int)
|
|
235
|
+
@click.option("--pretty", is_flag=True)
|
|
236
|
+
def graph_path(source, target, max_hops, pretty):
|
|
237
|
+
"""Shortest path between two nodes."""
|
|
238
|
+
_, tools = _open_backend()
|
|
239
|
+
_json_echo(
|
|
240
|
+
tools.cos_graph_path(source, target, max_hops=max_hops),
|
|
241
|
+
pretty=pretty,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@cli.command(name="graph-trace")
|
|
245
|
+
@click.argument("entry_uid")
|
|
246
|
+
@click.option("--max-steps", default=50, type=int)
|
|
247
|
+
@click.option("--pretty", is_flag=True)
|
|
248
|
+
def graph_trace(entry_uid, max_steps, pretty):
|
|
249
|
+
"""Forward execution walk from an entry point."""
|
|
250
|
+
_, tools = _open_backend()
|
|
251
|
+
_json_echo(
|
|
252
|
+
tools.cos_graph_trace(entry_uid, max_steps=max_steps),
|
|
253
|
+
pretty=pretty,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
@cli.command(name="graph-similar")
|
|
257
|
+
@click.argument("uid")
|
|
258
|
+
@click.option("--top-k", default=5, type=int)
|
|
259
|
+
@click.option("--confidence-min", default=0.5, type=float)
|
|
260
|
+
@click.option("--pretty", is_flag=True)
|
|
261
|
+
def graph_similar(uid, top_k, confidence_min, pretty):
|
|
262
|
+
"""Semantic-similar nodes (difflib baseline; BGE-M3 later)."""
|
|
263
|
+
_, tools = _open_backend()
|
|
264
|
+
_json_echo(
|
|
265
|
+
tools.cos_graph_similar(uid, top_k=top_k, confidence_min=confidence_min),
|
|
266
|
+
pretty=pretty,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@cli.command(name="graph-contracts")
|
|
270
|
+
@click.option("--kind", default="http,mcp,grpc,event,websocket")
|
|
271
|
+
@click.option("--scope", default="all")
|
|
272
|
+
@click.option("--pretty", is_flag=True)
|
|
273
|
+
def graph_contracts(kind, scope, pretty):
|
|
274
|
+
"""API surface — routes, MCP tools, gRPC, events, WS."""
|
|
275
|
+
_, tools = _open_backend()
|
|
276
|
+
_json_echo(
|
|
277
|
+
tools.cos_graph_contracts(
|
|
278
|
+
scope=scope,
|
|
279
|
+
kinds=tuple(k.strip() for k in kind.split(",") if k.strip()),
|
|
280
|
+
),
|
|
281
|
+
pretty=pretty,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
@cli.command(name="graph-rename-plan")
|
|
285
|
+
@click.argument("uid")
|
|
286
|
+
@click.argument("new_name")
|
|
287
|
+
@click.option("--no-strings", is_flag=True)
|
|
288
|
+
@click.option("--pretty", is_flag=True)
|
|
289
|
+
def graph_rename_plan(uid, new_name, no_strings, pretty):
|
|
290
|
+
"""Produce a rename plan — call-sites, docs, tests, strings."""
|
|
291
|
+
_, tools = _open_backend()
|
|
292
|
+
_json_echo(
|
|
293
|
+
tools.cos_graph_rename_plan(uid, new_name, check_strings=not no_strings),
|
|
294
|
+
pretty=pretty,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@cli.command(name="graph-export")
|
|
298
|
+
@click.option("--format", "fmt", default="json", type=click.Choice(["json", "mermaid", "dot"]))
|
|
299
|
+
@click.option("--root-uid", default=None)
|
|
300
|
+
@click.option("--edge-types", default="")
|
|
301
|
+
@click.option("--max-nodes", default=500, type=int)
|
|
302
|
+
@click.option("--out", default=None, help="Write to file instead of stdout.")
|
|
303
|
+
def graph_export(fmt, root_uid, edge_types, max_nodes, out):
|
|
304
|
+
"""Export a subgraph as json / mermaid / dot."""
|
|
305
|
+
_, tools = _open_backend()
|
|
306
|
+
envelope = tools.cos_graph_export(
|
|
307
|
+
format=fmt,
|
|
308
|
+
root_uid=root_uid,
|
|
309
|
+
edge_types=[e.strip() for e in edge_types.split(",") if e.strip()] or None,
|
|
310
|
+
max_nodes=max_nodes,
|
|
311
|
+
)
|
|
312
|
+
parsed = json.loads(envelope) if isinstance(envelope, str) else envelope
|
|
313
|
+
if not parsed.get("ok"):
|
|
314
|
+
raise click.ClickException(json.dumps(parsed))
|
|
315
|
+
data = parsed["data"]
|
|
316
|
+
if fmt == "json":
|
|
317
|
+
payload = json.dumps(
|
|
318
|
+
{"nodes": data["nodes"], "edges": data["edges"]},
|
|
319
|
+
indent=2,
|
|
320
|
+
default=str,
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
payload = data["diagram"]
|
|
324
|
+
if out:
|
|
325
|
+
Path(out).write_text(payload, encoding="utf-8")
|
|
326
|
+
click.echo(f"[graph-export] wrote {out}")
|
|
327
|
+
else:
|
|
328
|
+
click.echo(payload)
|
|
329
|
+
|
|
330
|
+
@cli.command(name="graph-stats")
|
|
331
|
+
@click.option("--pretty", is_flag=True)
|
|
332
|
+
def graph_stats(pretty):
|
|
333
|
+
"""Quick snapshot: node count, edge count, schema version."""
|
|
334
|
+
backend, _ = _open_backend()
|
|
335
|
+
_bootstrap_paths()
|
|
336
|
+
from database import get_db_stats # type: ignore
|
|
337
|
+
|
|
338
|
+
stats = get_db_stats(backend._conn)
|
|
339
|
+
report = {
|
|
340
|
+
"schema_version": stats["schema_version"],
|
|
341
|
+
"node_count": backend.count_nodes(),
|
|
342
|
+
"edge_count": backend.count_edges(),
|
|
343
|
+
"backend": backend.backend_id,
|
|
344
|
+
"db_size_bytes": stats.get("db_size_bytes"),
|
|
345
|
+
}
|
|
346
|
+
_json_echo(report, pretty=pretty)
|
|
347
|
+
|
|
348
|
+
@cli.command(name="graph-cycles")
|
|
349
|
+
@click.option("--scope", default="imports", type=click.Choice(["imports", "calls"]))
|
|
350
|
+
@click.option("--top", default=20, type=int)
|
|
351
|
+
@click.option("--min-size", default=2, type=int)
|
|
352
|
+
@click.option("--pretty", is_flag=True)
|
|
353
|
+
def graph_cycles(scope, top, min_size, pretty):
|
|
354
|
+
"""Circular dependencies — strongly-connected components."""
|
|
355
|
+
_, tools = _open_backend()
|
|
356
|
+
_json_echo(
|
|
357
|
+
tools.cos_graph_cycles(scope=scope, top=top, min_size=min_size),
|
|
358
|
+
pretty=pretty,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
@cli.command(name="graph-test-gap")
|
|
362
|
+
@click.option("--kind", default="")
|
|
363
|
+
@click.option("--top", default=50, type=int)
|
|
364
|
+
@click.option("--pretty", is_flag=True)
|
|
365
|
+
def graph_test_gap(kind, top, pretty):
|
|
366
|
+
"""Untested symbols — prod code with zero inbound test edge."""
|
|
367
|
+
_, tools = _open_backend()
|
|
368
|
+
_json_echo(tools.cos_graph_test_gap(kind=kind, top=top), pretty=pretty)
|
|
369
|
+
|
|
370
|
+
@cli.command(name="graph-dead-code")
|
|
371
|
+
@click.option("--kind", default="")
|
|
372
|
+
@click.option("--top", default=50, type=int)
|
|
373
|
+
@click.option("--include-tests", is_flag=True)
|
|
374
|
+
@click.option("--pretty", is_flag=True)
|
|
375
|
+
def graph_dead_code(kind, top, include_tests, pretty):
|
|
376
|
+
"""Dead-code candidates — in-repo symbols with no non-test caller."""
|
|
377
|
+
_, tools = _open_backend()
|
|
378
|
+
_json_echo(
|
|
379
|
+
tools.cos_graph_dead_code(kind=kind, top=top, include_tests=include_tests),
|
|
380
|
+
pretty=pretty,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
@cli.command(name="graph-diff")
|
|
384
|
+
@click.option("--base", default="HEAD~1")
|
|
385
|
+
@click.option("--head", default="HEAD")
|
|
386
|
+
@click.option("--pretty", is_flag=True)
|
|
387
|
+
def graph_diff(base, head, pretty):
|
|
388
|
+
"""Blast-radius of a git range — base..head changed symbols + downstream."""
|
|
389
|
+
_, tools = _open_backend()
|
|
390
|
+
_json_echo(tools.cos_graph_diff(base=base, head=head), pretty=pretty)
|
|
391
|
+
|
|
392
|
+
@cli.command(name="graph-resolve")
|
|
393
|
+
@click.argument("q")
|
|
394
|
+
@click.option("--kinds", default="")
|
|
395
|
+
@click.option("--top", default=10, type=int)
|
|
396
|
+
@click.option("--pretty", is_flag=True)
|
|
397
|
+
def graph_resolve(q, kinds, top, pretty):
|
|
398
|
+
"""Resolve a label / path / partial to canonical uids."""
|
|
399
|
+
_, tools = _open_backend()
|
|
400
|
+
kset = tuple(k.strip() for k in kinds.split(",") if k.strip())
|
|
401
|
+
_json_echo(
|
|
402
|
+
tools.cos_graph_resolve(q, kinds=kset or None, top=top),
|
|
403
|
+
pretty=pretty,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
@cli.command(name="graph-centrality")
|
|
407
|
+
@click.option("--metric", default="degree", type=click.Choice(["degree", "betweenness"]))
|
|
408
|
+
@click.option("--top", default=20, type=int)
|
|
409
|
+
@click.option("--kind", default="")
|
|
410
|
+
@click.option("--pretty", is_flag=True)
|
|
411
|
+
def graph_centrality(metric, top, kind, pretty):
|
|
412
|
+
"""Hub / chokepoint nodes — degree or betweenness centrality."""
|
|
413
|
+
_, tools = _open_backend()
|
|
414
|
+
_json_echo(
|
|
415
|
+
tools.cos_graph_centrality(metric=metric, top=top, kind=kind or None),
|
|
416
|
+
pretty=pretty,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
@cli.command(name="graph-ranking")
|
|
420
|
+
@click.option("--query", default="")
|
|
421
|
+
@click.option("--top", default=20, type=int)
|
|
422
|
+
@click.option("--kind", default="")
|
|
423
|
+
@click.option("--pretty", is_flag=True)
|
|
424
|
+
def graph_ranking(query, top, kind, pretty):
|
|
425
|
+
"""PageRank importance — optionally personalised by query."""
|
|
426
|
+
_, tools = _open_backend()
|
|
427
|
+
_json_echo(
|
|
428
|
+
tools.cos_graph_ranking(query=query or None, top=top, kind=kind or None),
|
|
429
|
+
pretty=pretty,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
@cli.command(name="graph-doctor")
|
|
433
|
+
@click.option("--fix", is_flag=True, help="Attempt safe repairs (delete dangling/stale).")
|
|
434
|
+
@click.option("--pretty", is_flag=True)
|
|
435
|
+
def graph_doctor(fix, pretty):
|
|
436
|
+
"""Graph health — orphans, dangling edges, duplicates, backend status."""
|
|
437
|
+
_, tools = _open_backend()
|
|
438
|
+
_json_echo(tools.cos_graph_doctor(fix=fix), pretty=pretty)
|
|
439
|
+
|
|
440
|
+
@cli.command(name="graph-reindex")
|
|
441
|
+
@click.option("--path", default=None, help="Directory to reindex (default: repo root).")
|
|
442
|
+
@click.option("--no-docs", is_flag=True, help="Skip the docs RAG layer.")
|
|
443
|
+
@click.option(
|
|
444
|
+
"--max-files",
|
|
445
|
+
default=1_000_000,
|
|
446
|
+
type=int,
|
|
447
|
+
help="Cap on files walked (default 1M for monorepo-scale).",
|
|
448
|
+
)
|
|
449
|
+
@click.option(
|
|
450
|
+
"--workers",
|
|
451
|
+
"-j",
|
|
452
|
+
default=1,
|
|
453
|
+
type=int,
|
|
454
|
+
help="Parallel worker processes (default 1 = sequential). For "
|
|
455
|
+
"large monorepos, set to CPU count (e.g. -j 8). SQLite WAL + "
|
|
456
|
+
"the dispatcher's lock-retry loop handle concurrent writes.",
|
|
457
|
+
)
|
|
458
|
+
@click.option(
|
|
459
|
+
"--force",
|
|
460
|
+
"-f",
|
|
461
|
+
is_flag=True,
|
|
462
|
+
help="V1: bypass the file_index_state cache; reindex even "
|
|
463
|
+
"files whose content_hash matches the last successful run.",
|
|
464
|
+
)
|
|
465
|
+
@click.option(
|
|
466
|
+
"--status",
|
|
467
|
+
is_flag=True,
|
|
468
|
+
help="V1: print the top 50 most-recently-indexed files from "
|
|
469
|
+
"file_index_state and exit (debugging aid).",
|
|
470
|
+
)
|
|
471
|
+
@click.option(
|
|
472
|
+
"--rebuild-kinds",
|
|
473
|
+
is_flag=True,
|
|
474
|
+
help="S3: re-run the v16 kind-normalization data migration "
|
|
475
|
+
"without a full reindex. Useful after the NodeKind enum "
|
|
476
|
+
"ships to canonicalise legacy colon-prefixed kinds in place.",
|
|
477
|
+
)
|
|
478
|
+
@click.option(
|
|
479
|
+
"--extractor",
|
|
480
|
+
type=click.Choice(["auto", "legacy", "tree-sitter"], case_sensitive=False),
|
|
481
|
+
default="auto",
|
|
482
|
+
show_default=True,
|
|
483
|
+
help=(
|
|
484
|
+
"TASK-122 A/B flag: which parser ladder extractors should "
|
|
485
|
+
"use. 'legacy' forces ast/regex baselines; 'tree-sitter' "
|
|
486
|
+
"prefers tree-sitter when grammars are installed (lands "
|
|
487
|
+
"in TASK-119/120/121); 'auto' picks the current default. "
|
|
488
|
+
"Sets COS_EXTRACTOR_PREFERENCE for downstream extractors."
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
@click.option(
|
|
492
|
+
"--prune-stale",
|
|
493
|
+
is_flag=True,
|
|
494
|
+
help=(
|
|
495
|
+
"W7.5 / R4-N7: delete nodes whose file_path no longer exists "
|
|
496
|
+
"on disk BEFORE reindexing. Reindex is idempotent-upsert so "
|
|
497
|
+
"moved/renamed/deleted files leave ghost nodes that bloat "
|
|
498
|
+
"the graph; this flag invokes cos_graph_doctor(fix=True) "
|
|
499
|
+
"to prune them first."
|
|
500
|
+
),
|
|
501
|
+
)
|
|
502
|
+
def graph_reindex(
|
|
503
|
+
path,
|
|
504
|
+
no_docs,
|
|
505
|
+
max_files,
|
|
506
|
+
workers,
|
|
507
|
+
force,
|
|
508
|
+
status,
|
|
509
|
+
rebuild_kinds,
|
|
510
|
+
extractor,
|
|
511
|
+
prune_stale,
|
|
512
|
+
):
|
|
513
|
+
"""Walk a directory and rebuild the graph via the dispatcher."""
|
|
514
|
+
_bootstrap_paths()
|
|
515
|
+
# publish the chosen ladder via env so every spawned
|
|
516
|
+
# subprocess (incremental indexer, doc indexer, etc.) sees it.
|
|
517
|
+
# Existing call sites that bypass the CLI (e.g. PostToolUse
|
|
518
|
+
# auto-reindex) keep their default behaviour because the env
|
|
519
|
+
# var is unset when the CLI flag isn't passed.
|
|
520
|
+
os.environ["COS_EXTRACTOR_PREFERENCE"] = (extractor or "auto").lower()
|
|
521
|
+
if status:
|
|
522
|
+
_graph_reindex_print_status()
|
|
523
|
+
return
|
|
524
|
+
if rebuild_kinds:
|
|
525
|
+
# S3 data migration — idempotent; can be invoked standalone.
|
|
526
|
+
try:
|
|
527
|
+
import database # type: ignore
|
|
528
|
+
|
|
529
|
+
from graph_os.types import normalize_kind # type: ignore
|
|
530
|
+
except ImportError as exc:
|
|
531
|
+
click.echo(f"[graph-reindex] rebuild-kinds import failed: {exc}", err=True)
|
|
532
|
+
return
|
|
533
|
+
conn = database.get_connection()
|
|
534
|
+
try:
|
|
535
|
+
rows = conn.execute("SELECT DISTINCT kind FROM graph_nodes").fetchall()
|
|
536
|
+
renames: dict[str, str] = {}
|
|
537
|
+
for r in rows:
|
|
538
|
+
legacy = r[0]
|
|
539
|
+
if legacy is None:
|
|
540
|
+
continue
|
|
541
|
+
try:
|
|
542
|
+
canonical = normalize_kind(legacy).value
|
|
543
|
+
except ValueError:
|
|
544
|
+
continue
|
|
545
|
+
if canonical != legacy:
|
|
546
|
+
renames[legacy] = canonical
|
|
547
|
+
total = 0
|
|
548
|
+
for legacy, canonical in renames.items():
|
|
549
|
+
cur = conn.execute(
|
|
550
|
+
"UPDATE graph_nodes SET kind = ? WHERE kind = ?",
|
|
551
|
+
(canonical, legacy),
|
|
552
|
+
)
|
|
553
|
+
total += cur.rowcount or 0
|
|
554
|
+
conn.commit()
|
|
555
|
+
click.echo(
|
|
556
|
+
f"[graph-reindex] rebuild-kinds: "
|
|
557
|
+
f"{len(renames)} distinct kind(s) rewritten, "
|
|
558
|
+
f"{total} row(s) updated"
|
|
559
|
+
)
|
|
560
|
+
return
|
|
561
|
+
finally:
|
|
562
|
+
conn.close()
|
|
563
|
+
|
|
564
|
+
import time as _time
|
|
565
|
+
|
|
566
|
+
from graph_os.ingest import walk_local # type: ignore
|
|
567
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
568
|
+
|
|
569
|
+
# W7.5 / R4-N7: prune stale-path nodes before reindex when asked.
|
|
570
|
+
if prune_stale:
|
|
571
|
+
from graph_os.tools.graph import cos_graph_doctor # type: ignore
|
|
572
|
+
|
|
573
|
+
doc = cos_graph_doctor(fix=True)
|
|
574
|
+
# cos_graph_doctor returns a JSON string (FastMCP wire); parse
|
|
575
|
+
# it before reading nested fields.
|
|
576
|
+
if isinstance(doc, str):
|
|
577
|
+
doc = json.loads(doc)
|
|
578
|
+
fixed = doc.get("data", {}).get("meta", {}).get("fixed_count", 0)
|
|
579
|
+
click.echo(f"[graph-reindex] prune-stale: removed {fixed} stale node(s)")
|
|
580
|
+
|
|
581
|
+
target = Path(path or Path.cwd()).resolve()
|
|
582
|
+
# --path is the WALK directory, not the project root. Resolve the
|
|
583
|
+
# enclosing repo root (nearest ancestor with .coding-os/) so a
|
|
584
|
+
# sub-dir --path can't spawn a stray <subdir>/.coding-os/ DB or
|
|
585
|
+
# emit subdir-relative file_paths.
|
|
586
|
+
project_root = next(
|
|
587
|
+
(p for p in (target, *target.parents) if (p / ".coding-os").is_dir()),
|
|
588
|
+
target,
|
|
589
|
+
)
|
|
590
|
+
plan = walk_local(target, max_files=max_files)
|
|
591
|
+
click.echo(f"[graph-reindex] walking {target}; {len(plan.files)} files (force={force})")
|
|
592
|
+
# Surface oversized files dropped by the per-file byte cap — a skipped
|
|
593
|
+
# large source file is a coverage gap, not a no-op (TASK-293).
|
|
594
|
+
_oversize = plan.metadata.get("skipped_oversize") or []
|
|
595
|
+
if _oversize:
|
|
596
|
+
click.echo(
|
|
597
|
+
f"[graph-reindex] skipped {len(_oversize)} oversized file(s) "
|
|
598
|
+
f"(> COS_GRAPH_MAX_FILE_BYTES): {', '.join(_oversize[:5])}"
|
|
599
|
+
+ (" …" if len(_oversize) > 5 else ""),
|
|
600
|
+
err=True,
|
|
601
|
+
)
|
|
602
|
+
# Symlink / unreadable skips — counts only, surfaced so they aren't
|
|
603
|
+
# silent (TASK-302). A symlink target is indexed on its own pass, so
|
|
604
|
+
# a non-zero symlink count is informational, not an error.
|
|
605
|
+
_sym = int(plan.metadata.get("skipped_symlink") or 0)
|
|
606
|
+
_rerr = int(plan.metadata.get("skipped_read_error") or 0)
|
|
607
|
+
if _sym or _rerr:
|
|
608
|
+
click.echo(
|
|
609
|
+
f"[graph-reindex] skipped {_sym} symlink(s), {_rerr} unreadable file(s)",
|
|
610
|
+
err=True,
|
|
611
|
+
)
|
|
612
|
+
processed = skipped = errors = lock_streak = 0
|
|
613
|
+
failed_paths: list[str] = []
|
|
614
|
+
started = _time.monotonic()
|
|
615
|
+
# Circuit breaker: a write lock held by another process makes EVERY
|
|
616
|
+
# file fail after its bounded busy-wait (~5s × 3 retries). Without
|
|
617
|
+
# this, an 800-file walk grinds silently for an hour — the per-file
|
|
618
|
+
# failure used to land only in layers.graph.status, which this loop
|
|
619
|
+
# counted as PROCESSED (2026-06-11 stall, TASK-394/395). The streak
|
|
620
|
+
# resets on any success so a transient start-of-walk lock storm
|
|
621
|
+
# (workers warming up alongside hub/MCP writers) doesn't abort a
|
|
622
|
+
# run that is actually progressing.
|
|
623
|
+
_LOCK_STREAK_ABORT = 10
|
|
624
|
+
|
|
625
|
+
def _record(report: dict) -> None:
|
|
626
|
+
nonlocal processed, skipped, errors, lock_streak
|
|
627
|
+
cache = report.get("cache")
|
|
628
|
+
if cache == "hit":
|
|
629
|
+
skipped += 1
|
|
630
|
+
return
|
|
631
|
+
processed += 1
|
|
632
|
+
reason = _report_failure_reason(report)
|
|
633
|
+
if reason is None:
|
|
634
|
+
lock_streak = 0
|
|
635
|
+
return
|
|
636
|
+
errors += 1
|
|
637
|
+
failed_paths.append(str(report.get("path") or ""))
|
|
638
|
+
if errors <= 5:
|
|
639
|
+
click.echo(f"[graph-reindex] ! {report.get('path')}: {reason}", err=True)
|
|
640
|
+
if _is_lock_shaped(reason):
|
|
641
|
+
lock_streak += 1
|
|
642
|
+
if lock_streak >= _LOCK_STREAK_ABORT:
|
|
643
|
+
raise click.ClickException(
|
|
644
|
+
f"aborting: {lock_streak} consecutive lock-shaped graph write "
|
|
645
|
+
"failures — another process is holding the DB write lock; "
|
|
646
|
+
"close it and re-run"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# Live per-file progress bar — click.progressbar auto-hides when
|
|
650
|
+
# stdout is not a TTY (pipes / CI), so non-interactive runs keep
|
|
651
|
+
# the summary-only output. Replaces the per-file cache-hit echo.
|
|
652
|
+
bar = click.progressbar(length=len(plan.files), label="[graph-reindex] indexing")
|
|
653
|
+
if workers and workers > 1:
|
|
654
|
+
# ProcessPoolExecutor parallelism for monorepo-scale walks. Each
|
|
655
|
+
# worker opens its own SQLite connection via init_db() inside
|
|
656
|
+
# dispatch(); WAL mode + the dispatcher's busy-retry loop handle
|
|
657
|
+
# concurrent writers.
|
|
658
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
659
|
+
|
|
660
|
+
click.echo(f"[graph-reindex] parallel workers={workers}")
|
|
661
|
+
futures = {}
|
|
662
|
+
with ProcessPoolExecutor(max_workers=workers) as pool, bar:
|
|
663
|
+
for file_path in plan.files:
|
|
664
|
+
fut = pool.submit(
|
|
665
|
+
_parallel_dispatch,
|
|
666
|
+
str(file_path),
|
|
667
|
+
str(project_root),
|
|
668
|
+
not no_docs,
|
|
669
|
+
force,
|
|
670
|
+
)
|
|
671
|
+
futures[fut] = file_path
|
|
672
|
+
for fut in as_completed(futures):
|
|
673
|
+
file_path = futures[fut]
|
|
674
|
+
try:
|
|
675
|
+
report = fut.result()
|
|
676
|
+
_record(report)
|
|
677
|
+
except click.ClickException:
|
|
678
|
+
# Circuit breaker tripped — drop queued work NOW;
|
|
679
|
+
# the context manager's shutdown(wait=True) would
|
|
680
|
+
# otherwise grind through every pending future.
|
|
681
|
+
pool.shutdown(wait=False, cancel_futures=True)
|
|
682
|
+
raise
|
|
683
|
+
except Exception as exc:
|
|
684
|
+
errors += 1
|
|
685
|
+
failed_paths.append(str(file_path))
|
|
686
|
+
click.echo(f"[graph-reindex] ! {file_path}: {exc}", err=True)
|
|
687
|
+
bar.update(1)
|
|
688
|
+
else:
|
|
689
|
+
with bar:
|
|
690
|
+
for file_path in plan.files:
|
|
691
|
+
try:
|
|
692
|
+
report = dispatch(
|
|
693
|
+
file_path,
|
|
694
|
+
project_root=project_root,
|
|
695
|
+
include_docs=not no_docs,
|
|
696
|
+
force=force,
|
|
697
|
+
link_stubs=False, # global link after the walk
|
|
698
|
+
)
|
|
699
|
+
_record(report)
|
|
700
|
+
except click.ClickException:
|
|
701
|
+
raise
|
|
702
|
+
except Exception as exc:
|
|
703
|
+
errors += 1
|
|
704
|
+
failed_paths.append(str(file_path))
|
|
705
|
+
click.echo(f"[graph-reindex] ! {file_path}: {exc}", err=True)
|
|
706
|
+
bar.update(1)
|
|
707
|
+
|
|
708
|
+
# Writers serialize in SQLite, so parallel workers can drop a few
|
|
709
|
+
# files to lock contention (their hash is NOT advanced). Retry the
|
|
710
|
+
# casualties sequentially — no sibling contention — so a parallel
|
|
711
|
+
# walk converges to the same zero-error result as a sequential one.
|
|
712
|
+
if failed_paths:
|
|
713
|
+
recovered = 0
|
|
714
|
+
for rel in [p for p in failed_paths if p]:
|
|
715
|
+
try:
|
|
716
|
+
report = dispatch(
|
|
717
|
+
project_root / rel,
|
|
718
|
+
project_root=project_root,
|
|
719
|
+
include_docs=not no_docs,
|
|
720
|
+
force=force,
|
|
721
|
+
link_stubs=False,
|
|
722
|
+
)
|
|
723
|
+
if _report_failure_reason(report) is None:
|
|
724
|
+
recovered += 1
|
|
725
|
+
except Exception as exc:
|
|
726
|
+
click.echo(f"[graph-reindex] ! retry {rel}: {exc}", err=True)
|
|
727
|
+
errors -= recovered
|
|
728
|
+
click.echo(f"[graph-reindex] retry pass: {recovered}/{len(failed_paths)} recovered")
|
|
729
|
+
duration = _time.monotonic() - started
|
|
730
|
+
|
|
731
|
+
# Reconcile the graph to the current walk BEFORE reporting: file_index_state
|
|
732
|
+
# rows (and their nodes/edges) for files no longer indexed — deleted from
|
|
733
|
+
# disk OR now excluded by .gitignore — are stale and make cos_graph_doctor
|
|
734
|
+
# over-count. GC them first so the parse-error summary below reads the
|
|
735
|
+
# reconciled (exact) state. ONLY on a full, uncapped repo walk
|
|
736
|
+
# (target == project_root); a --path sub-walk or a max_files-capped walk
|
|
737
|
+
# would wrongly flag every other file as stale (TASK-302).
|
|
738
|
+
if target == project_root and len(plan.files) < max_files:
|
|
739
|
+
try:
|
|
740
|
+
from database import init_db, resolve_db_path # type: ignore
|
|
741
|
+
|
|
742
|
+
gc_conn = init_db(str(resolve_db_path(project_root)))
|
|
743
|
+
walked = {p.relative_to(project_root).as_posix() for p in plan.files}
|
|
744
|
+
stale = [
|
|
745
|
+
r[0]
|
|
746
|
+
for r in gc_conn.execute(
|
|
747
|
+
"SELECT DISTINCT file_path FROM file_index_state"
|
|
748
|
+
).fetchall()
|
|
749
|
+
if r[0] and r[0] not in walked
|
|
750
|
+
]
|
|
751
|
+
gc_nodes = 0
|
|
752
|
+
for sp in stale:
|
|
753
|
+
gc_conn.execute(
|
|
754
|
+
"DELETE FROM graph_edges_v12 WHERE source_id IN "
|
|
755
|
+
"(SELECT id FROM graph_nodes WHERE file_path=?) "
|
|
756
|
+
"OR target_id IN (SELECT id FROM graph_nodes WHERE file_path=?)",
|
|
757
|
+
(sp, sp),
|
|
758
|
+
)
|
|
759
|
+
cur = gc_conn.execute("DELETE FROM graph_nodes WHERE file_path=?", (sp,))
|
|
760
|
+
gc_nodes += cur.rowcount or 0
|
|
761
|
+
gc_conn.execute("DELETE FROM file_index_state WHERE file_path=?", (sp,))
|
|
762
|
+
if stale:
|
|
763
|
+
gc_conn.commit()
|
|
764
|
+
click.echo(
|
|
765
|
+
f"[graph-reindex] reconcile: pruned {len(stale)} no-longer-indexed "
|
|
766
|
+
f"file(s), {gc_nodes} node(s)"
|
|
767
|
+
)
|
|
768
|
+
except Exception as exc:
|
|
769
|
+
click.echo(f"[graph-reindex] reconcile skipped: {exc}", err=True)
|
|
770
|
+
|
|
771
|
+
# Surface partial-extraction coverage gaps (some symbols dropped on a
|
|
772
|
+
# parse error) — "errors" only counts hard exceptions, so this was
|
|
773
|
+
# silent (TASK-293). Read the cumulative truth from file_index_state,
|
|
774
|
+
# the same source cos_graph_doctor reports, so the two always agree.
|
|
775
|
+
parse_err_total = parse_err_files = 0
|
|
776
|
+
try:
|
|
777
|
+
from database import init_db, resolve_db_path # type: ignore
|
|
778
|
+
|
|
779
|
+
_pe_conn = init_db(str(resolve_db_path(project_root)))
|
|
780
|
+
_pe_row = _pe_conn.execute(
|
|
781
|
+
"SELECT COALESCE(SUM(parse_errors_count), 0), COUNT(DISTINCT file_path) "
|
|
782
|
+
"FROM file_index_state WHERE parse_errors_count > 0"
|
|
783
|
+
).fetchone()
|
|
784
|
+
parse_err_total = int(_pe_row[0] or 0)
|
|
785
|
+
parse_err_files = int(_pe_row[1] or 0)
|
|
786
|
+
except Exception as exc: # fail-open — never block the summary
|
|
787
|
+
import logging
|
|
788
|
+
|
|
789
|
+
logging.getLogger("graph_os.cli").debug("parse-error summary probe skipped: %s", exc)
|
|
790
|
+
click.echo(
|
|
791
|
+
f"[graph-reindex] processed={processed} skipped={skipped} "
|
|
792
|
+
f"errors={errors} parse_errors={parse_err_total} in {parse_err_files} files "
|
|
793
|
+
f"duration={duration:.2f}s"
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# per-file linking during the walk cannot resolve a stub
|
|
797
|
+
# whose real target is indexed LATER (file-order dependency), so
|
|
798
|
+
# cross-module bare-name calls (ok()/fail()/...) leak to external
|
|
799
|
+
# stubs and references/impact under-report callers. Run the GLOBAL
|
|
800
|
+
# linker once now that every node exists — same precise module→file
|
|
801
|
+
# matching as the per-file pass, just applied across the whole graph.
|
|
802
|
+
try:
|
|
803
|
+
_bootstrap_paths()
|
|
804
|
+
from database import init_db, resolve_db_path # type: ignore
|
|
805
|
+
|
|
806
|
+
from graph_os.backends.sqlite_backend import SqliteBackend # type: ignore
|
|
807
|
+
|
|
808
|
+
# Link the same DB the walk indexed — the repo-root DB, NOT a
|
|
809
|
+
# <target>/.coding-os/ stray when --path points at a sub-dir.
|
|
810
|
+
conn = init_db(str(resolve_db_path(project_root)))
|
|
811
|
+
backend = SqliteBackend(conn=conn)
|
|
812
|
+
relinked = backend.link_external_stubs()
|
|
813
|
+
imports_linked = backend.link_import_bindings()
|
|
814
|
+
php_linked = backend.link_php_handlers()
|
|
815
|
+
click.echo(
|
|
816
|
+
f"[graph-reindex] cross-file link: {relinked} stub(s) resolved, "
|
|
817
|
+
f"{imports_linked} import binding(s)"
|
|
818
|
+
+ (f" (+{php_linked} php handler(s))" if php_linked else "")
|
|
819
|
+
)
|
|
820
|
+
except Exception as exc:
|
|
821
|
+
click.echo(f"[graph-reindex] cross-file link skipped: {exc}", err=True)
|
|
822
|
+
|
|
823
|
+
# Residue sweep — the file reconcile above reads file_index_state,
|
|
824
|
+
# which holds file rows only, so folder-spine nodes and zero-edge
|
|
825
|
+
# phantoms (a folder is not a file row; a phantom carries a NULL /
|
|
826
|
+
# off-tree file_path) survive it. A bulk `git mv` / `rm -rf` of a
|
|
827
|
+
# directory fires no per-file deletion-prune, so only this
|
|
828
|
+
# authoritative full-walk pass clears the old-path folder/phantom
|
|
829
|
+
# residue. Runs AFTER the global link so a live external stub already
|
|
830
|
+
# holds its edges and is never swept; the doctor safe-repair deletes
|
|
831
|
+
# only absent-on-disk paths and zero-edge orphans, so an on-disk
|
|
832
|
+
# `src/`-prefixed node with `contains` edges survives. Same full-walk
|
|
833
|
+
# guard as the file reconcile — a sub-walk / capped walk is not
|
|
834
|
+
# authoritative.
|
|
835
|
+
if target == project_root and len(plan.files) < max_files:
|
|
836
|
+
try:
|
|
837
|
+
from graph_os.tools.graph import cos_graph_doctor # type: ignore
|
|
838
|
+
|
|
839
|
+
swept = cos_graph_doctor(fix=True)
|
|
840
|
+
if isinstance(swept, str):
|
|
841
|
+
swept = json.loads(swept)
|
|
842
|
+
swept_n = swept.get("data", {}).get("meta", {}).get("fixed_count", 0)
|
|
843
|
+
if swept_n:
|
|
844
|
+
click.echo(
|
|
845
|
+
f"[graph-reindex] reconcile-sweep: pruned {swept_n} "
|
|
846
|
+
"residual folder/phantom node(s)"
|
|
847
|
+
)
|
|
848
|
+
except Exception as exc:
|
|
849
|
+
click.echo(f"[graph-reindex] reconcile-sweep skipped: {exc}", err=True)
|
|
850
|
+
|
|
851
|
+
@cli.command(name="graph-index-local")
|
|
852
|
+
@click.argument("path")
|
|
853
|
+
@click.option("--alias", default=None)
|
|
854
|
+
@click.option("--max-files", default=50_000, type=int)
|
|
855
|
+
@click.option("--max-size-mb", default=500, type=int)
|
|
856
|
+
def graph_index_local(path, alias, max_files, max_size_mb):
|
|
857
|
+
"""Index a local folder (outside the current repo)."""
|
|
858
|
+
_bootstrap_paths()
|
|
859
|
+
from graph_os.ingest import walk_local # type: ignore
|
|
860
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
861
|
+
|
|
862
|
+
target = Path(path).expanduser().resolve()
|
|
863
|
+
plan = walk_local(
|
|
864
|
+
target,
|
|
865
|
+
alias=alias,
|
|
866
|
+
max_files=max_files,
|
|
867
|
+
max_size_bytes=max_size_mb * 1024 * 1024,
|
|
868
|
+
)
|
|
869
|
+
click.echo(f"[local] alias={plan.alias} files={len(plan.files)}")
|
|
870
|
+
indexed = 0
|
|
871
|
+
with click.progressbar(plan.files, label="[local] indexing") as bar:
|
|
872
|
+
for file_path in bar:
|
|
873
|
+
report = dispatch(file_path, project_root=target, include_docs=True)
|
|
874
|
+
if report.get("status") == "ok":
|
|
875
|
+
indexed += 1
|
|
876
|
+
click.echo(f"[local] indexed {indexed}/{len(plan.files)}")
|
|
877
|
+
|
|
878
|
+
@cli.command(name="graph-index-github")
|
|
879
|
+
@click.argument("url")
|
|
880
|
+
@click.option("--branch", default=None)
|
|
881
|
+
@click.option("--alias", default=None)
|
|
882
|
+
@click.option("--auth", default=None, help="Token for private repos (never logged).")
|
|
883
|
+
@click.option("--max-size-mb", default=500, type=int)
|
|
884
|
+
@click.option("--timeout", default=300, type=int)
|
|
885
|
+
def graph_index_github(url, branch, alias, auth, max_size_mb, timeout):
|
|
886
|
+
"""Clone a public GitHub repo + index (shallow by default)."""
|
|
887
|
+
_bootstrap_paths()
|
|
888
|
+
from graph_os.ingest import GithubSize, clone_github # type: ignore
|
|
889
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
890
|
+
|
|
891
|
+
plan = clone_github(
|
|
892
|
+
url,
|
|
893
|
+
branch=branch,
|
|
894
|
+
alias=alias,
|
|
895
|
+
auth=auth,
|
|
896
|
+
size=GithubSize(
|
|
897
|
+
max_size_bytes=max_size_mb * 1024 * 1024,
|
|
898
|
+
timeout_seconds=timeout,
|
|
899
|
+
),
|
|
900
|
+
)
|
|
901
|
+
click.echo(f"[github] alias={plan.alias} files={len(plan.files)}")
|
|
902
|
+
indexed = 0
|
|
903
|
+
with click.progressbar(plan.files, label="[github] indexing") as bar:
|
|
904
|
+
for file_path in bar:
|
|
905
|
+
report = dispatch(file_path, project_root=plan.root, include_docs=True)
|
|
906
|
+
if report.get("status") == "ok":
|
|
907
|
+
indexed += 1
|
|
908
|
+
click.echo(f"[github] indexed {indexed}/{len(plan.files)}")
|
|
909
|
+
|
|
910
|
+
@cli.command(name="graph-index-zip")
|
|
911
|
+
@click.argument("archive")
|
|
912
|
+
@click.option("--alias", default=None)
|
|
913
|
+
@click.option("--out-dir", default=None, help="Extraction root (default tmp).")
|
|
914
|
+
@click.option("--max-size-mb", default=500, type=int)
|
|
915
|
+
def graph_index_zip(archive, alias, out_dir, max_size_mb):
|
|
916
|
+
"""Extract a ZIP archive with bomb protection + index."""
|
|
917
|
+
_bootstrap_paths()
|
|
918
|
+
from graph_os.ingest import ZipSize, extract_zip # type: ignore
|
|
919
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
920
|
+
|
|
921
|
+
out = Path(out_dir) if out_dir else Path(tempfile.mkdtemp(prefix="cos-zip-"))
|
|
922
|
+
plan = extract_zip(
|
|
923
|
+
archive,
|
|
924
|
+
alias=alias,
|
|
925
|
+
out_dir=out,
|
|
926
|
+
size=ZipSize(max_size_bytes=max_size_mb * 1024 * 1024),
|
|
927
|
+
)
|
|
928
|
+
click.echo(f"[zip] alias={plan.alias} files={len(plan.files)}")
|
|
929
|
+
indexed = 0
|
|
930
|
+
with click.progressbar(plan.files, label="[zip] indexing") as bar:
|
|
931
|
+
for file_path in bar:
|
|
932
|
+
report = dispatch(file_path, project_root=plan.root, include_docs=True)
|
|
933
|
+
if report.get("status") == "ok":
|
|
934
|
+
indexed += 1
|
|
935
|
+
click.echo(f"[zip] indexed {indexed}/{len(plan.files)}")
|
|
936
|
+
|
|
937
|
+
@cli.command(name="graph-detect-changes")
|
|
938
|
+
@click.option(
|
|
939
|
+
"--staged",
|
|
940
|
+
"mode",
|
|
941
|
+
flag_value="staged",
|
|
942
|
+
help="Diff staged changes (git diff --cached --name-only).",
|
|
943
|
+
)
|
|
944
|
+
@click.option(
|
|
945
|
+
"--working",
|
|
946
|
+
"mode",
|
|
947
|
+
flag_value="working",
|
|
948
|
+
default=True,
|
|
949
|
+
help="Diff working-tree changes (git diff --name-only). [default]",
|
|
950
|
+
)
|
|
951
|
+
@click.option(
|
|
952
|
+
"--range",
|
|
953
|
+
"git_range",
|
|
954
|
+
default=None,
|
|
955
|
+
metavar="RANGE",
|
|
956
|
+
help="Diff a commit range, e.g. HEAD~1..HEAD (git diff --name-only RANGE).",
|
|
957
|
+
)
|
|
958
|
+
@click.option("--pretty", is_flag=True)
|
|
959
|
+
def graph_detect_changes(mode, git_range, pretty):
|
|
960
|
+
"""Map changed files to affected graph symbols + downstream tasks."""
|
|
961
|
+
import subprocess
|
|
962
|
+
|
|
963
|
+
# Build the git command based on selected mode.
|
|
964
|
+
if git_range:
|
|
965
|
+
scope = git_range
|
|
966
|
+
git_cmd = ["git", "diff", "--name-only", git_range]
|
|
967
|
+
elif mode == "staged":
|
|
968
|
+
scope = "staged"
|
|
969
|
+
git_cmd = ["git", "diff", "--cached", "--name-only"]
|
|
970
|
+
else:
|
|
971
|
+
scope = "working"
|
|
972
|
+
git_cmd = ["git", "diff", "--name-only"]
|
|
973
|
+
|
|
974
|
+
try:
|
|
975
|
+
result = subprocess.run(
|
|
976
|
+
git_cmd,
|
|
977
|
+
capture_output=True,
|
|
978
|
+
text=True,
|
|
979
|
+
timeout=30,
|
|
980
|
+
)
|
|
981
|
+
if result.returncode != 0:
|
|
982
|
+
raise click.ClickException(
|
|
983
|
+
f"git exited {result.returncode}: {result.stderr.strip()}"
|
|
984
|
+
)
|
|
985
|
+
files = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
986
|
+
except FileNotFoundError:
|
|
987
|
+
raise click.ClickException("git not found on PATH")
|
|
988
|
+
except subprocess.TimeoutExpired:
|
|
989
|
+
raise click.ClickException("git diff timed out after 30 s")
|
|
990
|
+
|
|
991
|
+
_, tools = _open_backend()
|
|
992
|
+
_json_echo(
|
|
993
|
+
tools.cos_graph_detect_changes(
|
|
994
|
+
scope=scope,
|
|
995
|
+
files=files or None,
|
|
996
|
+
analyze_downstream=True,
|
|
997
|
+
),
|
|
998
|
+
pretty=pretty,
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
@cli.command(name="graph-communities")
|
|
1002
|
+
@click.option("--top", default=20, show_default=True, type=int)
|
|
1003
|
+
@click.option("--min-size", default=2, show_default=True, type=int)
|
|
1004
|
+
@click.option("--pretty", is_flag=True)
|
|
1005
|
+
def graph_communities(top, min_size, pretty):
|
|
1006
|
+
"""Louvain process clusters (TASK-075).
|
|
1007
|
+
|
|
1008
|
+
Reads the indexed graph; computes Louvain communities over
|
|
1009
|
+
the call/import subgraph. Use to seed the Search tab grouping
|
|
1010
|
+
or to audit how the graph clusters into named processes.
|
|
1011
|
+
"""
|
|
1012
|
+
_, tools = _open_backend()
|
|
1013
|
+
result = tools.cos_graph_communities(top=int(top), min_size=int(min_size))
|
|
1014
|
+
_json_echo(result, pretty=pretty)
|
|
1015
|
+
|
|
1016
|
+
@cli.command(name="graph-entrypoints")
|
|
1017
|
+
@click.option("--top", default=20, show_default=True, type=int)
|
|
1018
|
+
@click.option(
|
|
1019
|
+
"--kind",
|
|
1020
|
+
default="",
|
|
1021
|
+
help="Filter on entry_kind: main / cli / http / cron / test.",
|
|
1022
|
+
)
|
|
1023
|
+
@click.option("--min-score", default=0.05, show_default=True, type=float)
|
|
1024
|
+
@click.option("--pretty", is_flag=True)
|
|
1025
|
+
def graph_entrypoints(top, kind, min_score, pretty):
|
|
1026
|
+
"""Top-N scored entry points (main / cli / http / cron / test) — TASK-081.
|
|
1027
|
+
|
|
1028
|
+
Reads the indexed graph; no file re-parsing. Use to seed
|
|
1029
|
+
cos_graph_trace, populate the Hub Graph tab "Start from entry
|
|
1030
|
+
point" panel, or sanity-check after a reindex.
|
|
1031
|
+
"""
|
|
1032
|
+
_, tools = _open_backend()
|
|
1033
|
+
result = tools.cos_graph_entrypoints(
|
|
1034
|
+
top=int(top),
|
|
1035
|
+
kind=(kind or None),
|
|
1036
|
+
min_score=float(min_score),
|
|
1037
|
+
)
|
|
1038
|
+
_json_echo(result, pretty=pretty)
|
|
1039
|
+
|
|
1040
|
+
@cli.command(name="graph-viz")
|
|
1041
|
+
@click.option("--path", default=None)
|
|
1042
|
+
@click.option("--out", default=None, help="Output HTML path.")
|
|
1043
|
+
@click.option("--root-uid", default=None)
|
|
1044
|
+
@click.option("--title", default="graph_os")
|
|
1045
|
+
@click.option("--bundled", is_flag=True)
|
|
1046
|
+
@click.option("--serve", is_flag=True)
|
|
1047
|
+
@click.option("--port", default=0, type=int)
|
|
1048
|
+
@click.option("--open/--no-open", "open_browser", default=True)
|
|
1049
|
+
def graph_viz(path, out, root_uid, title, bundled, serve, port, open_browser):
|
|
1050
|
+
"""Generate the HTML graph viewer and optionally open / serve it."""
|
|
1051
|
+
_bootstrap_paths()
|
|
1052
|
+
from graph_os.ingest import walk_local # type: ignore
|
|
1053
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
1054
|
+
from graph_os.viewer import build_view # type: ignore
|
|
1055
|
+
|
|
1056
|
+
backend, _ = _open_backend()
|
|
1057
|
+
if path:
|
|
1058
|
+
target = Path(path).expanduser().resolve()
|
|
1059
|
+
plan = walk_local(target)
|
|
1060
|
+
for file_path in plan.files:
|
|
1061
|
+
dispatch(file_path, project_root=target, include_docs=True)
|
|
1062
|
+
|
|
1063
|
+
out_path = Path(out or Path(".coding-os") / "graph-viz.html").resolve()
|
|
1064
|
+
build_view(backend, out_path, title=title, root_uid=root_uid, bundled=bundled)
|
|
1065
|
+
click.echo(f"[graph-viz] wrote {out_path}")
|
|
1066
|
+
if serve:
|
|
1067
|
+
_serve_static(out_path, port=port, open_browser=open_browser)
|
|
1068
|
+
elif open_browser:
|
|
1069
|
+
import webbrowser
|
|
1070
|
+
|
|
1071
|
+
webbrowser.open(out_path.as_uri())
|
|
1072
|
+
|
|
1073
|
+
# ── group family --------------------------------------------------
|
|
1074
|
+
@cli.group(name="graph-group")
|
|
1075
|
+
def graph_group():
|
|
1076
|
+
"""Cross-repo group operations."""
|
|
1077
|
+
|
|
1078
|
+
@graph_group.command("create")
|
|
1079
|
+
@click.argument("name")
|
|
1080
|
+
@click.option(
|
|
1081
|
+
"--manifest-dir", default=None, help="Root for ~/.coding-os/groups/<name>/ overrides."
|
|
1082
|
+
)
|
|
1083
|
+
def group_create(name, manifest_dir):
|
|
1084
|
+
_bootstrap_paths()
|
|
1085
|
+
from graph_os.groups import GroupManifest, save_manifest # type: ignore
|
|
1086
|
+
|
|
1087
|
+
target = _group_manifest_path(name, manifest_dir)
|
|
1088
|
+
if target.exists():
|
|
1089
|
+
raise click.ClickException(f"group already exists at {target}")
|
|
1090
|
+
save_manifest(GroupManifest(name=name, members=[]), target)
|
|
1091
|
+
click.echo(f"[group] created {target}")
|
|
1092
|
+
|
|
1093
|
+
@graph_group.command("add")
|
|
1094
|
+
@click.argument("name")
|
|
1095
|
+
@click.argument("path")
|
|
1096
|
+
@click.option("--alias", default=None)
|
|
1097
|
+
@click.option("--owns-route", multiple=True)
|
|
1098
|
+
@click.option("--manifest-dir", default=None)
|
|
1099
|
+
def group_add(name, path, alias, owns_route, manifest_dir):
|
|
1100
|
+
_bootstrap_paths()
|
|
1101
|
+
from graph_os.groups import load_manifest, register_member, save_manifest # type: ignore
|
|
1102
|
+
|
|
1103
|
+
target = _group_manifest_path(name, manifest_dir)
|
|
1104
|
+
if not target.exists():
|
|
1105
|
+
raise click.ClickException("group missing; run `cos graph-group create` first")
|
|
1106
|
+
manifest = load_manifest(target)
|
|
1107
|
+
alias = alias or Path(path).expanduser().resolve().name
|
|
1108
|
+
manifest = register_member(
|
|
1109
|
+
manifest,
|
|
1110
|
+
alias=alias,
|
|
1111
|
+
path=str(Path(path).expanduser().resolve()),
|
|
1112
|
+
owned_routes=list(owns_route),
|
|
1113
|
+
)
|
|
1114
|
+
save_manifest(manifest, target)
|
|
1115
|
+
click.echo(f"[group] added {alias} to {name}")
|
|
1116
|
+
|
|
1117
|
+
@graph_group.command("list")
|
|
1118
|
+
@click.option("--manifest-dir", default=None)
|
|
1119
|
+
def group_list(manifest_dir):
|
|
1120
|
+
base = _group_root(manifest_dir)
|
|
1121
|
+
if not base.exists():
|
|
1122
|
+
click.echo("(no groups)")
|
|
1123
|
+
return
|
|
1124
|
+
for entry in sorted(base.iterdir()):
|
|
1125
|
+
if entry.is_dir() and (entry / "group.json").exists():
|
|
1126
|
+
click.echo(entry.name)
|
|
1127
|
+
|
|
1128
|
+
@graph_group.command("status")
|
|
1129
|
+
@click.argument("name")
|
|
1130
|
+
@click.option("--manifest-dir", default=None)
|
|
1131
|
+
def group_status(name, manifest_dir):
|
|
1132
|
+
_bootstrap_paths()
|
|
1133
|
+
from graph_os.groups import load_manifest # type: ignore
|
|
1134
|
+
|
|
1135
|
+
target = _group_manifest_path(name, manifest_dir)
|
|
1136
|
+
if not target.exists():
|
|
1137
|
+
raise click.ClickException("group missing")
|
|
1138
|
+
manifest = load_manifest(target)
|
|
1139
|
+
payload = {
|
|
1140
|
+
"name": manifest.name,
|
|
1141
|
+
"members": [
|
|
1142
|
+
{
|
|
1143
|
+
"alias": m.alias,
|
|
1144
|
+
"path": m.path,
|
|
1145
|
+
"exists": Path(m.path).exists(),
|
|
1146
|
+
"owned_routes": m.owned_routes,
|
|
1147
|
+
}
|
|
1148
|
+
for m in manifest.members
|
|
1149
|
+
],
|
|
1150
|
+
}
|
|
1151
|
+
click.echo(json.dumps(payload, indent=2))
|
|
1152
|
+
|
|
1153
|
+
@graph_group.command("sync")
|
|
1154
|
+
@click.argument("name")
|
|
1155
|
+
@click.option("--manifest-dir", default=None)
|
|
1156
|
+
def group_sync(name, manifest_dir):
|
|
1157
|
+
_bootstrap_paths()
|
|
1158
|
+
from graph_os.groups import load_manifest # type: ignore
|
|
1159
|
+
from graph_os.ingest import walk_local # type: ignore
|
|
1160
|
+
from graph_os.tools.reindex_dispatch import dispatch # type: ignore
|
|
1161
|
+
|
|
1162
|
+
target = _group_manifest_path(name, manifest_dir)
|
|
1163
|
+
manifest = load_manifest(target)
|
|
1164
|
+
indexed = 0
|
|
1165
|
+
for member in manifest.members:
|
|
1166
|
+
root = Path(member.path)
|
|
1167
|
+
if not root.exists():
|
|
1168
|
+
click.echo(f"[sync] skip {member.alias} (missing: {root})", err=True)
|
|
1169
|
+
continue
|
|
1170
|
+
plan = walk_local(root)
|
|
1171
|
+
for file_path in plan.files:
|
|
1172
|
+
report = dispatch(file_path, project_root=root, include_docs=True)
|
|
1173
|
+
if report.get("status") == "ok":
|
|
1174
|
+
indexed += 1
|
|
1175
|
+
click.echo(f"[sync] {member.alias}: {len(plan.files)} files")
|
|
1176
|
+
click.echo(f"[sync] indexed {indexed} entries total")
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
# ---------------------------------------------------------------------------
|
|
1180
|
+
# Helpers
|
|
1181
|
+
# ---------------------------------------------------------------------------
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
def _group_root(manifest_dir: str | None) -> Path:
|
|
1185
|
+
return (
|
|
1186
|
+
Path(manifest_dir).expanduser() if manifest_dir else (Path.home() / ".coding-os" / "groups")
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def _group_manifest_path(name: str, manifest_dir: str | None) -> Path:
|
|
1191
|
+
root = _group_root(manifest_dir)
|
|
1192
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
1193
|
+
folder = root / name
|
|
1194
|
+
folder.mkdir(exist_ok=True)
|
|
1195
|
+
return folder / "group.json"
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def _graph_reindex_print_status() -> None:
|
|
1199
|
+
"""V1 ``--status``: print top 50 most-recently-indexed file_index_state rows."""
|
|
1200
|
+
from datetime import datetime
|
|
1201
|
+
|
|
1202
|
+
_bootstrap_paths()
|
|
1203
|
+
try:
|
|
1204
|
+
import database # type: ignore
|
|
1205
|
+
except ImportError as exc:
|
|
1206
|
+
raise click.ClickException(f"thinking_os db import failed: {exc}") from exc
|
|
1207
|
+
conn = database.init_db()
|
|
1208
|
+
try:
|
|
1209
|
+
if not database.has_file_index_state_table(conn):
|
|
1210
|
+
click.echo(
|
|
1211
|
+
"[graph-reindex] file_index_state table missing (migration v17 not applied)."
|
|
1212
|
+
)
|
|
1213
|
+
return
|
|
1214
|
+
rows = conn.execute(
|
|
1215
|
+
"SELECT file_path, content_hash, extractor_chain, "
|
|
1216
|
+
"last_indexed_at, last_error FROM file_index_state "
|
|
1217
|
+
"ORDER BY last_indexed_at DESC LIMIT 50"
|
|
1218
|
+
).fetchall()
|
|
1219
|
+
finally:
|
|
1220
|
+
conn.close()
|
|
1221
|
+
|
|
1222
|
+
if not rows:
|
|
1223
|
+
click.echo("[graph-reindex] file_index_state is empty.")
|
|
1224
|
+
return
|
|
1225
|
+
|
|
1226
|
+
click.echo(f"{'file_path':<60} {'hash':<12} {'indexed_at':<20} status")
|
|
1227
|
+
click.echo("-" * 110)
|
|
1228
|
+
for file_path, chash, chain, ts, err in rows:
|
|
1229
|
+
when = datetime.fromtimestamp(int(ts)).isoformat(timespec="seconds")
|
|
1230
|
+
status = "error" if err else "ok"
|
|
1231
|
+
chain_hint = chain[:20] + ("…" if len(chain) > 20 else "")
|
|
1232
|
+
display = f"{file_path} [{chain_hint}]"
|
|
1233
|
+
if len(display) > 60:
|
|
1234
|
+
display = display[:57] + "..."
|
|
1235
|
+
click.echo(f"{display:<60} {chash[:12]:<12} {when:<20} {status}")
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def _serve_static(path: Path, *, port: int, open_browser: bool) -> None:
|
|
1239
|
+
import socket
|
|
1240
|
+
import webbrowser
|
|
1241
|
+
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
1242
|
+
|
|
1243
|
+
os.chdir(path.parent)
|
|
1244
|
+
|
|
1245
|
+
class _Handler(SimpleHTTPRequestHandler):
|
|
1246
|
+
def log_message(self, *_args, **_kwargs):
|
|
1247
|
+
pass
|
|
1248
|
+
|
|
1249
|
+
if port == 0:
|
|
1250
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
1251
|
+
s.bind(("127.0.0.1", 0))
|
|
1252
|
+
port = s.getsockname()[1]
|
|
1253
|
+
httpd = HTTPServer(("127.0.0.1", port), _Handler)
|
|
1254
|
+
url = f"http://127.0.0.1:{port}/{path.name}"
|
|
1255
|
+
click.echo(f"[serve] {url}")
|
|
1256
|
+
if open_browser:
|
|
1257
|
+
webbrowser.open(url)
|
|
1258
|
+
try:
|
|
1259
|
+
httpd.serve_forever()
|
|
1260
|
+
except KeyboardInterrupt:
|
|
1261
|
+
httpd.shutdown()
|
|
1262
|
+
click.echo("\n[serve] bye")
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
__all__ = ["register"]
|