coding-os 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adapters/claude/README.md +6 -0
- adapters/claude/_install_helpers/extract_stacks.py +43 -0
- adapters/claude/_install_helpers/update_mcp_json.py +75 -0
- adapters/claude/adapter.yaml +164 -0
- adapters/claude/hooks/README.md +40 -0
- adapters/claude/hooks/agent_memory_sync.py +131 -0
- adapters/claude/hooks/ensure-agent-memory-link.sh +36 -0
- adapters/claude/hooks/sync-agent-memory.sh +21 -0
- adapters/claude/install.sh +86 -0
- adapters/claude/sdk_dispatcher.py +871 -0
- adapters/claude/settings.local.template.json +31 -0
- adapters/claude/settings.template.json +808 -0
- adapters/claude/update_mcp_json.py +85 -0
- adapters/codex/adapter.yaml +254 -0
- adapters/codex/chat_provider.py +230 -0
- adapters/codex/commands/formula-f1.md +129 -0
- adapters/codex/commands/formula-f10.md +100 -0
- adapters/codex/commands/formula-f11.md +123 -0
- adapters/codex/commands/formula-f2.md +139 -0
- adapters/codex/commands/formula-f3.md +127 -0
- adapters/codex/commands/formula-f4.md +101 -0
- adapters/codex/commands/formula-f5.md +135 -0
- adapters/codex/commands/formula-f6.md +147 -0
- adapters/codex/commands/formula-f7.md +111 -0
- adapters/codex/commands/formula-f8.md +133 -0
- adapters/codex/commands/formula-f9.md +112 -0
- adapters/codex/enable_codex_hooks.py +94 -0
- adapters/codex/ensure_codex_mcp.py +124 -0
- adapters/codex/hooks/codex-merge-hook-output.py +72 -0
- adapters/codex/hooks/codex-normalize-edit.py +96 -0
- adapters/codex/hooks/codex-postedit-dispatch.sh +75 -0
- adapters/codex/hooks/codex-posttool-dispatch.sh +70 -0
- adapters/codex/hooks/codex-preedit-dispatch.sh +83 -0
- adapters/codex/hooks/codex-pretool-dispatch.sh +82 -0
- adapters/codex/hooks/codex-sessionend-dispatch.sh +20 -0
- adapters/codex/hooks/codex-sessionstart-dispatch.sh +68 -0
- adapters/codex/hooks/codex-stop-dispatch.sh +73 -0
- adapters/codex/hooks/codex-userpromptsubmit-dispatch.sh +74 -0
- adapters/codex/hooks.template.json +208 -0
- adapters/codex/install.sh +83 -0
- adapters/codex/sdk_dispatcher.py +449 -0
- board_os/__init__.py +39 -0
- board_os/_agent_runtime.py +256 -0
- board_os/config.py +421 -0
- board_os/git_coherence.py +107 -0
- board_os/hub_adapter_manifest.py +140 -0
- board_os/mcp_tools.py +3228 -0
- board_os/migration.py +166 -0
- board_os/parser.py +317 -0
- board_os/presence.py +156 -0
- board_os/sync.py +320 -0
- board_os/transition_gates.py +224 -0
- board_os/transition_gates_cli.py +272 -0
- board_os/transition_gates_validator.py +551 -0
- board_os/verify_suites.py +126 -0
- board_os/verify_suites_cli.py +328 -0
- board_os/workflow.py +967 -0
- cli/__init__.py +0 -0
- cli/_data_types.py +248 -0
- cli/_init_helpers.py +587 -0
- cli/_resources.py +100 -0
- cli/adapter_registry.py +239 -0
- cli/add_stack.py +315 -0
- cli/aggregator.py +438 -0
- cli/board_commands.py +1218 -0
- cli/brain_commands.py +255 -0
- cli/cognition.py +345 -0
- cli/config_composer.py +349 -0
- cli/core_version.py +41 -0
- cli/cron_commands.py +278 -0
- cli/db_reset.py +298 -0
- cli/doc_commands.py +111 -0
- cli/doctor.py +2953 -0
- cli/doctor_board.py +365 -0
- cli/doctor_extras.py +1121 -0
- cli/doctor_graph.py +608 -0
- cli/doctor_tokens.py +254 -0
- cli/graph_commands.py +1265 -0
- cli/hook_renderer.py +393 -0
- cli/hub_commands.py +580 -0
- cli/list_adapters.py +79 -0
- cli/list_stacks.py +105 -0
- cli/logs_commands.py +89 -0
- cli/main.py +3070 -0
- cli/materialize_file.py +65 -0
- cli/mcp_start.py +153 -0
- cli/module_commands.py +513 -0
- cli/pr_commands.py +2024 -0
- cli/preset_commands.py +126 -0
- cli/preset_registry.py +171 -0
- cli/project_overrides.py +119 -0
- cli/registry.py +365 -0
- cli/remove_stack.py +492 -0
- cli/renderer.py +620 -0
- cli/setup.py +464 -0
- cli/skill_commands.py +689 -0
- cli/skill_registry.py +235 -0
- cli/skills_list.py +332 -0
- cli/stack_lint.py +351 -0
- cli/stack_registry.py +688 -0
- cli/subsystems.py +335 -0
- cli/sync_all.py +310 -0
- cli/tail_command.py +410 -0
- cli/update.py +608 -0
- cli/verify_since_edit.py +439 -0
- coding_os-0.3.2.dist-info/METADATA +508 -0
- coding_os-0.3.2.dist-info/RECORD +1304 -0
- coding_os-0.3.2.dist-info/WHEEL +5 -0
- coding_os-0.3.2.dist-info/entry_points.txt +5 -0
- coding_os-0.3.2.dist-info/licenses/LICENSE +201 -0
- coding_os-0.3.2.dist-info/top_level.txt +10 -0
- core/__init__.py +0 -0
- core/board_os/__init__.py +39 -0
- core/board_os/_agent_runtime.py +256 -0
- core/board_os/config.py +421 -0
- core/board_os/git_coherence.py +107 -0
- core/board_os/hub_adapter_manifest.py +140 -0
- core/board_os/mcp_tools.py +3228 -0
- core/board_os/migration.py +166 -0
- core/board_os/parser.py +317 -0
- core/board_os/presence.py +156 -0
- core/board_os/sync.py +320 -0
- core/board_os/transition-gates.yaml +176 -0
- core/board_os/transition_gates.py +224 -0
- core/board_os/transition_gates_cli.py +272 -0
- core/board_os/transition_gates_validator.py +551 -0
- core/board_os/verify-suites.yaml +113 -0
- core/board_os/verify_suites.py +126 -0
- core/board_os/verify_suites_cli.py +328 -0
- core/board_os/workflow.py +967 -0
- core/commands/board.md +27 -0
- core/commands/classify.md +23 -0
- core/commands/compose.md +23 -0
- core/commands/daily.md +31 -0
- core/commands/diagnose.md +7 -0
- core/commands/memory-search.md +23 -0
- core/commands/new-project.md +33 -0
- core/commands/retro.md +38 -0
- core/commands/review.md +14 -0
- core/commands/task.md +17 -0
- core/commands/verify.md +34 -0
- core/docs/thinking_os-final-edition.md +1449 -0
- core/doctor-config.yaml +74 -0
- core/graph_os/__init__.py +29 -0
- core/graph_os/backend.py +233 -0
- core/graph_os/backends/__init__.py +13 -0
- core/graph_os/backends/sqlite_backend.py +1053 -0
- core/graph_os/bench/__init__.py +17 -0
- core/graph_os/bench/fixtures.py +56 -0
- core/graph_os/bench/harness.py +95 -0
- core/graph_os/bench/persian_precision.py +142 -0
- core/graph_os/bench/scale_500k.py +120 -0
- core/graph_os/bench/token_cost.py +170 -0
- core/graph_os/bench/viewer_fps.py +110 -0
- core/graph_os/communities.py +410 -0
- core/graph_os/enterprise.py +218 -0
- core/graph_os/entry_points.py +226 -0
- core/graph_os/extractors/__init__.py +25 -0
- core/graph_os/extractors/code_generic.py +914 -0
- core/graph_os/extractors/code_go.py +1422 -0
- core/graph_os/extractors/code_json.py +340 -0
- core/graph_os/extractors/code_php.py +979 -0
- core/graph_os/extractors/code_python.py +1454 -0
- core/graph_os/extractors/code_shell.py +538 -0
- core/graph_os/extractors/code_toml.py +302 -0
- core/graph_os/extractors/code_ts.py +1665 -0
- core/graph_os/extractors/code_yaml.py +394 -0
- core/graph_os/extractors/contracts.py +1592 -0
- core/graph_os/extractors/md_links.py +890 -0
- core/graph_os/extractors/task_deps.py +345 -0
- core/graph_os/groups/__init__.py +22 -0
- core/graph_os/groups/cross_repo.py +156 -0
- core/graph_os/groups/manifest.py +141 -0
- core/graph_os/ingest/__init__.py +19 -0
- core/graph_os/ingest/base.py +306 -0
- core/graph_os/ingest/github.py +112 -0
- core/graph_os/ingest/zip.py +95 -0
- core/graph_os/toolchain.py +393 -0
- core/graph_os/tools/__init__.py +9 -0
- core/graph_os/tools/graph.py +5573 -0
- core/graph_os/tools/reindex_dispatch.py +730 -0
- core/graph_os/tree_sitter_overlay.py +235 -0
- core/graph_os/types.py +252 -0
- core/graph_os/vec_index.py +277 -0
- core/graph_os/viewer/__init__.py +12 -0
- core/graph_os/viewer/exporter.py +93 -0
- core/graph_os/viewer/template.py +189 -0
- core/hooks/_helpers/_paths.py +40 -0
- core/hooks/_helpers/advance_role.py +72 -0
- core/hooks/_helpers/auto_compose.py +228 -0
- core/hooks/_helpers/auto_validate_lessons.py +55 -0
- core/hooks/_helpers/branch_guard_check.py +796 -0
- core/hooks/_helpers/check_commit_message.py +108 -0
- core/hooks/_helpers/check_dangerous_rm.py +80 -0
- core/hooks/_helpers/check_git_bypass.py +154 -0
- core/hooks/_helpers/check_git_destructive.py +77 -0
- core/hooks/_helpers/check_settings_write.py +97 -0
- core/hooks/_helpers/consume_override.py +51 -0
- core/hooks/_helpers/context_budget.py +77 -0
- core/hooks/_helpers/cos_say_json.py +103 -0
- core/hooks/_helpers/destructive_edit_check.py +163 -0
- core/hooks/_helpers/detect_status_transition.py +82 -0
- core/hooks/_helpers/digest_regen.py +56 -0
- core/hooks/_helpers/doc_sync_check.py +498 -0
- core/hooks/_helpers/drain_embedding_outbox.py +52 -0
- core/hooks/_helpers/extract_additional_context.py +51 -0
- core/hooks/_helpers/extract_commit_msg_arg.py +74 -0
- core/hooks/_helpers/git_command_parse.py +424 -0
- core/hooks/_helpers/git_settings_fields.py +47 -0
- core/hooks/_helpers/graph_context_match.py +37 -0
- core/hooks/_helpers/graph_marker_check.py +70 -0
- core/hooks/_helpers/jit_recall.py +56 -0
- core/hooks/_helpers/json_field.py +41 -0
- core/hooks/_helpers/narrative_signal.py +59 -0
- core/hooks/_helpers/observation_count.py +31 -0
- core/hooks/_helpers/pre_commit_batch.py +177 -0
- core/hooks/_helpers/pre_commit_fake_input.py +42 -0
- core/hooks/_helpers/presence_gc.py +102 -0
- core/hooks/_helpers/presence_write.py +167 -0
- core/hooks/_helpers/recover_indirect.py +35 -0
- core/hooks/_helpers/routing_evolution.py +104 -0
- core/hooks/_helpers/session_recap.py +72 -0
- core/hooks/_helpers/skill_primer.py +229 -0
- core/hooks/_helpers/task_sync.py +59 -0
- core/hooks/_helpers/tool_failure_capture.py +147 -0
- core/hooks/_helpers/trajectory_autosnap.py +278 -0
- core/hooks/_helpers/trajectory_startup.py +62 -0
- core/hooks/_helpers/turn_summary.py +82 -0
- core/hooks/_helpers/validate_task_frontmatter.py +98 -0
- core/hooks/_helpers/wip_limit_check.py +103 -0
- core/hooks/_helpers/wip_lines.py +53 -0
- core/hooks/_helpers/work_log_append.py +89 -0
- core/hooks/_helpers/wrap_dispatch_output.py +82 -0
- core/hooks/advance-role.sh +48 -0
- core/hooks/agent-presence.sh +179 -0
- core/hooks/auto-brain-decay.sh +184 -0
- core/hooks/auto-compose-roles.sh +83 -0
- core/hooks/auto-graph-reconcile-shell.sh +119 -0
- core/hooks/auto-regen-doc-index.sh +120 -0
- core/hooks/auto-reindex-docs.sh +130 -0
- core/hooks/auto-task-sync.sh +56 -0
- core/hooks/auto-trace-rotate.sh +88 -0
- core/hooks/block-bad-patterns.sh +212 -0
- core/hooks/block-dangerous-commands.sh +182 -0
- core/hooks/block-hardcoded-literals.sh +90 -0
- core/hooks/block-migration-conflict.sh +114 -0
- core/hooks/block-protected-files.sh +129 -0
- core/hooks/block-secrets.sh +185 -0
- core/hooks/block-shared-tree-edit.sh +75 -0
- core/hooks/block-uv-heredoc.sh +78 -0
- core/hooks/branch-guard.sh +122 -0
- core/hooks/capture-observation.sh +76 -0
- core/hooks/capture-tool-failure.sh +24 -0
- core/hooks/capture-work-log.sh +89 -0
- core/hooks/check-agents-md-refs.sh +75 -0
- core/hooks/check-agents-md-size.sh +49 -0
- core/hooks/check-capture-worked.sh +148 -0
- core/hooks/check-doc-size.sh +61 -0
- core/hooks/check-mcp-extras.sh +92 -0
- core/hooks/check-state.sh +87 -0
- core/hooks/classify-task-mode.sh +103 -0
- core/hooks/cos-env.sh +1301 -0
- core/hooks/drain-embedding-outbox.sh +24 -0
- core/hooks/enforce-anti-ambiguity.sh +74 -0
- core/hooks/enforce-commit-message.sh +73 -0
- core/hooks/enforce-doc-anchor.sh +224 -0
- core/hooks/enforce-doc-sync.sh +206 -0
- core/hooks/enforce-graph-context.sh +89 -0
- core/hooks/enforce-graph-first-read.sh +94 -0
- core/hooks/enforce-memory-check.sh +128 -0
- core/hooks/enforce-rename-plan.sh +78 -0
- core/hooks/enforce-scaffold-boundary.sh +68 -0
- core/hooks/enforce-skill.sh +125 -0
- core/hooks/enforce-task-body.sh +52 -0
- core/hooks/enforce-task-start.sh +81 -0
- core/hooks/enforce-task-transition.sh +75 -0
- core/hooks/enforce-template.sh +143 -0
- core/hooks/enforce-verify.sh +112 -0
- core/hooks/enforce-wip-limit.sh +43 -0
- core/hooks/enforce-zoom.sh +69 -0
- core/hooks/ensure-hub-up.sh +67 -0
- core/hooks/inject-mcp-caller-session.sh +70 -0
- core/hooks/jit-recall.sh +65 -0
- core/hooks/link-commit-to-task.sh +143 -0
- core/hooks/lint-task.sh +40 -0
- core/hooks/nudge-docs-first.sh +71 -0
- core/hooks/nudge-git-mode.sh +29 -0
- core/hooks/nudge-graph-os.sh +118 -0
- core/hooks/nudge-learn-narrative.sh +36 -0
- core/hooks/nudge-model-routing.sh +32 -0
- core/hooks/nudge-reentry.sh +101 -0
- core/hooks/nudge-reuse-first.sh +68 -0
- core/hooks/nudge-task-discovery.sh +81 -0
- core/hooks/nudge-thinking-os.sh +109 -0
- core/hooks/pr-reap.sh +23 -0
- core/hooks/reclaim-sweep.sh +58 -0
- core/hooks/record-verify-auto.sh +77 -0
- core/hooks/record-verify.sh +74 -0
- core/hooks/regen-reminder.sh +104 -0
- core/hooks/registry.yaml +1262 -0
- core/hooks/remind-daily.sh +27 -0
- core/hooks/remind-dogfood.sh +70 -0
- core/hooks/remind-learn-validate.sh +94 -0
- core/hooks/rules-primer.sh +50 -0
- core/hooks/search-enforce-inventory.sh +108 -0
- core/hooks/search-verify-remaining.sh +132 -0
- core/hooks/session-context.sh +729 -0
- core/hooks/session-end.sh +145 -0
- core/hooks/session-skill-primer.sh +43 -0
- core/hooks/snapshot-transcript.sh +56 -0
- core/hooks/sync-task-current.sh +85 -0
- core/hooks/test-first-reminder.sh +120 -0
- core/hooks/test-governor.sh +173 -0
- core/hooks/thinking_os-gate.sh +52 -0
- core/hooks/track-backtrack.sh +35 -0
- core/hooks/track-discovery.sh +121 -0
- core/hooks/track-skill.sh +53 -0
- core/hooks/validate-task-frontmatter.sh +49 -0
- core/hooks/verify-rename-callers.sh +119 -0
- core/hooks/warn-abandoned-task.sh +99 -0
- core/hooks/warn-destructive-edit.sh +64 -0
- core/hooks/warn-diff-size.sh +43 -0
- core/hooks/warn-graph-empty.sh +81 -0
- core/hooks/warn-mcp-down.sh +190 -0
- core/hooks/write-state.sh +55 -0
- core/logging_os/__init__.py +33 -0
- core/logging_os/api.py +127 -0
- core/logging_os/bridge.py +80 -0
- core/logging_os/config.py +172 -0
- core/logging_os/fingerprint.py +25 -0
- core/logging_os/redact.py +53 -0
- core/logging_os/render.py +83 -0
- core/logging_os/sinks.py +164 -0
- core/rules/anti-overengineering.md +44 -0
- core/rules/api-contract-discipline.md +41 -0
- core/rules/dimension-registry.md +155 -0
- core/rules/git-workflow.md +57 -0
- core/rules/memory.md +46 -0
- core/rules/model-routing.md +22 -0
- core/rules/skill-enforcement.md +75 -0
- core/rules/test-discipline.md +38 -0
- core/rules/thinking_os.md +48 -0
- core/rules/transparency-banner.md +37 -0
- core/runtime_paths.yaml +36 -0
- core/scaffold_manifest.json +14430 -0
- core/scheduled/__init__.py +0 -0
- core/scheduled/_activity.py +126 -0
- core/scheduled/_state.py +113 -0
- core/scheduled/config.py +86 -0
- core/scheduled/dep_reconcile.py +135 -0
- core/scheduled/error_sweep.py +137 -0
- core/scheduled/nightly.py +930 -0
- core/scheduled/responsive_extract.py +65 -0
- core/schemas/adapter.schema.json +269 -0
- core/schemas/preset.schema.json +50 -0
- core/schemas/skill.schema.json +81 -0
- core/schemas/stack.schema.json +404 -0
- core/scripts/_lib.sh +9 -0
- core/scripts/docs-lint.sh +228 -0
- core/scripts/docs-nav-fix.sh +133 -0
- core/scripts/docs-staleness-check.sh +154 -0
- core/scripts/install-adapter.sh +266 -0
- core/scripts/link-stack-skills.sh +52 -0
- core/scripts/log-latest.sh +106 -0
- core/scripts/log-search.sh +89 -0
- core/scripts/log-write.sh +134 -0
- core/scripts/ref-resolve.sh +71 -0
- core/skills/a11y/SKILL.md +305 -0
- core/skills/a11y/assets/a11y-checklist.md +137 -0
- core/skills/a11y/references/aria-and-focus.md +247 -0
- core/skills/a11y/references/rn-accessibility.md +343 -0
- core/skills/a11y/references/screen-reader-testing.md +190 -0
- core/skills/agent-memory/SKILL.md +191 -0
- core/skills/agent-memory/assets/memory-checklist.md +21 -0
- core/skills/agent-memory/references/memory-recipes.md +57 -0
- core/skills/api-design/SKILL.md +232 -0
- core/skills/api-design/assets/api-design-checklist.md +110 -0
- core/skills/api-design/references/error-envelope.md +381 -0
- core/skills/api-design/references/idempotency-pagination.md +312 -0
- core/skills/api-design/references/rest-contracts.md +426 -0
- core/skills/auth-patterns/SKILL.md +352 -0
- core/skills/auth-patterns/assets/auth-checklist.md +118 -0
- core/skills/auth-patterns/references/jwt-and-service-tokens.md +343 -0
- core/skills/auth-patterns/references/passkeys-2fa.md +289 -0
- core/skills/auth-patterns/references/sessions-vs-jwt.md +230 -0
- core/skills/auth-patterns/scripts/cookie-flag-check.py +146 -0
- core/skills/backend-fundamentals/SKILL.md +238 -0
- core/skills/backend-fundamentals/assets/backend-checklist.md +27 -0
- core/skills/backend-fundamentals/references/backend-patterns.md +56 -0
- core/skills/backend-fundamentals/scripts/check_layering.py +83 -0
- core/skills/clean-code/SKILL.md +642 -0
- core/skills/clean-code/scripts/audit-fail-closed.py +167 -0
- core/skills/codebase-explorer/SKILL.md +89 -0
- core/skills/codebase-explorer/assets/reading-checklist.md +24 -0
- core/skills/codebase-explorer/references/reading-strategies.md +52 -0
- core/skills/codebase-explorer/scripts/outline.py +99 -0
- core/skills/db-design/SKILL.md +327 -0
- core/skills/db-design/assets/migration-template.sql +49 -0
- core/skills/db-design/references/migration-discipline.md +290 -0
- core/skills/db-design/references/postgres-patterns.md +340 -0
- core/skills/db-design/scripts/migration-safety.sh +150 -0
- core/skills/deployment-cicd/SKILL.md +260 -0
- core/skills/deployment-cicd/assets/deploy-checklist.md +26 -0
- core/skills/deployment-cicd/references/pipeline-and-release.md +54 -0
- core/skills/deployment-cicd/scripts/lint_workflow.py +79 -0
- core/skills/docker/SKILL.md +114 -0
- core/skills/docker/assets/dockerfile-checklist.md +31 -0
- core/skills/docker/references/compose-patterns.md +66 -0
- core/skills/docker/references/dockerfile-optimization.md +64 -0
- core/skills/docker/scripts/lint_dockerfile.sh +48 -0
- core/skills/docker/versions.json +16 -0
- core/skills/end-to-end-testing/SKILL.md +101 -0
- core/skills/end-to-end-testing/assets/e2e-checklist.md +23 -0
- core/skills/end-to-end-testing/references/maestro.md +63 -0
- core/skills/end-to-end-testing/references/playwright.md +68 -0
- core/skills/end-to-end-testing/scripts/lint_e2e.py +88 -0
- core/skills/end-to-end-testing/versions.json +16 -0
- core/skills/frontend-design/SKILL.md +76 -0
- core/skills/frontend-design/assets/design-checklist.md +29 -0
- core/skills/frontend-design/references/design-principles.md +65 -0
- core/skills/frontend-design/scripts/check_contrast.py +89 -0
- core/skills/frontend-fundamentals/SKILL.md +213 -0
- core/skills/frontend-fundamentals/assets/frontend-checklist.md +25 -0
- core/skills/frontend-fundamentals/references/rendering-and-state.md +66 -0
- core/skills/frontend-fundamentals/scripts/check_frontend.py +86 -0
- core/skills/graph-explorer/SKILL.md +215 -0
- core/skills/graph-explorer/scripts/explain-impact.sh +64 -0
- core/skills/graphql/SKILL.md +187 -0
- core/skills/grpc-microservices/SKILL.md +174 -0
- core/skills/hexagonal-architecture/SKILL.md +199 -0
- core/skills/hexagonal-architecture/assets/folder-scaffold.md +233 -0
- core/skills/hexagonal-architecture/references/anti-patterns.md +129 -0
- core/skills/hexagonal-architecture/references/go-fiber-layout.md +429 -0
- core/skills/hexagonal-architecture/references/python-fastapi-layout.md +453 -0
- core/skills/hexagonal-architecture/references/react-native-layout.md +428 -0
- core/skills/i18n/SKILL.md +126 -0
- core/skills/incident-response/SKILL.md +225 -0
- core/skills/incident-response/assets/incident-checklist.md +29 -0
- core/skills/incident-response/references/severity-and-runbook.md +53 -0
- core/skills/incident-response/scripts/classify_severity.py +86 -0
- core/skills/linux-sysadmin/SKILL.md +115 -0
- core/skills/linux-sysadmin/assets/hardening-checklist.md +29 -0
- core/skills/linux-sysadmin/references/ssh-hardening.md +62 -0
- core/skills/linux-sysadmin/references/systemd-and-services.md +77 -0
- core/skills/linux-sysadmin/scripts/triage.sh +50 -0
- core/skills/linux-sysadmin/versions.json +17 -0
- core/skills/llm-patterns/SKILL.md +410 -0
- core/skills/llm-patterns/assets/llm-feature-checklist.md +26 -0
- core/skills/llm-patterns/references/rag-and-evals.md +59 -0
- core/skills/llm-patterns/scripts/estimate_tokens.py +75 -0
- core/skills/messaging-queues/SKILL.md +142 -0
- core/skills/mobile-fundamentals/SKILL.md +406 -0
- core/skills/mobile-fundamentals/assets/mobile-launch-checklist.md +130 -0
- core/skills/mobile-fundamentals/references/navigation-and-deep-links.md +337 -0
- core/skills/mobile-fundamentals/references/offline-sync.md +339 -0
- core/skills/node-backend/SKILL.md +114 -0
- core/skills/node-backend/assets/node-checklist.md +25 -0
- core/skills/node-backend/references/async-and-errors.md +67 -0
- core/skills/node-backend/references/event-loop.md +65 -0
- core/skills/node-backend/scripts/check_package.py +78 -0
- core/skills/node-backend/versions.json +17 -0
- core/skills/observability/SKILL.md +289 -0
- core/skills/observability/assets/observability-checklist.md +27 -0
- core/skills/observability/references/instrumentation.md +57 -0
- core/skills/observability/scripts/lint_logging.py +77 -0
- core/skills/payments/SKILL.md +102 -0
- core/skills/performance/SKILL.md +305 -0
- core/skills/performance/assets/perf-checklist.md +143 -0
- core/skills/performance/references/mobile-performance.md +249 -0
- core/skills/performance/references/web-vitals.md +209 -0
- core/skills/php/SKILL.md +116 -0
- core/skills/php/assets/php-checklist.md +26 -0
- core/skills/php/references/modern-php.md +64 -0
- core/skills/php/references/security.md +72 -0
- core/skills/php/scripts/scan_php_smells.py +99 -0
- core/skills/php/versions.json +9 -0
- core/skills/pr-mode-driver/SKILL.md +65 -0
- core/skills/realtime-websockets/SKILL.md +152 -0
- core/skills/redis/SKILL.md +105 -0
- core/skills/redis/assets/redis-checklist.md +27 -0
- core/skills/redis/references/operations.md +66 -0
- core/skills/redis/references/patterns.md +62 -0
- core/skills/redis/scripts/analyze_info.py +101 -0
- core/skills/redis/versions.json +9 -0
- core/skills/search/SKILL.md +91 -0
- core/skills/search/references/grep.md +76 -0
- core/skills/search/scripts/verify-count.sh +73 -0
- core/skills/search-infra/SKILL.md +109 -0
- core/skills/security-mobile/SKILL.md +394 -0
- core/skills/security-mobile/assets/mobile-security-checklist.md +117 -0
- core/skills/security-mobile/references/masvs-l1-checklist.md +127 -0
- core/skills/security-web/SKILL.md +217 -0
- core/skills/security-web/assets/security-web-checklist.md +167 -0
- core/skills/security-web/references/owasp-top-10.md +551 -0
- core/skills/security-web/references/supply-chain.md +179 -0
- core/skills/security-web/scripts/csp-check.sh +153 -0
- core/skills/shell-scripting/SKILL.md +128 -0
- core/skills/shell-scripting/assets/script-checklist.md +33 -0
- core/skills/shell-scripting/references/argument-parsing.md +84 -0
- core/skills/shell-scripting/references/bash-robustness.md +78 -0
- core/skills/shell-scripting/scripts/lint_script.sh +53 -0
- core/skills/shell-scripting/scripts/new_script.py +131 -0
- core/skills/shell-scripting/versions.json +23 -0
- core/skills/sql-authoring/SKILL.md +104 -0
- core/skills/sql-authoring/assets/query-review-checklist.md +26 -0
- core/skills/sql-authoring/references/query-patterns.md +89 -0
- core/skills/sql-authoring/references/reading-explain.md +52 -0
- core/skills/sql-authoring/scripts/analyze_plan.py +100 -0
- core/skills/sql-authoring/versions.json +17 -0
- core/skills/state-management/SKILL.md +428 -0
- core/skills/state-management/references/tanstack-query-recipes.md +301 -0
- core/skills/state-management/references/zustand-recipes.md +364 -0
- core/skills/supabase/SKILL.md +108 -0
- core/skills/supabase/assets/supabase-checklist.md +26 -0
- core/skills/supabase/references/realtime-and-storage.md +55 -0
- core/skills/supabase/references/rls-and-auth.md +65 -0
- core/skills/supabase/scripts/check_rls.py +88 -0
- core/skills/supabase/versions.json +9 -0
- core/skills/task-driver/SKILL.md +272 -0
- core/skills/task-driver/scripts/task-lint.sh +163 -0
- core/skills/technical-writing/SKILL.md +85 -0
- core/skills/technical-writing/assets/doc-checklist.md +29 -0
- core/skills/technical-writing/references/doc-anatomy.md +53 -0
- core/skills/technical-writing/references/writing-craft.md +59 -0
- core/skills/technical-writing/scripts/new_doc.py +81 -0
- core/skills/terraform-k8s/SKILL.md +133 -0
- core/skills/testing-strategy/SKILL.md +266 -0
- core/skills/testing-strategy/assets/test-review-checklist.md +25 -0
- core/skills/testing-strategy/references/test-types.md +52 -0
- core/skills/testing-strategy/scripts/coverage_gate.py +79 -0
- core/skills/thinking_os/SKILL.md +288 -0
- core/skills/thinking_os/scripts/classify.sh +122 -0
- core/skills/typescript/SKILL.md +110 -0
- core/skills/typescript/assets/typescript-checklist.md +24 -0
- core/skills/typescript/references/strictness.md +53 -0
- core/skills/typescript/references/type-system.md +86 -0
- core/skills/typescript/scripts/check_tsconfig.py +92 -0
- core/skills/typescript/versions.json +9 -0
- core/subsystems.yaml +202 -0
- core/thinking_os/__init__.py +1 -0
- core/thinking_os/_agent_markers.py +32 -0
- core/thinking_os/agents/README.md +71 -0
- core/thinking_os/agents/analyst.md +139 -0
- core/thinking_os/agents/architect.md +127 -0
- core/thinking_os/agents/debugger.md +111 -0
- core/thinking_os/agents/deployer.md +112 -0
- core/thinking_os/agents/distiller.md +28 -0
- core/thinking_os/agents/documenter.md +101 -0
- core/thinking_os/agents/implementer.md +135 -0
- core/thinking_os/agents/internal/session_observer.md +39 -0
- core/thinking_os/agents/observer.md +100 -0
- core/thinking_os/agents/onboarder.md +81 -0
- core/thinking_os/agents/refactorer.md +123 -0
- core/thinking_os/agents/repairer.md +47 -0
- core/thinking_os/agents/researcher.md +129 -0
- core/thinking_os/agents/reviewer.md +147 -0
- core/thinking_os/agents/security_auditor.md +133 -0
- core/thinking_os/background.py +405 -0
- core/thinking_os/bootstrap_outcomes.py +200 -0
- core/thinking_os/budget.py +302 -0
- core/thinking_os/capture.py +495 -0
- core/thinking_os/cognition.py +516 -0
- core/thinking_os/cognition_schemas.py +517 -0
- core/thinking_os/compress.py +192 -0
- core/thinking_os/concepts.py +233 -0
- core/thinking_os/dashboard.py +159 -0
- core/thinking_os/database.py +2883 -0
- core/thinking_os/decay.py +393 -0
- core/thinking_os/digest.py +295 -0
- core/thinking_os/dispatcher.py +192 -0
- core/thinking_os/dispatcher_helpers.py +48 -0
- core/thinking_os/dispatchers/__init__.py +3 -0
- core/thinking_os/dispatchers/default.py +47 -0
- core/thinking_os/distill.py +192 -0
- core/thinking_os/doc_indexer.py +905 -0
- core/thinking_os/embeddings.py +943 -0
- core/thinking_os/formula_composer.py +556 -0
- core/thinking_os/gate_marker.py +75 -0
- core/thinking_os/graph.py +296 -0
- core/thinking_os/graph_indexer.py +360 -0
- core/thinking_os/health_check.py +517 -0
- core/thinking_os/impact.py +119 -0
- core/thinking_os/memory_gc.py +356 -0
- core/thinking_os/migrator_embeddings.py +318 -0
- core/thinking_os/precision.py +194 -0
- core/thinking_os/presets/registry.yaml +127 -0
- core/thinking_os/record_outcome.py +399 -0
- core/thinking_os/repair.py +105 -0
- core/thinking_os/retrieval_quality.py +239 -0
- core/thinking_os/roles/analyst.yaml +83 -0
- core/thinking_os/roles/architect.yaml +88 -0
- core/thinking_os/roles/debugger.yaml +71 -0
- core/thinking_os/roles/deployer.yaml +71 -0
- core/thinking_os/roles/documenter.yaml +72 -0
- core/thinking_os/roles/implementer.yaml +83 -0
- core/thinking_os/roles/observer.yaml +70 -0
- core/thinking_os/roles/refactorer.yaml +71 -0
- core/thinking_os/roles/researcher.yaml +72 -0
- core/thinking_os/roles/reviewer.yaml +79 -0
- core/thinking_os/roles/security_auditor.yaml +83 -0
- core/thinking_os/roles_state.py +168 -0
- core/thinking_os/sanitizer.py +320 -0
- core/thinking_os/server.py +3160 -0
- core/thinking_os/session_enrich.py +272 -0
- core/thinking_os/session_observe_worker.py +111 -0
- core/thinking_os/session_startup.py +74 -0
- core/thinking_os/session_summary.py +245 -0
- core/thinking_os/situations/registry.yaml +99 -0
- core/thinking_os/task_analyzer.py +462 -0
- core/thinking_os/task_parser.py +342 -0
- core/thinking_os/task_sync.py +73 -0
- core/thinking_os/tools/__init__.py +6 -0
- core/thinking_os/tools/_shared.py +947 -0
- core/thinking_os/tools/cognition.py +1867 -0
- core/thinking_os/tools/docs.py +770 -0
- core/thinking_os/tools/learning.py +2078 -0
- core/thinking_os/tools/logs.py +79 -0
- core/thinking_os/tools/memory.py +840 -0
- core/thinking_os/tools/metrics.py +200 -0
- core/thinking_os/tools/retrieve.py +415 -0
- core/thinking_os/tools/routing.py +658 -0
- core/thinking_os/tools/tasks.py +449 -0
- core/thinking_os/tools/trajectory.py +181 -0
- core/thinking_os/tracing.py +235 -0
- core/web/__init__.py +5 -0
- core/web/_cache.py +118 -0
- core/web/_deps.py +56 -0
- core/web/_envelope.py +85 -0
- core/web/_project_context.py +140 -0
- core/web/chat_providers.py +108 -0
- core/web/init_jobs.py +216 -0
- core/web/routes/__init__.py +25 -0
- core/web/routes/_bounded_read.py +76 -0
- core/web/routes/board.py +1089 -0
- core/web/routes/cognition.py +1838 -0
- core/web/routes/config.py +635 -0
- core/web/routes/graph.py +513 -0
- core/web/routes/health.py +180 -0
- core/web/routes/hooks.py +288 -0
- core/web/routes/hub.py +1219 -0
- core/web/routes/logs.py +374 -0
- core/web/routes/metrics.py +43 -0
- core/web/routes/observability.py +400 -0
- core/web/routes/patterns.py +227 -0
- core/web/routes/presence.py +609 -0
- core/web/routes/roles.py +446 -0
- core/web/routes/scheduled.py +261 -0
- core/web/routes/search.py +238 -0
- core/web/routes/sessions.py +220 -0
- core/web/routes/settings.py +363 -0
- core/web/routes/stream.py +547 -0
- core/web/security.py +157 -0
- core/web/server.py +283 -0
- graph_os/__init__.py +29 -0
- graph_os/backend.py +233 -0
- graph_os/backends/__init__.py +13 -0
- graph_os/backends/sqlite_backend.py +1053 -0
- graph_os/communities.py +410 -0
- graph_os/enterprise.py +218 -0
- graph_os/entry_points.py +226 -0
- graph_os/extractors/__init__.py +25 -0
- graph_os/extractors/code_generic.py +914 -0
- graph_os/extractors/code_go.py +1422 -0
- graph_os/extractors/code_json.py +340 -0
- graph_os/extractors/code_php.py +979 -0
- graph_os/extractors/code_python.py +1454 -0
- graph_os/extractors/code_shell.py +538 -0
- graph_os/extractors/code_toml.py +302 -0
- graph_os/extractors/code_ts.py +1665 -0
- graph_os/extractors/code_yaml.py +394 -0
- graph_os/extractors/contracts.py +1592 -0
- graph_os/extractors/md_links.py +890 -0
- graph_os/extractors/task_deps.py +345 -0
- graph_os/groups/__init__.py +22 -0
- graph_os/groups/cross_repo.py +156 -0
- graph_os/groups/manifest.py +141 -0
- graph_os/ingest/__init__.py +19 -0
- graph_os/ingest/base.py +306 -0
- graph_os/ingest/github.py +112 -0
- graph_os/ingest/zip.py +95 -0
- graph_os/toolchain.py +393 -0
- graph_os/tools/__init__.py +9 -0
- graph_os/tools/graph.py +5573 -0
- graph_os/tools/reindex_dispatch.py +730 -0
- graph_os/tree_sitter_overlay.py +235 -0
- graph_os/types.py +252 -0
- graph_os/vec_index.py +277 -0
- graph_os/viewer/__init__.py +12 -0
- graph_os/viewer/exporter.py +93 -0
- graph_os/viewer/template.py +189 -0
- scheduled/__init__.py +0 -0
- scheduled/_activity.py +126 -0
- scheduled/_state.py +113 -0
- scheduled/config.py +86 -0
- scheduled/dep_reconcile.py +135 -0
- scheduled/error_sweep.py +137 -0
- scheduled/nightly.py +930 -0
- scheduled/responsive_extract.py +65 -0
- scripts/__init__.py +4 -0
- scripts/_commit_msg_body.sh +31 -0
- scripts/_post_commit_body.sh +49 -0
- scripts/_pre_commit_body.sh +121 -0
- scripts/_prepare_commit_msg_body.sh +53 -0
- scripts/audit_mcp_tools.py +693 -0
- scripts/bench_sdk_dispatcher.py +177 -0
- scripts/capture_golden.py +169 -0
- scripts/check_graph_phantoms.py +75 -0
- scripts/dev/audit_doc_links.py +359 -0
- scripts/dev/audit_scaffold_module_tags.py +107 -0
- scripts/dev/backfill_doc_headers.py +328 -0
- scripts/dev/backfill_nav_lines.py +119 -0
- scripts/dev/fix_nav_placement.py +106 -0
- scripts/dev/inspect_sdk_options.py +45 -0
- scripts/dev/migrate_check_ids.py +170 -0
- scripts/dev/strip_purpose_blocks.py +154 -0
- scripts/dump_openapi.py +66 -0
- scripts/e2e_dispatch_tool.py +195 -0
- scripts/generate_manifest.py +166 -0
- scripts/golden_sections.py +20 -0
- scripts/graph_demo.py +161 -0
- scripts/install-git-hooks.sh +47 -0
- scripts/migrate_embeddings_minilm_to_bge_m3.py +84 -0
- scripts/operational_eval.py +445 -0
- scripts/probe_agent_session_resolver.py +59 -0
- scripts/prune_deleted_path.py +127 -0
- scripts/refactor_agent_dual_mode.py +171 -0
- scripts/refresh_skill_versions.py +302 -0
- scripts/regen_doc_index.py +209 -0
- scripts/regen_doctor_schema.py +62 -0
- scripts/regen_rules.py +94 -0
- scripts/rename_formulas_to_semantic.py +241 -0
- scripts/smoke_db_connections.py +183 -0
- scripts/smoke_doc_header.py +72 -0
- scripts/smoke_graph_e2e.py +374 -0
- scripts/smoke_sdk_dispatch.py +84 -0
- scripts/smoke_uid_resolver.py +164 -0
- scripts/verify_dispatchers.py +244 -0
- scripts/verify_phase_c_e2e.py +436 -0
- templates/__init__.py +6 -0
- templates/_base/Makefile.base +353 -0
- templates/_base/base.yaml +59 -0
- templates/_base/coding-os.yaml.template +39 -0
- templates/_base/dimension-registry.template.md +68 -0
- templates/_base/domain-config.template.json +46 -0
- templates/_base/fragments/anatomy-map.md.tmpl +11 -0
- templates/_base/fragments/context-discipline.md.tmpl +3 -0
- templates/_base/fragments/core-loop.md.tmpl +52 -0
- templates/_base/fragments/engineering-routing.md.tmpl +3 -0
- templates/_base/fragments/header.md.tmpl +6 -0
- templates/_base/fragments/identity.md.tmpl +3 -0
- templates/_base/fragments/principles.md.tmpl +3 -0
- templates/_base/fragments/retrieval-routing.md.tmpl +23 -0
- templates/_base/fragments/session-handoff.md.tmpl +3 -0
- templates/_base/fragments/skills.md.tmpl +3 -0
- templates/_base/fragments/ssot-map.md.tmpl +3 -0
- templates/_base/fragments/stop-conditions.md.tmpl +3 -0
- templates/_base/fragments/subagent-dispatch.md.tmpl +3 -0
- templates/_base/fragments/task-authoring.md.tmpl +69 -0
- templates/_base/fragments/task-logging.md.tmpl +8 -0
- templates/_base/fragments/tool-routing.md.tmpl +9 -0
- templates/_base/fragments/verification-matrix.md.tmpl +12 -0
- templates/_base/lang/dart/analysis_options.yaml +7 -0
- templates/_base/lang/php/phpcs.xml.dist +10 -0
- templates/_base/lang/python/pyproject.toml +19 -0
- templates/_base/lang/rust/clippy.toml +5 -0
- templates/_base/lang/rust/rustfmt.toml +3 -0
- templates/_base/lang/typescript/eslint.config.js +26 -0
- templates/_base/lang/typescript/tsconfig.json +15 -0
- templates/_base/lang/typescript/vitest.config.ts +10 -0
- templates/_base/scaffold/changes.log +1 -0
- templates/_base/scaffold/docs/00-index.md +55 -0
- templates/_base/scaffold/docs/_meta/feature-dependency-tree.md +30 -0
- templates/_base/scaffold/docs/_meta/foundation-map.md +56 -0
- templates/_base/scaffold/docs/_meta/questions.md +8 -0
- templates/_base/scaffold/docs/_meta/roadmap.md +33 -0
- templates/_base/scaffold/docs/api-contracts/00-index.md +58 -0
- templates/_base/scaffold/docs/api-contracts/error-format.md +58 -0
- templates/_base/scaffold/docs/architecture/00-index.md +42 -0
- templates/_base/scaffold/docs/architecture/adr/00-index.md +39 -0
- templates/_base/scaffold/docs/engineering/00-index.md +9 -0
- templates/_base/scaffold/docs/governance/00-index.md +55 -0
- templates/_base/scaffold/docs/governance/_templates/doc-cheat-sheet.md +202 -0
- templates/_base/scaffold/docs/governance/_templates/playbook-template.md +81 -0
- templates/_base/scaffold/docs/governance/_templates/post-mortem-template.md +85 -0
- templates/_base/scaffold/docs/governance/_templates/runbook-template.md +88 -0
- templates/_base/scaffold/docs/governance/_templates/security-review-template.md +111 -0
- templates/_base/scaffold/docs/governance/_templates/task-detail.md +61 -0
- templates/_base/scaffold/docs/governance/agent-workflow.md +101 -0
- templates/_base/scaffold/docs/governance/anatomy-contract.md +150 -0
- templates/_base/scaffold/docs/governance/critical-rules.md +224 -0
- templates/_base/scaffold/docs/governance/decision-records.md +58 -0
- templates/_base/scaffold/docs/governance/docs-first-protocol.md +157 -0
- templates/_base/scaffold/docs/governance/docs-system.md +151 -0
- templates/_base/scaffold/docs/governance/gdpr-compliance.md +66 -0
- templates/_base/scaffold/docs/governance/mcp-tool-inventory.md +112 -0
- templates/_base/scaffold/docs/governance/risk-register.md +26 -0
- templates/_base/scaffold/docs/governance/scaffold-boundary-contract.md +161 -0
- templates/_base/scaffold/docs/governance/task-lifecycle.md +125 -0
- templates/_base/scaffold/docs/governance/wrapper-derivation.md +50 -0
- templates/_base/scaffold/docs/insights/00-index.md +17 -0
- templates/_base/scaffold/docs/ops/00-index.md +59 -0
- templates/_base/scaffold/docs/ops/runbooks/00-index.md +9 -0
- templates/_base/scaffold/docs/playbooks/00-index.md +12 -0
- templates/_base/scaffold/docs/playbooks/research-validation.md +29 -0
- templates/_base/scaffold/docs/playbooks/security-review.md +41 -0
- templates/_base/scaffold/docs/prd/00-index.md +43 -0
- templates/_base/scaffold/docs/prd/01-snapshot-vision.md +56 -0
- templates/_base/scaffold/docs/workflow/workflow-guide.md +138 -0
- templates/_base/scaffold/src/shared/README.md +23 -0
- templates/_base/skill-enforcement.template.md +14 -0
- templates/_base/task-detail.template.md +61 -0
- templates/_presets/ai-saas.yaml +9 -0
- templates/_presets/django-next.yaml +8 -0
- templates/_presets/dotnet-react.yaml +8 -0
- templates/_presets/flutter-baas.yaml +8 -0
- templates/_presets/go-react.yaml +8 -0
- templates/_presets/hexagonal-product.yaml +16 -0
- templates/_presets/jamstack.yaml +8 -0
- templates/_presets/laravel-vue.yaml +8 -0
- templates/_presets/mean.yaml +8 -0
- templates/_presets/mern.yaml +9 -0
- templates/_presets/nest-angular.yaml +8 -0
- templates/_presets/nextjs-fastapi.yaml +10 -0
- templates/_presets/nuxt-fullstack.yaml +9 -0
- templates/_presets/pern.yaml +9 -0
- templates/_presets/rails-react.yaml +8 -0
- templates/_presets/rn-api.yaml +8 -0
- templates/_presets/rust-svelte.yaml +8 -0
- templates/_presets/spring-react.yaml +8 -0
- templates/_presets/t3-style.yaml +9 -0
- templates/_presets/tall.yaml +8 -0
- templates/_presets/wordpress-cms.yaml +8 -0
- templates/angular/rules/frontend.md +19 -0
- templates/angular/scaffold/docs/engineering/accessibility.md +46 -0
- templates/angular/scaffold/docs/engineering/angular-rules.md +35 -0
- templates/angular/scaffold/docs/playbooks/angular-app.md +42 -0
- templates/angular/scaffold/src/frontend/angular.json +52 -0
- templates/angular/scaffold/src/frontend/package.json +28 -0
- templates/angular/scaffold/src/frontend/src/app/app.component.ts +19 -0
- templates/angular/scaffold/src/frontend/src/app/app.config.ts +22 -0
- templates/angular/scaffold/src/frontend/src/app/app.routes.ts +8 -0
- templates/angular/scaffold/src/frontend/src/app/core/global-error-handler.ts +12 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.component.ts +14 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.service.spec.ts +16 -0
- templates/angular/scaffold/src/frontend/src/app/health/health.service.ts +10 -0
- templates/angular/scaffold/src/frontend/src/index.html +11 -0
- templates/angular/scaffold/src/frontend/src/main.ts +9 -0
- templates/angular/scaffold/src/frontend/src/styles.css +14 -0
- templates/angular/scaffold/src/frontend/tsconfig.app.json +8 -0
- templates/angular/scaffold/src/frontend/tsconfig.json +27 -0
- templates/angular/scaffold/src/frontend/tsconfig.spec.json +8 -0
- templates/angular/scaffold-boundary.yaml +27 -0
- templates/angular/skills/angular/SKILL.md +79 -0
- templates/angular/skills/angular/references/anatomy.md +69 -0
- templates/angular/stack.yaml +75 -0
- templates/aspnet-core/rules/backend.md +20 -0
- templates/aspnet-core/scaffold/docs/engineering/aspnet-core-rules.md +36 -0
- templates/aspnet-core/scaffold/docs/playbooks/aspnet-core-service.md +40 -0
- templates/aspnet-core/scaffold/src/backend/Backend.csproj +11 -0
- templates/aspnet-core/scaffold/src/backend/Backend.sln +27 -0
- templates/aspnet-core/scaffold/src/backend/Common/ExceptionHandlingMiddleware.cs +35 -0
- templates/aspnet-core/scaffold/src/backend/Features/Health/HealthEndpoints.cs +10 -0
- templates/aspnet-core/scaffold/src/backend/Features/Health/HealthService.cs +9 -0
- templates/aspnet-core/scaffold/src/backend/Program.cs +22 -0
- templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/Backend.Tests.csproj +22 -0
- templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/HealthServiceTests.cs +15 -0
- templates/aspnet-core/scaffold-boundary.yaml +24 -0
- templates/aspnet-core/skills/aspnet-core/SKILL.md +80 -0
- templates/aspnet-core/skills/aspnet-core/references/anatomy.md +65 -0
- templates/aspnet-core/stack.yaml +68 -0
- templates/astro/rules/frontend.md +20 -0
- templates/astro/scaffold/docs/engineering/astro-rules.md +40 -0
- templates/astro/scaffold/docs/playbooks/astro-app.md +57 -0
- templates/astro/scaffold/docs/playbooks/content-seo.md +37 -0
- templates/astro/scaffold/src/frontend/astro.config.mjs +10 -0
- templates/astro/scaffold/src/frontend/package.json +23 -0
- templates/astro/scaffold/src/frontend/src/components/Greeting.astro +13 -0
- templates/astro/scaffold/src/frontend/src/content/posts/hello.md +10 -0
- templates/astro/scaffold/src/frontend/src/content.config.ts +19 -0
- templates/astro/scaffold/src/frontend/src/lib/problem.test.ts +39 -0
- templates/astro/scaffold/src/frontend/src/lib/problem.ts +30 -0
- templates/astro/scaffold/src/frontend/src/pages/api/health.ts +13 -0
- templates/astro/scaffold/src/frontend/src/pages/index.astro +21 -0
- templates/astro/scaffold/src/frontend/tsconfig.json +9 -0
- templates/astro/scaffold/src/frontend/vitest.config.ts +10 -0
- templates/astro/scaffold-boundary.yaml +28 -0
- templates/astro/skills/astro/SKILL.md +71 -0
- templates/astro/skills/astro/references/anatomy.md +66 -0
- templates/astro/stack.yaml +75 -0
- templates/csharp-plain/scaffold/src/backend/Backend.csproj +12 -0
- templates/csharp-plain/scaffold/src/backend/Program.cs +1 -0
- templates/csharp-plain/scaffold-boundary.yaml +23 -0
- templates/csharp-plain/stack.yaml +50 -0
- templates/django/rules/backend.md +18 -0
- templates/django/scaffold/docs/engineering/anti-ambiguity.md +74 -0
- templates/django/scaffold/docs/engineering/backend-rules.md +133 -0
- templates/django/scaffold/docs/engineering/glossary.md +51 -0
- templates/django/scaffold/docs/engineering/logging-standards.md +107 -0
- templates/django/scaffold/docs/engineering/naming-conventions.md +68 -0
- templates/django/scaffold/docs/engineering/secrets-rotation-runbook.md +142 -0
- templates/django/scaffold/docs/playbooks/backend-api.md +119 -0
- templates/django/scaffold/src/backend/config/__init__.py +0 -0
- templates/django/scaffold/src/backend/config/settings.py +31 -0
- templates/django/scaffold/src/backend/config/urls.py +11 -0
- templates/django/scaffold/src/backend/config/wsgi.py +6 -0
- templates/django/scaffold/src/backend/manage.py +14 -0
- templates/django/scaffold/src/backend/pyproject.toml +37 -0
- templates/django/scaffold/src/backend/tests/test_health.py +4 -0
- templates/django/scaffold-boundary.yaml +25 -0
- templates/django/skills/python-django/SKILL.md +450 -0
- templates/django/skills/python-django/references/anatomy.md +117 -0
- templates/django/skills/python-django/scripts/new_endpoint.py +89 -0
- templates/django/stack.yaml +73 -0
- templates/fastapi/rules/backend.md +18 -0
- templates/fastapi/scaffold/docs/engineering/fastapi-rules.md +37 -0
- templates/fastapi/scaffold/docs/playbooks/fastapi-service.md +30 -0
- templates/fastapi/scaffold/src/backend/app/__init__.py +0 -0
- templates/fastapi/scaffold/src/backend/app/main.py +8 -0
- templates/fastapi/scaffold/src/backend/pyproject.toml +39 -0
- templates/fastapi/scaffold/src/backend/tests/test_health.py +11 -0
- templates/fastapi/scaffold-boundary.yaml +25 -0
- templates/fastapi/skills/python-fastapi/SKILL.md +75 -0
- templates/fastapi/skills/python-fastapi/references/anatomy.md +117 -0
- templates/fastapi/skills/python-fastapi/scripts/new_endpoint.py +101 -0
- templates/fastapi/stack.yaml +57 -0
- templates/flutter/rules/mobile.md +26 -0
- templates/flutter/scaffold/docs/engineering/flutter-rules.md +35 -0
- templates/flutter/scaffold/docs/playbooks/flutter-app.md +45 -0
- templates/flutter/scaffold/src/mobile/lib/core/error_mapper.dart +17 -0
- templates/flutter/scaffold/src/mobile/lib/core/router.dart +13 -0
- templates/flutter/scaffold/src/mobile/lib/main.dart +22 -0
- templates/flutter/scaffold/src/mobile/lib/screens/health_screen.dart +32 -0
- templates/flutter/scaffold/src/mobile/lib/services/health_service.dart +11 -0
- templates/flutter/scaffold/src/mobile/lib/state/health_provider.dart +13 -0
- templates/flutter/scaffold/src/mobile/pubspec.yaml +22 -0
- templates/flutter/scaffold/src/mobile/test/health_provider_test.dart +90 -0
- templates/flutter/scaffold-boundary.yaml +28 -0
- templates/flutter/skills/flutter/SKILL.md +76 -0
- templates/flutter/skills/flutter/references/anatomy.md +64 -0
- templates/flutter/stack.yaml +70 -0
- templates/go/rules/backend.md +19 -0
- templates/go/scaffold/docs/engineering/go-rules.md +45 -0
- templates/go/scaffold/docs/playbooks/go-service.md +30 -0
- templates/go/scaffold/src/backend/cmd/api/main.go +22 -0
- templates/go/scaffold/src/backend/cmd/api/main_test.go +20 -0
- templates/go/scaffold/src/backend/go.mod +3 -0
- templates/go/scaffold-boundary.yaml +25 -0
- templates/go/skills/go-patterns/SKILL.md +68 -0
- templates/go/skills/go-patterns/assets/go-checklist.md +29 -0
- templates/go/skills/go-patterns/references/anatomy.md +115 -0
- templates/go/skills/go-patterns/references/go-2026-idioms.md +92 -0
- templates/go/skills/go-patterns/scripts/new_endpoint.py +117 -0
- templates/go/skills/go-patterns/versions.json +16 -0
- templates/go/stack.yaml +54 -0
- templates/go-fiber/rules/backend.md +20 -0
- templates/go-fiber/scaffold/docs/engineering/fiber-rules.md +97 -0
- templates/go-fiber/scaffold/docs/playbooks/fiber-service.md +149 -0
- templates/go-fiber/scaffold/src/backend/cmd/api/main.go +21 -0
- templates/go-fiber/scaffold/src/backend/cmd/api/main_test.go +17 -0
- templates/go-fiber/scaffold/src/backend/go.mod +23 -0
- templates/go-fiber/scaffold/src/backend/go.sum +49 -0
- templates/go-fiber/scaffold-boundary.yaml +26 -0
- templates/go-fiber/skills/go-fiber/SKILL.md +203 -0
- templates/go-fiber/skills/go-fiber/assets/fiber-checklist.md +26 -0
- templates/go-fiber/skills/go-fiber/references/anatomy.md +116 -0
- templates/go-fiber/skills/go-fiber/references/fiber-v3-patterns.md +89 -0
- templates/go-fiber/skills/go-fiber/scripts/new_endpoint.py +107 -0
- templates/go-fiber/skills/go-fiber/versions.json +16 -0
- templates/go-fiber/stack.yaml +59 -0
- templates/go-plain/scaffold/src/backend/go.mod +3 -0
- templates/go-plain/scaffold/src/backend/main.go +7 -0
- templates/go-plain/scaffold-boundary.yaml +22 -0
- templates/go-plain/stack.yaml +51 -0
- templates/java-plain/scaffold/src/backend/mvnw +302 -0
- templates/java-plain/scaffold/src/backend/pom.xml +41 -0
- templates/java-plain/scaffold/src/backend/src/main/java/com/example/app/Main.java +10 -0
- templates/java-plain/scaffold-boundary.yaml +23 -0
- templates/java-plain/stack.yaml +51 -0
- templates/laravel/rules/backend.md +20 -0
- templates/laravel/scaffold/docs/engineering/laravel-rules.md +28 -0
- templates/laravel/scaffold/docs/playbooks/laravel-service.md +28 -0
- templates/laravel/scaffold/src/backend/app/Exceptions/Handler.php +27 -0
- templates/laravel/scaffold/src/backend/app/Http/Controllers/HealthController.php +15 -0
- templates/laravel/scaffold/src/backend/app/Support/HealthStatus.php +14 -0
- templates/laravel/scaffold/src/backend/composer.json +24 -0
- templates/laravel/scaffold/src/backend/phpunit.xml +10 -0
- templates/laravel/scaffold/src/backend/public/index.php +8 -0
- templates/laravel/scaffold/src/backend/routes/api.php +7 -0
- templates/laravel/scaffold/src/backend/tests/Unit/HealthStatusTest.php +16 -0
- templates/laravel/scaffold-boundary.yaml +23 -0
- templates/laravel/skills/laravel/SKILL.md +56 -0
- templates/laravel/skills/laravel/references/anatomy.md +65 -0
- templates/laravel/stack.yaml +66 -0
- templates/meta/rules/graph-first.md +27 -0
- templates/meta/rules/hook-author.md +19 -0
- templates/meta/rules/mcp-tool-author.md +18 -0
- templates/meta/rules/meta-engineering.md +17 -0
- templates/meta/scaffold-boundary.yaml +55 -0
- templates/meta/skills/claude-sdk-integration/SKILL.md +163 -0
- templates/meta/skills/claude-sdk-integration/assets/sdk-checklist.md +27 -0
- templates/meta/skills/claude-sdk-integration/scripts/check_model_ids.py +97 -0
- templates/meta/skills/graph-os-authoring/SKILL.md +278 -0
- templates/meta/skills/graph-os-authoring/assets/graph-os-checklist.md +25 -0
- templates/meta/skills/graph-os-authoring/scripts/new_extractor.py +76 -0
- templates/meta/skills/hook-authoring/SKILL.md +292 -0
- templates/meta/skills/hook-authoring/assets/hook-checklist.md +30 -0
- templates/meta/skills/hook-authoring/scripts/new_hook.sh +75 -0
- templates/meta/skills/mcp-tool-authoring/SKILL.md +301 -0
- templates/meta/skills/mcp-tool-authoring/assets/mcp-tool-checklist.md +29 -0
- templates/meta/skills/mcp-tool-authoring/scripts/new_tool.py +74 -0
- templates/meta/skills/meta-engineering/SKILL.md +151 -0
- templates/meta/skills/meta-engineering/assets/meta-edit-checklist.md +28 -0
- templates/meta/skills/meta-engineering/scripts/which_layer.py +61 -0
- templates/meta/skills/python-meta-server/SKILL.md +162 -0
- templates/meta/skills/python-meta-server/assets/meta-server-checklist.md +28 -0
- templates/meta/skills/python-meta-server/scripts/check_envelope.py +91 -0
- templates/meta/skills/react-vite-hub/SKILL.md +140 -0
- templates/meta/skills/react-vite-hub/assets/hub-ui-checklist.md +23 -0
- templates/meta/skills/react-vite-hub/scripts/check_vite_env.py +73 -0
- templates/meta/stack.yaml +119 -0
- templates/nestjs/rules/backend.md +20 -0
- templates/nestjs/scaffold/docs/engineering/nestjs-rules.md +33 -0
- templates/nestjs/scaffold/docs/playbooks/nestjs-service.md +39 -0
- templates/nestjs/scaffold/src/backend/nest-cli.json +5 -0
- templates/nestjs/scaffold/src/backend/package.json +28 -0
- templates/nestjs/scaffold/src/backend/src/app.module.ts +9 -0
- templates/nestjs/scaffold/src/backend/src/common/all-exceptions.filter.ts +59 -0
- templates/nestjs/scaffold/src/backend/src/health/health.controller.ts +14 -0
- templates/nestjs/scaffold/src/backend/src/health/health.module.ts +10 -0
- templates/nestjs/scaffold/src/backend/src/health/health.service.spec.ts +21 -0
- templates/nestjs/scaffold/src/backend/src/health/health.service.ts +9 -0
- templates/nestjs/scaffold/src/backend/src/main.ts +26 -0
- templates/nestjs/scaffold/src/backend/tsconfig.json +16 -0
- templates/nestjs/scaffold/src/backend/vitest.config.ts +9 -0
- templates/nestjs/scaffold-boundary.yaml +25 -0
- templates/nestjs/skills/nestjs/SKILL.md +67 -0
- templates/nestjs/skills/nestjs/references/anatomy.md +65 -0
- templates/nestjs/stack.yaml +68 -0
- templates/nextjs/rules/frontend.md +18 -0
- templates/nextjs/scaffold/docs/design/00-index.md +23 -0
- templates/nextjs/scaffold/docs/design/colors-tokens.md +141 -0
- templates/nextjs/scaffold/docs/design/components-patterns.md +159 -0
- templates/nextjs/scaffold/docs/design/motion-accessibility.md +137 -0
- templates/nextjs/scaffold/docs/design/typography-spacing.md +107 -0
- templates/nextjs/scaffold/docs/engineering/accessibility-web.md +56 -0
- templates/nextjs/scaffold/docs/engineering/copywriting-standard.md +102 -0
- templates/nextjs/scaffold/docs/engineering/formatting-rules.md +89 -0
- templates/nextjs/scaffold/docs/engineering/frontend-rendering-rules.md +80 -0
- templates/nextjs/scaffold/docs/engineering/frontend-rules.md +183 -0
- templates/nextjs/scaffold/docs/engineering/i18n-policy.md +99 -0
- templates/nextjs/scaffold/docs/pages-content-spec/00-index.md +78 -0
- templates/nextjs/scaffold/docs/playbooks/content-seo.md +55 -0
- templates/nextjs/scaffold/docs/playbooks/docs-governance.md +51 -0
- templates/nextjs/scaffold/docs/playbooks/frontend-ui.md +63 -0
- templates/nextjs/scaffold/src/frontend/app/layout.tsx +14 -0
- templates/nextjs/scaffold/src/frontend/app/page.tsx +3 -0
- templates/nextjs/scaffold/src/frontend/eslint.config.js +17 -0
- templates/nextjs/scaffold/src/frontend/lib/greeting.test.ts +9 -0
- templates/nextjs/scaffold/src/frontend/lib/greeting.ts +3 -0
- templates/nextjs/scaffold/src/frontend/package.json +28 -0
- templates/nextjs/scaffold/src/frontend/tsconfig.json +18 -0
- templates/nextjs/scaffold/src/frontend/vitest.config.ts +10 -0
- templates/nextjs/scaffold-boundary.yaml +30 -0
- templates/nextjs/skills/nextjs-react/SKILL.md +485 -0
- templates/nextjs/skills/nextjs-react/references/anatomy.md +116 -0
- templates/nextjs/skills/nextjs-react/scripts/new_component.py +72 -0
- templates/nextjs/stack.yaml +81 -0
- templates/node-express/rules/backend.md +20 -0
- templates/node-express/scaffold/docs/engineering/express-rules.md +30 -0
- templates/node-express/scaffold/docs/playbooks/express-service.md +35 -0
- templates/node-express/scaffold/src/backend/package.json +24 -0
- templates/node-express/scaffold/src/backend/src/index.ts +17 -0
- templates/node-express/scaffold/src/backend/src/middleware/error-handler.ts +12 -0
- templates/node-express/scaffold/src/backend/src/routes/health.test.ts +33 -0
- templates/node-express/scaffold/src/backend/src/routes/health.ts +7 -0
- templates/node-express/scaffold/src/backend/tsconfig.json +15 -0
- templates/node-express/scaffold/src/backend/types/express-bootstrap.d.ts +21 -0
- templates/node-express/scaffold-boundary.yaml +25 -0
- templates/node-express/skills/node-express/SKILL.md +70 -0
- templates/node-express/skills/node-express/references/anatomy.md +63 -0
- templates/node-express/stack.yaml +63 -0
- templates/python/scaffold/docs/engineering/python-rules.md +27 -0
- templates/python/scaffold/docs/playbooks/python-library.md +35 -0
- templates/python/stack.yaml +60 -0
- templates/rails/rules/backend.md +10 -0
- templates/rails/scaffold/docs/engineering/rails-rules.md +33 -0
- templates/rails/scaffold/docs/playbooks/rails-service.md +42 -0
- templates/rails/scaffold/src/backend/Gemfile +12 -0
- templates/rails/scaffold/src/backend/app/controllers/application_controller.rb +26 -0
- templates/rails/scaffold/src/backend/app/controllers/health_controller.rb +6 -0
- templates/rails/scaffold/src/backend/app/models/health.rb +6 -0
- templates/rails/scaffold/src/backend/config/application.rb +12 -0
- templates/rails/scaffold/src/backend/config/boot.rb +3 -0
- templates/rails/scaffold/src/backend/config/routes.rb +4 -0
- templates/rails/scaffold/src/backend/config.ru +5 -0
- templates/rails/scaffold/src/backend/spec/rails_helper.rb +18 -0
- templates/rails/scaffold/src/backend/spec/requests/health_spec.rb +24 -0
- templates/rails/scaffold-boundary.yaml +25 -0
- templates/rails/skills/rails/SKILL.md +62 -0
- templates/rails/skills/rails/references/anatomy.md +71 -0
- templates/rails/stack.yaml +72 -0
- templates/react-native/rules/mobile.md +26 -0
- templates/react-native/scaffold/docs/engineering/accessibility-mobile.md +95 -0
- templates/react-native/scaffold/docs/engineering/mobile-rules.md +56 -0
- templates/react-native/scaffold/docs/engineering/offline-first.md +61 -0
- templates/react-native/scaffold/docs/playbooks/mobile-app.md +49 -0
- templates/react-native/scaffold/src/mobile/App.tsx +9 -0
- templates/react-native/scaffold/src/mobile/eslint.config.js +17 -0
- templates/react-native/scaffold/src/mobile/package.json +23 -0
- templates/react-native/scaffold/src/mobile/src/greeting.test.ts +9 -0
- templates/react-native/scaffold/src/mobile/src/greeting.ts +3 -0
- templates/react-native/scaffold/src/mobile/tsconfig.json +17 -0
- templates/react-native/scaffold/src/mobile/vitest.config.ts +10 -0
- templates/react-native/scaffold-boundary.yaml +30 -0
- templates/react-native/skills/react-native-mobile/SKILL.md +119 -0
- templates/react-native/skills/react-native-mobile/assets/rn-mobile-checklist.md +28 -0
- templates/react-native/skills/react-native-mobile/references/anatomy.md +140 -0
- templates/react-native/skills/react-native-mobile/references/rn-2026-practices.md +54 -0
- templates/react-native/skills/react-native-mobile/scripts/new_screen.py +73 -0
- templates/react-native/skills/react-native-mobile/versions.json +16 -0
- templates/react-native/skills/react-native-patterns/SKILL.md +512 -0
- templates/react-native/skills/react-native-patterns/assets/rn-review-checklist.md +26 -0
- templates/react-native/skills/react-native-patterns/references/anatomy.md +62 -0
- templates/react-native/skills/react-native-patterns/references/list-performance.md +70 -0
- templates/react-native/skills/react-native-patterns/scripts/scan_rn_perf.py +77 -0
- templates/react-native/stack.yaml +70 -0
- templates/ruby-plain/scaffold/src/backend/Gemfile +8 -0
- templates/ruby-plain/scaffold/src/backend/main.rb +3 -0
- templates/ruby-plain/scaffold-boundary.yaml +23 -0
- templates/ruby-plain/stack.yaml +50 -0
- templates/rust-axum/rules/backend.md +20 -0
- templates/rust-axum/scaffold/docs/engineering/rust-axum-rules.md +35 -0
- templates/rust-axum/scaffold/docs/playbooks/rust-axum-service.md +43 -0
- templates/rust-axum/scaffold/src/backend/Cargo.toml +20 -0
- templates/rust-axum/scaffold/src/backend/src/app.rs +12 -0
- templates/rust-axum/scaffold/src/backend/src/error.rs +48 -0
- templates/rust-axum/scaffold/src/backend/src/main.rs +24 -0
- templates/rust-axum/scaffold/src/backend/src/routes/health.rs +37 -0
- templates/rust-axum/scaffold/src/backend/src/routes/mod.rs +2 -0
- templates/rust-axum/scaffold-boundary.yaml +25 -0
- templates/rust-axum/skills/rust/SKILL.md +73 -0
- templates/rust-axum/skills/rust/references/anatomy.md +63 -0
- templates/rust-axum/stack.yaml +65 -0
- templates/rust-plain/scaffold/src/backend/Cargo.toml +6 -0
- templates/rust-plain/scaffold/src/backend/src/main.rs +3 -0
- templates/rust-plain/scaffold-boundary.yaml +23 -0
- templates/rust-plain/stack.yaml +51 -0
- templates/spring-boot/rules/backend.md +20 -0
- templates/spring-boot/scaffold/docs/engineering/spring-boot-rules.md +35 -0
- templates/spring-boot/scaffold/docs/playbooks/spring-boot-service.md +45 -0
- templates/spring-boot/scaffold/src/backend/mvnw +302 -0
- templates/spring-boot/scaffold/src/backend/pom.xml +69 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/Application.java +16 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/common/GlobalExceptionHandler.java +31 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthController.java +22 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthService.java +12 -0
- templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthStatus.java +4 -0
- templates/spring-boot/scaffold/src/backend/src/test/java/com/example/app/health/HealthServiceTest.java +15 -0
- templates/spring-boot/scaffold-boundary.yaml +26 -0
- templates/spring-boot/skills/spring-boot/SKILL.md +84 -0
- templates/spring-boot/skills/spring-boot/references/anatomy.md +63 -0
- templates/spring-boot/stack.yaml +65 -0
- templates/svelte-sveltekit/rules/frontend.md +20 -0
- templates/svelte-sveltekit/scaffold/docs/engineering/svelte-sveltekit-rules.md +37 -0
- templates/svelte-sveltekit/scaffold/docs/playbooks/svelte-sveltekit-app.md +36 -0
- templates/svelte-sveltekit/scaffold/src/frontend/package.json +22 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/app.html +12 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/hooks.server.ts +14 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/components/Greeting.svelte +6 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.test.ts +26 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.ts +4 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+layout.svelte +24 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.svelte +9 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/src/routes/health/+server.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/svelte.config.js +10 -0
- templates/svelte-sveltekit/scaffold/src/frontend/tsconfig.json +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/vite.config.ts +7 -0
- templates/svelte-sveltekit/scaffold/src/frontend/vitest.config.ts +11 -0
- templates/svelte-sveltekit/scaffold-boundary.yaml +29 -0
- templates/svelte-sveltekit/skills/svelte/SKILL.md +90 -0
- templates/svelte-sveltekit/skills/svelte/references/anatomy.md +62 -0
- templates/svelte-sveltekit/stack.yaml +70 -0
- templates/typescript-plain/scaffold/src/index.ts +3 -0
- templates/typescript-plain/scaffold/tsconfig.json +13 -0
- templates/typescript-plain/scaffold-boundary.yaml +22 -0
- templates/typescript-plain/stack.yaml +44 -0
- templates/vue-nuxt/rules/frontend.md +19 -0
- templates/vue-nuxt/scaffold/docs/engineering/nuxt-rules.md +30 -0
- templates/vue-nuxt/scaffold/docs/playbooks/nuxt-app.md +29 -0
- templates/vue-nuxt/scaffold/src/frontend/app.vue +3 -0
- templates/vue-nuxt/scaffold/src/frontend/nuxt.config.ts +11 -0
- templates/vue-nuxt/scaffold/src/frontend/package.json +20 -0
- templates/vue-nuxt/scaffold/src/frontend/pages/index.test.ts +19 -0
- templates/vue-nuxt/scaffold/src/frontend/pages/index.vue +11 -0
- templates/vue-nuxt/scaffold/src/frontend/vitest.config.ts +12 -0
- templates/vue-nuxt/scaffold-boundary.yaml +26 -0
- templates/vue-nuxt/skills/vue-nuxt/SKILL.md +57 -0
- templates/vue-nuxt/skills/vue-nuxt/references/anatomy.md +60 -0
- templates/vue-nuxt/stack.yaml +61 -0
- templates/wordpress/rules/backend.md +19 -0
- templates/wordpress/scaffold/docs/engineering/wordpress-rules.md +28 -0
- templates/wordpress/scaffold/docs/playbooks/wordpress-service.md +29 -0
- templates/wordpress/scaffold/src/backend/composer.json +17 -0
- templates/wordpress/scaffold/src/backend/phpcs.xml.dist +11 -0
- templates/wordpress/scaffold/src/backend/phpunit.xml +10 -0
- templates/wordpress/scaffold/src/backend/plugin/inc/health.php +8 -0
- templates/wordpress/scaffold/src/backend/plugin/plugin.php +28 -0
- templates/wordpress/scaffold/src/backend/tests/HealthStatusTest.php +15 -0
- templates/wordpress/scaffold/src/backend/theme/functions.php +18 -0
- templates/wordpress/scaffold/src/backend/theme/style.css +11 -0
- templates/wordpress/scaffold-boundary.yaml +23 -0
- templates/wordpress/skills/wordpress/SKILL.md +110 -0
- templates/wordpress/skills/wordpress/assets/wp-checklist.md +28 -0
- templates/wordpress/skills/wordpress/references/wp-development.md +75 -0
- templates/wordpress/skills/wordpress/references/wp-security.md +68 -0
- templates/wordpress/skills/wordpress/scripts/scan_wp_smells.py +91 -0
- templates/wordpress/skills/wordpress/versions.json +16 -0
- templates/wordpress/stack.yaml +60 -0
- thinking_os/__init__.py +1 -0
- thinking_os/_agent_markers.py +32 -0
- thinking_os/background.py +405 -0
- thinking_os/bootstrap_outcomes.py +200 -0
- thinking_os/budget.py +302 -0
- thinking_os/capture.py +495 -0
- thinking_os/cognition.py +516 -0
- thinking_os/cognition_schemas.py +517 -0
- thinking_os/compress.py +192 -0
- thinking_os/concepts.py +233 -0
- thinking_os/dashboard.py +159 -0
- thinking_os/database.py +2883 -0
- thinking_os/decay.py +393 -0
- thinking_os/digest.py +295 -0
- thinking_os/dispatcher.py +192 -0
- thinking_os/dispatcher_helpers.py +48 -0
- thinking_os/dispatchers/__init__.py +3 -0
- thinking_os/dispatchers/default.py +47 -0
- thinking_os/distill.py +192 -0
- thinking_os/doc_indexer.py +905 -0
- thinking_os/embeddings.py +943 -0
- thinking_os/formula_composer.py +556 -0
- thinking_os/gate_marker.py +75 -0
- thinking_os/graph.py +296 -0
- thinking_os/graph_indexer.py +360 -0
- thinking_os/health_check.py +517 -0
- thinking_os/impact.py +119 -0
- thinking_os/memory_gc.py +356 -0
- thinking_os/migrator_embeddings.py +318 -0
- thinking_os/precision.py +194 -0
- thinking_os/record_outcome.py +399 -0
- thinking_os/repair.py +105 -0
- thinking_os/retrieval_quality.py +239 -0
- thinking_os/roles_state.py +168 -0
- thinking_os/sanitizer.py +320 -0
- thinking_os/server.py +3160 -0
- thinking_os/session_enrich.py +272 -0
- thinking_os/session_observe_worker.py +111 -0
- thinking_os/session_startup.py +74 -0
- thinking_os/session_summary.py +245 -0
- thinking_os/task_analyzer.py +462 -0
- thinking_os/task_parser.py +342 -0
- thinking_os/task_sync.py +73 -0
- thinking_os/tools/__init__.py +6 -0
- thinking_os/tools/_shared.py +947 -0
- thinking_os/tools/cognition.py +1867 -0
- thinking_os/tools/docs.py +770 -0
- thinking_os/tools/learning.py +2078 -0
- thinking_os/tools/logs.py +79 -0
- thinking_os/tools/memory.py +840 -0
- thinking_os/tools/metrics.py +200 -0
- thinking_os/tools/retrieve.py +415 -0
- thinking_os/tools/routing.py +658 -0
- thinking_os/tools/tasks.py +449 -0
- thinking_os/tools/trajectory.py +181 -0
- thinking_os/tracing.py +235 -0
- web/__init__.py +5 -0
- web/_cache.py +118 -0
- web/_deps.py +56 -0
- web/_envelope.py +85 -0
- web/_project_context.py +140 -0
- web/chat_providers.py +108 -0
- web/init_jobs.py +216 -0
- web/routes/__init__.py +25 -0
- web/routes/_bounded_read.py +76 -0
- web/routes/board.py +1089 -0
- web/routes/cognition.py +1838 -0
- web/routes/config.py +635 -0
- web/routes/graph.py +513 -0
- web/routes/health.py +180 -0
- web/routes/hooks.py +288 -0
- web/routes/hub.py +1219 -0
- web/routes/logs.py +374 -0
- web/routes/metrics.py +43 -0
- web/routes/observability.py +400 -0
- web/routes/patterns.py +227 -0
- web/routes/presence.py +609 -0
- web/routes/roles.py +446 -0
- web/routes/scheduled.py +261 -0
- web/routes/search.py +238 -0
- web/routes/sessions.py +220 -0
- web/routes/settings.py +363 -0
- web/routes/stream.py +547 -0
- web/security.py +157 -0
- web/server.py +283 -0
core/hooks/cos-env.sh
ADDED
|
@@ -0,0 +1,1301 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Coding OS — Shared environment for hooks.
|
|
3
|
+
# Source this file at the top of every hook: source "$(dirname "$0")/cos-env.sh"
|
|
4
|
+
#
|
|
5
|
+
# Provides:
|
|
6
|
+
# COS_STATE_DIR — shared state directory root (default: .coding-os)
|
|
7
|
+
# Holds agent-agnostic artifacts: coding-os.db, .hooks.log,
|
|
8
|
+
# .agent marker, .capture-errors.log, .dogfood-reminded,
|
|
9
|
+
# installed-manifest.json, domain-config.json.
|
|
10
|
+
# COS_AGENT — which agent runtime invoked this hook (claude|codex|unknown)
|
|
11
|
+
# COS_AGENT_DIR — agent-private state directory = $COS_STATE_DIR/$COS_AGENT
|
|
12
|
+
# Holds per-agent state: session-id, .task-current,
|
|
13
|
+
# .thinking_os-gate, .zoom-checkpoint, .doc-anchor,
|
|
14
|
+
# .memory-check, .active-skill.
|
|
15
|
+
# Two agents running against the same project write to
|
|
16
|
+
# different dirs and never collide.
|
|
17
|
+
# COS_SESSION_FILE — path to session-id file (inside COS_AGENT_DIR)
|
|
18
|
+
# COS_DB_PATH — path to the project's coding-os SQLite DB (shared, in COS_STATE_DIR)
|
|
19
|
+
# COS_HOOK_LOG — path to the append-only hook activity log (shared,
|
|
20
|
+
# every line carries agent=X session=Y task=Z so downstream
|
|
21
|
+
# tools can filter by agent without a separate file)
|
|
22
|
+
#
|
|
23
|
+
# And the helper:
|
|
24
|
+
# cos_log_hook HOOK_NAME [ACTION] [DETAIL]
|
|
25
|
+
# Appends a line to $COS_HOOK_LOG so the user (or `cos hooks-log`) can
|
|
26
|
+
# see live hook activity. Fail-open: never errors a hook even if the
|
|
27
|
+
# write fails.
|
|
28
|
+
|
|
29
|
+
# Resolve $COS_STATE_DIR. Precedence (applied ONLY while it is the bare default):
|
|
30
|
+
# 1. explicit non-default COS_STATE_DIR → verbatim
|
|
31
|
+
# 2. $CLAUDE_PROJECT_DIR → $CLAUDE_PROJECT_DIR/.coding-os
|
|
32
|
+
# 3. $COS_PROJECT_ROOT → $COS_PROJECT_ROOT/.coding-os
|
|
33
|
+
# 4. upward marker-walk from $PWD → <root>/.coding-os
|
|
34
|
+
# 5. nothing found → relative .coding-os (legacy)
|
|
35
|
+
# Why a walk: hooks often run with cwd != repo root (e.g. `cd src/backend &&
|
|
36
|
+
# go build`); a bare relative ".coding-os" then lazily creates a STRAY
|
|
37
|
+
# .coding-os/ at the subdir (the nested-.coding-os bug). CLAUDE_PROJECT_DIR is
|
|
38
|
+
# unset under the VSCode native extension, so steps 3-4 are the runtime-
|
|
39
|
+
# independent fix. SPEC: docs/engineering/state-files.md § Project-root resolution.
|
|
40
|
+
COS_STATE_DIR="${COS_STATE_DIR:-.coding-os}"
|
|
41
|
+
|
|
42
|
+
# Pure-Bash project-root finder (no python3 spawn — this runs on every hook).
|
|
43
|
+
# DRIFT WARNING: mirrors src/core/thinking_os/database.py::_find_project_root_from_cwd
|
|
44
|
+
# (its _ROOT_MARKERS + .coding-os/-co-location requirement); tests/test_hooks.py
|
|
45
|
+
# asserts the two stay identical. The extra $HOME hard-stop here prevents binding
|
|
46
|
+
# the global hub at $HOME/.coding-os. Echoes the resolved root, or empty when none
|
|
47
|
+
# is found at/below the $HOME boundary.
|
|
48
|
+
_cos_find_project_root() {
|
|
49
|
+
local dir home_real first_state="" marker found_marker parent
|
|
50
|
+
# Rule 5: resolve symlinks (macOS /tmp -> /private/tmp) before the $HOME compare.
|
|
51
|
+
dir="$(cd -P "${PWD}" 2>/dev/null && pwd -P)" || dir=""
|
|
52
|
+
# Only an absolute, resolvable cwd is walkable. A relative/stale $PWD (cos-env.sh
|
|
53
|
+
# is SOURCED, so it inherits the parent's $PWD) would otherwise make dirname
|
|
54
|
+
# collapse to '.' — a fixpoint that spins forever. Bail to the relative default.
|
|
55
|
+
if [[ "$dir" != /* ]]; then
|
|
56
|
+
printf ''
|
|
57
|
+
return 0
|
|
58
|
+
fi
|
|
59
|
+
home_real="$(cd -P "${HOME:-/dev/null}" 2>/dev/null && pwd -P)" || home_real="${HOME:-}"
|
|
60
|
+
while [[ -n "$dir" && "$dir" != "/" ]]; do
|
|
61
|
+
# Never inspect/accept $HOME or above — $HOME/.coding-os is the global hub.
|
|
62
|
+
if [[ -n "$home_real" && "$dir" == "$home_real" ]]; then
|
|
63
|
+
break
|
|
64
|
+
fi
|
|
65
|
+
if [[ -d "$dir/.coding-os" ]]; then
|
|
66
|
+
if [[ -z "$first_state" ]]; then
|
|
67
|
+
first_state="$dir"
|
|
68
|
+
fi
|
|
69
|
+
# Prefer a .coding-os/ co-located with a root marker (skips a stray nested
|
|
70
|
+
# .coding-os/). Marker set mirrors database.py::_ROOT_MARKERS.
|
|
71
|
+
found_marker=""
|
|
72
|
+
for marker in .git .coding-os.yaml pyproject.toml package.json go.mod AGENTS.md; do
|
|
73
|
+
if [[ -e "$dir/$marker" ]]; then
|
|
74
|
+
found_marker=1
|
|
75
|
+
break
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
if [[ -n "$found_marker" ]]; then
|
|
79
|
+
printf '%s' "$dir"
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
parent="$(dirname "$dir")"
|
|
84
|
+
# dirname fixpoint ('/' or any path that cannot ascend) → stop, never spin.
|
|
85
|
+
if [[ "$parent" == "$dir" ]]; then
|
|
86
|
+
break
|
|
87
|
+
fi
|
|
88
|
+
dir="$parent"
|
|
89
|
+
done
|
|
90
|
+
# No marked root below the boundary → innermost bare .coding-os/ (never
|
|
91
|
+
# lazy-create at cwd), else empty (caller keeps the relative default).
|
|
92
|
+
printf '%s' "$first_state"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Resolve the MAIN repo root for a command running inside a git worktree, or
|
|
96
|
+
# empty. A linked worktree's --git-common-dir points at <main>/.git, so its
|
|
97
|
+
# parent is the main checkout. Used only when a worktree command lacks the
|
|
98
|
+
# COS_PROJECT_ROOT fast-path (TASK-515 / pr-mode). Always returns 0 — this file
|
|
99
|
+
# is SOURCED under the caller's `set -euo pipefail`, so a non-zero return from a
|
|
100
|
+
# command-substitution assignment would kill the hook (mirrors
|
|
101
|
+
# _cos_find_project_root's echo-empty-and-return-0 contract).
|
|
102
|
+
# SPEC: docs/playbooks/pr-workflow.md § 3.
|
|
103
|
+
_cos_main_repo_from_worktree() {
|
|
104
|
+
command -v git >/dev/null 2>&1 || { printf ''; return 0; }
|
|
105
|
+
local common
|
|
106
|
+
common="$(git rev-parse --git-common-dir 2>/dev/null)" || { printf ''; return 0; }
|
|
107
|
+
[[ -z "$common" ]] && { printf ''; return 0; }
|
|
108
|
+
common="$(cd -P "$common" 2>/dev/null && pwd -P)" || { printf ''; return 0; }
|
|
109
|
+
case "$common" in
|
|
110
|
+
*/.git) printf '%s' "${common%/.git}" ;;
|
|
111
|
+
*) printf '' ;;
|
|
112
|
+
esac
|
|
113
|
+
return 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Cheap worktree pre-check (NO git fork): a linked worktree's root carries `.git`
|
|
117
|
+
# as a FILE (a gitdir pointer), whereas a normal checkout has it as a directory.
|
|
118
|
+
# Walk up at most 40 levels from PWD looking for a `.git` file — returns 0 (found)
|
|
119
|
+
# so the git-fork probe below runs only for a plausible worktree, keeping normal
|
|
120
|
+
# trunk hooks fork-free. Bounded + dirname-fixpoint guarded against infinite walk.
|
|
121
|
+
_cos_has_dotgit_file() {
|
|
122
|
+
local dir="${PWD}" i=0
|
|
123
|
+
[[ "$dir" == /* ]] || return 1
|
|
124
|
+
while [[ -n "$dir" && "$dir" != "/" && $i -lt 40 ]]; do
|
|
125
|
+
[[ -f "$dir/.git" ]] && return 0
|
|
126
|
+
[[ -d "$dir/.git" ]] && return 1 # a real repo root — not a worktree
|
|
127
|
+
local parent; parent="$(dirname "$dir")"
|
|
128
|
+
[[ "$parent" == "$dir" ]] && break
|
|
129
|
+
dir="$parent"; i=$((i + 1))
|
|
130
|
+
done
|
|
131
|
+
return 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# _cos_helpers_dir — physical path to _helpers/, resolved through this file's
|
|
135
|
+
# own symlink chain. Consumers (and the meta-repo's own .claude/hooks/) symlink
|
|
136
|
+
# cos-env.sh but NOT _helpers/, so a $(dirname)-relative path lands in a dir with
|
|
137
|
+
# no helpers and the python fallbacks silently no-op — a fail-OPEN gap. readlink
|
|
138
|
+
# to the real file's dir finds _helpers/. Defined here (before the worktree-
|
|
139
|
+
# routing + pr-mode-enablement blocks below call it) so it is already in scope.
|
|
140
|
+
_cos_helpers_dir() {
|
|
141
|
+
local src dir
|
|
142
|
+
src="${BASH_SOURCE[0]}"
|
|
143
|
+
while [ -L "$src" ]; do
|
|
144
|
+
dir="$(cd -P "$(dirname "$src")" && pwd)"
|
|
145
|
+
src="$(readlink "$src")"
|
|
146
|
+
[[ "$src" != /* ]] && src="${dir}/${src}"
|
|
147
|
+
done
|
|
148
|
+
printf '%s' "$(cd -P "$(dirname "$src")" && pwd)/_helpers"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case "${COS_STATE_DIR}" in
|
|
152
|
+
.coding-os | ./.coding-os)
|
|
153
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
154
|
+
COS_STATE_DIR="${CLAUDE_PROJECT_DIR}/.coding-os"
|
|
155
|
+
elif [[ -n "${COS_PROJECT_ROOT:-}" ]]; then
|
|
156
|
+
COS_STATE_DIR="${COS_PROJECT_ROOT}/.coding-os"
|
|
157
|
+
else
|
|
158
|
+
_cos_root="$(_cos_find_project_root)"
|
|
159
|
+
if [[ -n "$_cos_root" ]]; then
|
|
160
|
+
COS_STATE_DIR="${_cos_root}/.coding-os"
|
|
161
|
+
fi
|
|
162
|
+
unset _cos_root
|
|
163
|
+
fi
|
|
164
|
+
;;
|
|
165
|
+
esac
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
# Worktree state routing + misroute guard (TASK-515 / pr-mode). A command
|
|
169
|
+
# inside a git worktree under ~/.coding-os/worktrees/ must resolve state to the
|
|
170
|
+
# MAIN repo, so every worktree of one repo shares its $COS_STATE_DIR (DB, board,
|
|
171
|
+
# presence, the test-governor .test-run.lock). Runs AFTER the case so it also
|
|
172
|
+
# corrects an INHERITED hub COS_STATE_DIR (e.g. a command spawned under
|
|
173
|
+
# `cos hub`, whose env carries COS_STATE_DIR=$HOME/.coding-os). Cheap raw-string
|
|
174
|
+
# gate first — only worktree cwds pay the resolution cost; the happy path
|
|
175
|
+
# (COS_PROJECT_ROOT exported by the cos pr dispatch) never spawns git.
|
|
176
|
+
# SPEC: docs/playbooks/pr-workflow.md § 3.
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
_cos_in_wt=""
|
|
179
|
+
case "${PWD}" in
|
|
180
|
+
*"/.coding-os/worktrees/"*) _cos_in_wt=1 ;;
|
|
181
|
+
esac
|
|
182
|
+
if [[ -z "$_cos_in_wt" && -n "${COS_WORKTREE_ROOT:-}" && "${PWD}" == "${COS_WORKTREE_ROOT}"/* ]]; then
|
|
183
|
+
_cos_in_wt=1
|
|
184
|
+
fi
|
|
185
|
+
# Catches a custom-location worktree the raw-string gates miss. Short-circuit ONLY
|
|
186
|
+
# on COS_PROJECT_ROOT (the authoritative fast-path) — the old CLAUDE_PROJECT_DIR
|
|
187
|
+
# arm gated this OFF for every Claude Code hook, exactly when the probe is needed,
|
|
188
|
+
# so a custom-location worktree misrouted state into itself (stray .coding-os in
|
|
189
|
+
# the agent's PR). Trunk fast-path preserved by a cheap `.git`-FILE pre-check: a
|
|
190
|
+
# linked worktree's root has `.git` as a file (gitdir pointer), a normal repo has
|
|
191
|
+
# it as a directory — only the former forks git below. (pr-workflow.md § 3, TASK-531.)
|
|
192
|
+
if [[ -z "$_cos_in_wt" && -z "${COS_PROJECT_ROOT:-}" ]] && _cos_has_dotgit_file; then
|
|
193
|
+
_cos_wt_main="$(_cos_main_repo_from_worktree)"
|
|
194
|
+
if [[ -n "$_cos_wt_main" ]]; then
|
|
195
|
+
_cos_wt_top="$(git rev-parse --show-toplevel 2>/dev/null)" || _cos_wt_top=""
|
|
196
|
+
if [[ -n "$_cos_wt_top" ]]; then
|
|
197
|
+
_cos_wt_top="$(cd -P "$_cos_wt_top" 2>/dev/null && pwd -P)" || _cos_wt_top=""
|
|
198
|
+
fi
|
|
199
|
+
if [[ -n "$_cos_wt_top" && "$_cos_wt_top" != "$_cos_wt_main" ]]; then
|
|
200
|
+
_cos_in_wt=1
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
unset _cos_wt_main _cos_wt_top
|
|
204
|
+
fi
|
|
205
|
+
if [[ -n "$_cos_in_wt" ]]; then
|
|
206
|
+
# In a worktree the project root is unambiguously the MAIN repo. COS_PROJECT_ROOT
|
|
207
|
+
# is authoritative and beats whatever the case produced (CLAUDE_PROJECT_DIR or
|
|
208
|
+
# the walk can point at the worktree itself or the global hub).
|
|
209
|
+
_cos_main=""
|
|
210
|
+
if [[ -n "${COS_PROJECT_ROOT:-}" ]]; then
|
|
211
|
+
_cos_main="${COS_PROJECT_ROOT}"
|
|
212
|
+
else
|
|
213
|
+
_cos_main="$(_cos_main_repo_from_worktree)"
|
|
214
|
+
fi
|
|
215
|
+
if [[ -n "$_cos_main" && -d "${_cos_main}/.coding-os" ]]; then
|
|
216
|
+
COS_STATE_DIR="${_cos_main}/.coding-os"
|
|
217
|
+
else
|
|
218
|
+
# main unresolvable: steer to a per-worktree QUARANTINE, never the global hub
|
|
219
|
+
# ($HOME/.coding-os) — binding there made every misrouted worktree share the
|
|
220
|
+
# hub's own state (DB/board/presence/locks) and collide with it and each other
|
|
221
|
+
# (TASK-582 regression of 1f8869b5). Also never a worktree-relative .coding-os —
|
|
222
|
+
# that stray gets committed into the agent's PR (TASK-531). The quarantine sits
|
|
223
|
+
# OFF the hub root and outside the checkout, keyed per worktree path; the
|
|
224
|
+
# misroute flag still fires so the operator sees the misconfig.
|
|
225
|
+
export COS_STATE_MISROUTE=1
|
|
226
|
+
printf 'cos-env: worktree state misroute — main repo unresolvable from %s; export COS_PROJECT_ROOT=<main-repo> for worktree commands (docs/playbooks/pr-workflow.md § 3).\n' "$PWD" >&2
|
|
227
|
+
_cos_home_real="$(cd -P "${HOME:-/dev/null}" 2>/dev/null && pwd -P)" || _cos_home_real="${HOME:-}"
|
|
228
|
+
if [[ -n "$_cos_home_real" ]]; then
|
|
229
|
+
_cos_wt_tag="$(printf '%s' "$PWD" | cksum 2>/dev/null | cut -d' ' -f1 2>/dev/null || true)"
|
|
230
|
+
COS_STATE_DIR="${_cos_home_real}/.coding-os-misrouted/${_cos_wt_tag:-orphan}"
|
|
231
|
+
unset _cos_wt_tag
|
|
232
|
+
fi
|
|
233
|
+
unset _cos_home_real
|
|
234
|
+
fi
|
|
235
|
+
unset _cos_main
|
|
236
|
+
fi
|
|
237
|
+
unset _cos_in_wt
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# pr-mode enablement (TASK-518) — when this project's Hub sets
|
|
241
|
+
# git_settings.enabled=true in hub-settings.json, export COS_GIT_WORKFLOW=pr
|
|
242
|
+
# (+ branch policy) into EVERY hook's process env. That env is the only place
|
|
243
|
+
# branch-guard / block-shared-tree-edit / the cos pr executor can read the mode
|
|
244
|
+
# (the inline per-command override is broken). Default OFF = byte-identical to
|
|
245
|
+
# trunk. A cheap grep-gate keeps trunk projects (no git_settings key) at one
|
|
246
|
+
# grep per hook; an explicitly-exported COS_GIT_WORKFLOW always wins.
|
|
247
|
+
# SPEC: docs/playbooks/pr-workflow.md § 1.
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
_cos_git_warn_once() {
|
|
250
|
+
# Debounced stderr warning (≤1/hour per condition per state dir) so a persistent
|
|
251
|
+
# corrupt/divergent git_settings surfaces without spamming every hook. Fail-open.
|
|
252
|
+
local key="$1" msg="$2" marker now last
|
|
253
|
+
marker="${COS_STATE_DIR}/.git-settings-warn-${key}"
|
|
254
|
+
now="$(date +%s 2>/dev/null || echo 0)"
|
|
255
|
+
if [[ -f "$marker" ]]; then
|
|
256
|
+
last="$(cat "$marker" 2>/dev/null || echo 0)"
|
|
257
|
+
if [[ $((now - last)) -lt 3600 ]]; then
|
|
258
|
+
return 0
|
|
259
|
+
fi
|
|
260
|
+
fi
|
|
261
|
+
printf '%s\n' "$msg" >&2 2>/dev/null || true
|
|
262
|
+
printf '%s' "$now" >"$marker" 2>/dev/null || true
|
|
263
|
+
}
|
|
264
|
+
if [[ -f "${COS_STATE_DIR}/hub-settings.json" ]] \
|
|
265
|
+
&& grep -q '"git_settings"' "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null; then
|
|
266
|
+
# jq fast-path, python3 fallback — a host WITHOUT jq must still honor an
|
|
267
|
+
# enabled project (the old `command -v jq` precondition silently downgraded
|
|
268
|
+
# it to trunk). Both emit one tab-separated line: enabled\tintegration\t
|
|
269
|
+
# protected(csv)\tautonomy. python3 is a hard dep of coding-os.
|
|
270
|
+
if command -v jq >/dev/null 2>&1; then
|
|
271
|
+
_cos_git_line="$(jq -r '[(.git_settings.enabled // false), (.git_settings.integration_branch // "main"), ((.git_settings.protected_branches // ["production"]) | join(",")), (.git_settings.autonomy_level // "draft")] | @tsv' "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null || true)"
|
|
272
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
273
|
+
_cos_git_line="$(python3 "$(_cos_helpers_dir)/git_settings_fields.py" "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null || true)"
|
|
274
|
+
else
|
|
275
|
+
_cos_git_line=""
|
|
276
|
+
fi
|
|
277
|
+
_cos_git_enabled="$(printf '%s' "$_cos_git_line" | cut -f1)"
|
|
278
|
+
if [[ -z "${COS_GIT_WORKFLOW:-}" && "$_cos_git_enabled" == "true" ]]; then
|
|
279
|
+
# Split declare/assign so shellcheck SC2155 stays clean (return-value masking).
|
|
280
|
+
COS_GIT_INTEGRATION_BRANCH="$(printf '%s' "$_cos_git_line" | cut -f2)"
|
|
281
|
+
COS_GIT_PROTECTED_BRANCHES="$(printf '%s' "$_cos_git_line" | cut -f3)"
|
|
282
|
+
COS_GIT_AUTONOMY="$(printf '%s' "$_cos_git_line" | cut -f4)"
|
|
283
|
+
export COS_GIT_WORKFLOW="pr" COS_GIT_INTEGRATION_BRANCH COS_GIT_PROTECTED_BRANCHES COS_GIT_AUTONOMY
|
|
284
|
+
elif [[ -z "${COS_GIT_WORKFLOW:-}" && -z "$_cos_git_line" ]]; then
|
|
285
|
+
# grep matched the git_settings key but no parser could read it (corrupt/torn
|
|
286
|
+
# JSON, or neither jq nor python3). The file EXISTS and names git_settings, so
|
|
287
|
+
# the operator opted into pr-mode; a torn write must NOT silently re-enable
|
|
288
|
+
# trunk (where a direct push to main is legal). Fail CLOSED to pr-mode — the
|
|
289
|
+
# stricter posture — with safe-default policy, and warn once. (TASK-587b)
|
|
290
|
+
COS_GIT_INTEGRATION_BRANCH="${COS_GIT_INTEGRATION_BRANCH:-main}"
|
|
291
|
+
COS_GIT_PROTECTED_BRANCHES="${COS_GIT_PROTECTED_BRANCHES:-production}"
|
|
292
|
+
COS_GIT_AUTONOMY="${COS_GIT_AUTONOMY:-draft}"
|
|
293
|
+
export COS_GIT_WORKFLOW="pr" COS_GIT_INTEGRATION_BRANCH COS_GIT_PROTECTED_BRANCHES COS_GIT_AUTONOMY
|
|
294
|
+
_cos_git_warn_once "unreadable" \
|
|
295
|
+
"cos-env: git_settings present but hub-settings.json could not be parsed — failing CLOSED to pr-mode (stricter). Fix the file or install jq/python3 to restore the chosen mode. (docs/playbooks/pr-workflow.md § 1)"
|
|
296
|
+
elif [[ "${COS_GIT_WORKFLOW:-}" == "trunk" && "$_cos_git_enabled" == "true" ]]; then
|
|
297
|
+
# An inherited explicit COS_GIT_WORKFLOW=trunk wins over the file by design;
|
|
298
|
+
# surface the self-downgrade of the Hub policy instead of applying it silently.
|
|
299
|
+
_cos_git_warn_once "divergence" \
|
|
300
|
+
"cos-env: git_settings enables pr-mode but an inherited COS_GIT_WORKFLOW=trunk overrides it — running TRUNK. Unset COS_GIT_WORKFLOW to honor the Hub setting. (docs/playbooks/pr-workflow.md § 1)"
|
|
301
|
+
fi
|
|
302
|
+
unset _cos_git_line _cos_git_enabled
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
# Default DB filename is `coding-os.db`. Legacy `thinking_os.db` is auto-renamed
|
|
306
|
+
# by src/core/thinking_os/database.py::migrate_legacy_db_filename() on first init_db()
|
|
307
|
+
# call after the upgrade — no shell-side migration needed.
|
|
308
|
+
COS_DB_PATH="${COS_DB_PATH:-${COS_STATE_DIR}/coding-os.db}"
|
|
309
|
+
COS_HOOK_LOG="${COS_HOOK_LOG:-${COS_STATE_DIR}/.hooks.log}"
|
|
310
|
+
# Block-only durable log. The main log above is flooded by high-volume 'fire'
|
|
311
|
+
# lines and capped, so rare 'block' events are evicted within hours — long
|
|
312
|
+
# before learn_extract mines them (the hook-block lesson signal was being lost).
|
|
313
|
+
# This log only ever receives 'block' lines, so it retains them across the
|
|
314
|
+
# extraction window. Mined by learning._mine_hook_block_lessons.
|
|
315
|
+
COS_HOOK_BLOCK_LOG="${COS_HOOK_BLOCK_LOG:-${COS_STATE_DIR}/.hook-blocks.log}"
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
# Per-project hook override (TASK-256) — a disabled NON-safety hook self-skips
|
|
319
|
+
# for THIS project. `$COS_STATE_DIR/disabled-hook-scripts` (one script basename
|
|
320
|
+
# per line) is the derived allowlist written by cli.project_overrides, which
|
|
321
|
+
# NEVER lists a safety-category hook — so a safety hook can never be disabled.
|
|
322
|
+
# Guarded + fail-open: when the file is absent (the common case) this costs a
|
|
323
|
+
# single stat; any error continues normally. Skip == `exit 0` (this file is
|
|
324
|
+
# SOURCED, so the exit ends the calling hook before its body runs). Set
|
|
325
|
+
# COS_SKIP_OVERRIDE_CHECK=1 to bypass (used by non-hook sourcers / tests).
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
if [[ -z "${COS_SKIP_OVERRIDE_CHECK:-}" && -f "${COS_STATE_DIR}/disabled-hook-scripts" ]]; then
|
|
328
|
+
_cos_self="$(basename "${BASH_SOURCE[1]:-${0:-}}" 2>/dev/null || echo "")"
|
|
329
|
+
if [[ -n "$_cos_self" ]] \
|
|
330
|
+
&& grep -qxF -- "$_cos_self" "${COS_STATE_DIR}/disabled-hook-scripts" 2>/dev/null; then
|
|
331
|
+
unset _cos_self
|
|
332
|
+
exit 0
|
|
333
|
+
fi
|
|
334
|
+
unset _cos_self
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Cap the log at 500 lines so `cos hooks-log` stays snappy and the file
|
|
338
|
+
# never blooms into a multi-MB artifact that would be tempting to open.
|
|
339
|
+
# Truncation runs when the file passes 2× the cap (=1000 lines) and keeps
|
|
340
|
+
# only the most recent $COS_HOOK_LOG_MAX_LINES.
|
|
341
|
+
COS_HOOK_LOG_MAX_LINES="${COS_HOOK_LOG_MAX_LINES:-500}"
|
|
342
|
+
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
# Agent runtime detection — which runtime invoked this hook?
|
|
345
|
+
# Priority: explicit COS_AGENT env > .coding-os/.agent (install marker) >
|
|
346
|
+
# Codex > Claude Code > unknown.
|
|
347
|
+
#
|
|
348
|
+
# Must run BEFORE COS_AGENT_DIR / COS_SESSION_FILE are computed.
|
|
349
|
+
#
|
|
350
|
+
# DRIFT WARNING: src/cli/board_commands.py::_detect_agent_runtime mirrors this
|
|
351
|
+
# same priority in Python for CLI-originated task transitions. If you add
|
|
352
|
+
# a new marker here, add it there (and the matching test in
|
|
353
|
+
# tests/test_board_commands_agent_detect.py).
|
|
354
|
+
# ---------------------------------------------------------------------------
|
|
355
|
+
if [[ -z "${COS_AGENT:-}" ]]; then
|
|
356
|
+
COS_AGENT=""
|
|
357
|
+
# Prefer runtime-specific env markers over persisted .agent.
|
|
358
|
+
# .agent is a fallback when the host runtime doesn't expose identity.
|
|
359
|
+
#
|
|
360
|
+
# Claude Code (VSCode/Antigravity variants) does NOT export CLAUDECODE=1
|
|
361
|
+
# to hook processes — only CLAUDE_CODE_ENTRYPOINT and CLAUDE_AGENT_SDK_VERSION
|
|
362
|
+
# make it through. Treat those as authoritative claude signals, otherwise
|
|
363
|
+
# hooks fired by Claude Code mis-tag themselves via the .agent fallback.
|
|
364
|
+
if [[ -n "${CODEX_SESSION_ID:-}" ]] || [[ -n "${CODEX_AGENT_DIR:-}" ]] || [[ -n "${CODEX_HOME:-}" ]]; then
|
|
365
|
+
COS_AGENT="codex"
|
|
366
|
+
elif [[ -n "${CLAUDECODE:-}" ]] || [[ -n "${CLAUDE_CODE_SSE_PORT:-}" ]] \
|
|
367
|
+
|| [[ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ]] \
|
|
368
|
+
|| [[ -n "${CLAUDE_AGENT_SDK_VERSION:-}" ]]; then
|
|
369
|
+
COS_AGENT="claude"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
if [[ -z "${COS_AGENT:-}" ]] && [[ -f "${COS_STATE_DIR}/.agent" ]]; then
|
|
373
|
+
COS_AGENT="$(head -c 32 "${COS_STATE_DIR}/.agent" 2>/dev/null | tr -d '[:space:]' || true)"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
# Last-resort Claude compatibility marker; only use this when no
|
|
377
|
+
# stronger signal existed.
|
|
378
|
+
if [[ -z "${COS_AGENT:-}" ]] && [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
379
|
+
COS_AGENT="claude"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
COS_AGENT="${COS_AGENT:-unknown}"
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
# Agent-private state dir. Every per-agent state file lives here so two
|
|
386
|
+
# DIFFERENT agents (e.g. Claude + Codex) attached to the same project never
|
|
387
|
+
# trample each other's state.
|
|
388
|
+
COS_AGENT_DIR="${COS_AGENT_DIR:-${COS_STATE_DIR}/${COS_AGENT}}"
|
|
389
|
+
|
|
390
|
+
# ---------------------------------------------------------------------------
|
|
391
|
+
# Panel identity — keys cognitive state per-PANEL of the SAME agent.
|
|
392
|
+
#
|
|
393
|
+
# Two panels of the same Claude/Codex instance on the same project
|
|
394
|
+
# share $COS_AGENT_DIR but get distinct $COS_PANEL_DIR subdirs so cognitive
|
|
395
|
+
# state files (.thinking_os-gate, .task-current, .active-skill, .doc-anchor,
|
|
396
|
+
# .memory-check, .zoom-checkpoint, .active-formula, .learn-suggestions and
|
|
397
|
+
# the dedupe markers) never collide.
|
|
398
|
+
#
|
|
399
|
+
# Resolution priority (highest first):
|
|
400
|
+
# 1. $COS_PANEL_ID env — explicit caller override (test harness, manual).
|
|
401
|
+
# 2. Adapter runtime stdin `session_id` — set later by
|
|
402
|
+
# cos_panel_upgrade_from_payload(). cos-env.sh CANNOT consume stdin
|
|
403
|
+
# itself (would steal it from the hook), so this slot is filled by
|
|
404
|
+
# the hook after it reads stdin.
|
|
405
|
+
# 3. Adapter-runtime env vars — declared per adapter in
|
|
406
|
+
# src/adapters/<id>/adapter.yaml::runtime_session_marker. Listed
|
|
407
|
+
# below in fall-through order so any adapter that exports its
|
|
408
|
+
# session id natively (Claude `CLAUDE_SESSION_ID`, Codex
|
|
409
|
+
# `CODEX_SESSION_ID`) is picked up. A new
|
|
410
|
+
# adapter adds its var here + to its adapter.yaml when it ships.
|
|
411
|
+
# 4. PPID-derived stable token — last resort. The hook's parent process
|
|
412
|
+
# is the agent runtime process for THIS panel; PIDs differ across
|
|
413
|
+
# panels of the same agent, so a hash over (PPID, agent) is a
|
|
414
|
+
# panel-stable identifier in the absence of a richer signal.
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
_cos_resolve_panel_id() {
|
|
417
|
+
local id="" v val ppid_val
|
|
418
|
+
if [[ -n "${COS_PANEL_ID:-}" ]]; then
|
|
419
|
+
printf '%s' "$COS_PANEL_ID"
|
|
420
|
+
return
|
|
421
|
+
fi
|
|
422
|
+
# CLAUDE_CODE_SESSION_ID is the real var Claude Code exports to every hook
|
|
423
|
+
# subprocess (stable per session, distinct per tab). It MUST lead — without
|
|
424
|
+
# it the resolver fell through to the ppid fallback (#4), which is ephemeral
|
|
425
|
+
# per tool call on Claude (fresh subprocess each fire) and scattered one
|
|
426
|
+
# session's state across dozens of panels. Keep the bare CLAUDE_SESSION_ID
|
|
427
|
+
# after it as a forward-compat alias. Stays in sync with each adapter.yaml
|
|
428
|
+
# ::runtime_session_marker.env_vars (per-agent SSOT for the var name).
|
|
429
|
+
# Real adapter session vars only (claude/codex) — matches the two
|
|
430
|
+
# src/adapters/*/adapter.yaml::runtime_session_marker.env_vars. Speculative
|
|
431
|
+
# GEMINI_*/ANTHROPIC_* removed: a future
|
|
432
|
+
# adapter adds its var here + to its adapter.yaml when it actually ships.
|
|
433
|
+
for v in CLAUDE_CODE_SESSION_ID CLAUDE_SESSION_ID CODEX_SESSION_ID; do
|
|
434
|
+
val="$(printenv "$v" 2>/dev/null || true)"
|
|
435
|
+
if [[ -n "$val" ]]; then
|
|
436
|
+
id="$val"
|
|
437
|
+
break
|
|
438
|
+
fi
|
|
439
|
+
done
|
|
440
|
+
if [[ -z "$id" ]]; then
|
|
441
|
+
ppid_val="${PPID:-$$}"
|
|
442
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
443
|
+
id="ppid-$(printf 'p%s-%s' "$ppid_val" "${COS_AGENT:-unknown}" | shasum -a 1 | cut -c1-8)"
|
|
444
|
+
else
|
|
445
|
+
id="ppid-${ppid_val}"
|
|
446
|
+
fi
|
|
447
|
+
fi
|
|
448
|
+
# FS-safe: keep [A-Za-z0-9._-] only, cap at 64 chars
|
|
449
|
+
printf '%s' "$id" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-64
|
|
450
|
+
}
|
|
451
|
+
COS_PANEL_ID="${COS_PANEL_ID:-$(_cos_resolve_panel_id)}"
|
|
452
|
+
COS_PANEL_DIR="${COS_PANEL_DIR:-${COS_AGENT_DIR}/panels/${COS_PANEL_ID}}"
|
|
453
|
+
COS_SESSION_FILE="${COS_PANEL_DIR}/session-id"
|
|
454
|
+
|
|
455
|
+
# Classify how the panel id was derived so SessionStart can surface a collision
|
|
456
|
+
# risk (cheap, no side effects — the loud warning lives in session-context.sh).
|
|
457
|
+
# A ppid-* prefix means NO runtime session-id var was exported and we fell back
|
|
458
|
+
# to a PPID hash; two panels sharing a PPID would then collide on one panel dir.
|
|
459
|
+
case "$COS_PANEL_ID" in
|
|
460
|
+
ppid-*) export COS_PANEL_ID_SOURCE="ppid" ;;
|
|
461
|
+
*) export COS_PANEL_ID_SOURCE="session" ;;
|
|
462
|
+
esac
|
|
463
|
+
|
|
464
|
+
# Per-panel state file allowlist (basenames). Single source of truth used by
|
|
465
|
+
# cos_state_path / write-state.sh / check-state.sh to route a write to the
|
|
466
|
+
# panel dir vs the shared per-agent dir. Adding a new per-panel marker:
|
|
467
|
+
# append its basename here; the writer/reader auto-route from then on.
|
|
468
|
+
#
|
|
469
|
+
# Explicitly NOT in this list (intentionally shared across panels of the
|
|
470
|
+
# same agent): .agent, .model, .swimlane, .last-verify*, .last-decay,
|
|
471
|
+
# .turn-activity.log, .overrides.json, .hooks.log, sessions/, traces/,
|
|
472
|
+
# locks/, heartbeat, coding-os.db. Rationale per file in
|
|
473
|
+
# docs/engineering/state-files.md.
|
|
474
|
+
# .task-mode and the discovery-debounce markers moved to per-panel — two
|
|
475
|
+
# panels of the same agent must not share banner verbosity or nudge debounce.
|
|
476
|
+
COS_PER_PANEL_FILES="${COS_PER_PANEL_FILES:-.thinking_os-gate .task-current .active-skill .doc-anchor .memory-check .zoom-checkpoint .active-formula .learn-suggestions .zoom-prompt-suggested .docs-first-nudged .roles-composed .roles .role .graph-call-seen .abandoned-task-warned .graph-empty-warning-shown .doc-anchor-override .memory-check-override .uv-heredoc-override .task-mode .last-discovery-reminder session-id}"
|
|
477
|
+
|
|
478
|
+
# Model signal for the routing / learning pipeline. Priority:
|
|
479
|
+
# 1. Caller already exported COS_AGENT_MODEL (test harness / explicit).
|
|
480
|
+
# 2. Agent runtime env — adapters expose their preferred model env so
|
|
481
|
+
# consumer projects with different adapters share one resolver.
|
|
482
|
+
# 3. $COS_AGENT_DIR/.model — latest hook-stdin model snapshot (written
|
|
483
|
+
# by session-context.sh when the agent sends `model` in the hook
|
|
484
|
+
# payload). This is the dynamic source of truth — Claude Code
|
|
485
|
+
# rotates models mid-session and the hook payload carries the
|
|
486
|
+
# current choice.
|
|
487
|
+
# Everything falls back to empty (NULL) — routing_weights tolerates it.
|
|
488
|
+
if [[ -z "${COS_AGENT_MODEL:-}" ]]; then
|
|
489
|
+
# printenv keeps this portable across bash / zsh (zsh lacks ${!var}).
|
|
490
|
+
for _cand in CLAUDE_CODE_MODEL CODEX_MODEL ANTHROPIC_MODEL \
|
|
491
|
+
OPENAI_MODEL; do
|
|
492
|
+
_val="$(printenv "$_cand" 2>/dev/null || true)"
|
|
493
|
+
if [[ -n "$_val" ]]; then
|
|
494
|
+
COS_AGENT_MODEL="$_val"
|
|
495
|
+
break
|
|
496
|
+
fi
|
|
497
|
+
done
|
|
498
|
+
unset _cand _val
|
|
499
|
+
fi
|
|
500
|
+
if [[ -z "${COS_AGENT_MODEL:-}" ]] && [[ -f "${COS_AGENT_DIR}/.model" ]]; then
|
|
501
|
+
COS_AGENT_MODEL="$(head -c 64 "${COS_AGENT_DIR}/.model" 2>/dev/null \
|
|
502
|
+
| tr -d '[:space:]' || true)"
|
|
503
|
+
fi
|
|
504
|
+
COS_AGENT_MODEL="${COS_AGENT_MODEL:-}"
|
|
505
|
+
|
|
506
|
+
# Hook latency SLI — stamp wall-clock entry time when the hook sources
|
|
507
|
+
# this file. cos_log_hook subtracts it to emit dt=<ms>, giving the hook layer
|
|
508
|
+
# a real per-invocation duration without a wrapper. $EPOCHREALTIME is bash 5+
|
|
509
|
+
# (seconds.microseconds, e.g. 1717545600.123456); we keep the raw string and
|
|
510
|
+
# do integer-µs math in cos_log_hook so there is no float/locale dependency.
|
|
511
|
+
COS_HOOK_T0="${COS_HOOK_T0:-${EPOCHREALTIME:-}}"
|
|
512
|
+
|
|
513
|
+
export COS_STATE_DIR COS_AGENT_DIR COS_PANEL_ID COS_PANEL_DIR COS_SESSION_FILE COS_DB_PATH COS_HOOK_LOG COS_HOOK_BLOCK_LOG COS_HOOK_LOG_MAX_LINES COS_AGENT COS_AGENT_MODEL COS_PER_PANEL_FILES COS_HOOK_T0
|
|
514
|
+
|
|
515
|
+
# Activity heartbeat — written on every hook invocation so GC can measure
|
|
516
|
+
# inactivity rather than session age. Per-panel so orphan GC can target
|
|
517
|
+
# dead panels' subdirs without disturbing live siblings.
|
|
518
|
+
mkdir -p "${COS_PANEL_DIR}" 2>/dev/null || true
|
|
519
|
+
date +%s > "${COS_PANEL_DIR}/heartbeat" 2>/dev/null || true
|
|
520
|
+
|
|
521
|
+
# ---------------------------------------------------------------------------
|
|
522
|
+
# Identity helpers — pure reads, cheap enough to call per hook invocation.
|
|
523
|
+
# ---------------------------------------------------------------------------
|
|
524
|
+
cos_current_session() {
|
|
525
|
+
# Echo current session id or 'none' if not available. Fail-open.
|
|
526
|
+
# Read STRICTLY from panel-private session-id (no AGENT_DIR fallback —
|
|
527
|
+
# cross-panel leak protection: a different panel's session-id parked
|
|
528
|
+
# at $COS_AGENT_DIR/session-id must not become "ours"). When no panel
|
|
529
|
+
# session-id file exists yet, fall back to $COS_PANEL_ID so write-
|
|
530
|
+
# state.sh can still stamp ownership.
|
|
531
|
+
local raw
|
|
532
|
+
if [[ -f "$COS_SESSION_FILE" ]]; then
|
|
533
|
+
raw="$(head -c 64 "$COS_SESSION_FILE" 2>/dev/null | tr -d '[:space:]' || true)"
|
|
534
|
+
if [[ -n "$raw" ]]; then
|
|
535
|
+
echo "$raw"
|
|
536
|
+
return
|
|
537
|
+
fi
|
|
538
|
+
fi
|
|
539
|
+
if [[ -n "${COS_PANEL_ID:-}" ]]; then
|
|
540
|
+
echo "$COS_PANEL_ID"
|
|
541
|
+
return
|
|
542
|
+
fi
|
|
543
|
+
echo "none"
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
cos_current_task() {
|
|
547
|
+
# Echo current task marker in agent-friendly form:
|
|
548
|
+
# 1. If the marker contains a TASK-### token, return just that (e.g.,
|
|
549
|
+
# "TASK-NNN") so the agent can jump straight to docs/tasks/TASK-NNN-*.md.
|
|
550
|
+
# 2. Else if the marker is a governance/docs-update/exploratory slug,
|
|
551
|
+
# return it truncated to 40 chars (keeps log lines readable).
|
|
552
|
+
# 3. Else return 'none'.
|
|
553
|
+
# File format: "<session_id> <task_name>" (single whitespace).
|
|
554
|
+
# STRICTLY panel-scoped — never read AGENT_DIR fossil (cross-panel leak).
|
|
555
|
+
local f="${COS_PANEL_DIR}/.task-current"
|
|
556
|
+
if [[ ! -f "$f" ]]; then
|
|
557
|
+
echo "none"
|
|
558
|
+
return
|
|
559
|
+
fi
|
|
560
|
+
|
|
561
|
+
local line raw
|
|
562
|
+
line="$(head -1 "$f" 2>/dev/null || true)"
|
|
563
|
+
# Second whitespace-separated token onward is the task name.
|
|
564
|
+
raw="$(echo "$line" | awk '{$1=""; sub(/^ /, ""); print}' 2>/dev/null || true)"
|
|
565
|
+
if [[ -z "$raw" ]]; then
|
|
566
|
+
echo "none"
|
|
567
|
+
return
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
# Prefer explicit TASK-### anywhere in the marker — shortest agent-useful form.
|
|
571
|
+
local match
|
|
572
|
+
match="$(echo "$raw" | grep -oE 'TASK-([A-Z][A-Z0-9]*-)?[0-9]+' | head -1 || true)"
|
|
573
|
+
if [[ -n "$match" ]]; then
|
|
574
|
+
echo "$match"
|
|
575
|
+
return
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# Fallback: truncate long governance slugs so log lines stay readable.
|
|
579
|
+
if [[ ${#raw} -gt 40 ]]; then
|
|
580
|
+
echo "${raw:0:37}..."
|
|
581
|
+
else
|
|
582
|
+
echo "$raw"
|
|
583
|
+
fi
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
# ---------------------------------------------------------------------------
|
|
587
|
+
# Hook activity logging — makes hook execution visible to the user.
|
|
588
|
+
# Format: [ISO-8601] [hook_name] [action] agent=X session=Y task=Z detail...
|
|
589
|
+
# Keeps human-readable shape so `grep`/`awk`/`tail -f` all still work; new
|
|
590
|
+
# identity fields are appended in front of the free-form detail so downstream
|
|
591
|
+
# filters (cos hooks-log --agent X) never need a JSON parser.
|
|
592
|
+
# ---------------------------------------------------------------------------
|
|
593
|
+
# ---------------------------------------------------------------------------
|
|
594
|
+
# cos_record_activity <category> <detail> — append a per-turn activity entry
|
|
595
|
+
# ---------------------------------------------------------------------------
|
|
596
|
+
# Logs operator-visible side effects (memory capture, graph reindex, task
|
|
597
|
+
# sync, skill invoke, worklog) for the per-turn pulse. session-context.sh
|
|
598
|
+
# reads this file on the next UserPromptSubmit, summarizes, then truncates.
|
|
599
|
+
# Claude Code does NOT render PostToolUse stdout, so this aggregation is
|
|
600
|
+
# the only reliable way to surface PostToolUse activity in the chat UI.
|
|
601
|
+
# Fail-open: never aborts the parent hook on logging failure.
|
|
602
|
+
# ---------------------------------------------------------------------------
|
|
603
|
+
cos_record_activity() {
|
|
604
|
+
local category="${1:-}"
|
|
605
|
+
local detail="${2:-}"
|
|
606
|
+
[[ -z "$category" ]] && return 0
|
|
607
|
+
[[ -z "${COS_AGENT_DIR:-}" ]] && return 0
|
|
608
|
+
local log ts size
|
|
609
|
+
log="$COS_AGENT_DIR/.turn-activity.log"
|
|
610
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")"
|
|
611
|
+
{
|
|
612
|
+
mkdir -p "$COS_AGENT_DIR" 2>/dev/null
|
|
613
|
+
printf '%s\t%s\t%s\n' "$category" "$detail" "$ts" >> "$log"
|
|
614
|
+
} 2>/dev/null || true
|
|
615
|
+
# Opportunistic truncation — keep last 500 lines once the file exceeds 50 KB.
|
|
616
|
+
size=$(stat -f%z "$log" 2>/dev/null || stat -c%s "$log" 2>/dev/null || echo 0)
|
|
617
|
+
if [[ "$size" -gt 50000 ]]; then
|
|
618
|
+
{
|
|
619
|
+
tail -n 500 "$log" > "${log}.tmp" && mv "${log}.tmp" "$log"
|
|
620
|
+
} 2>/dev/null || true
|
|
621
|
+
fi
|
|
622
|
+
return 0
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
# Echo a python interpreter that can import cos's installed extras (the `rag`
|
|
626
|
+
# group: sentence_transformers). Bare python3 usually lacks them; the `cos`
|
|
627
|
+
# entry point's shebang points at the venv that has them. Falls back to python3.
|
|
628
|
+
cos_resolve_python() {
|
|
629
|
+
local gc ac py
|
|
630
|
+
gc="$(command -v cos 2>/dev/null || true)"
|
|
631
|
+
if [[ -z "$gc" ]]; then command -v python3 2>/dev/null || true; return; fi
|
|
632
|
+
ac="$gc"
|
|
633
|
+
if [[ -L "$gc" ]]; then
|
|
634
|
+
ac="$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$gc" 2>/dev/null || echo "$gc")"
|
|
635
|
+
fi
|
|
636
|
+
py="$(awk 'NR==1{sub(/^#!/,""); sub(/[[:space:]].*$/,""); print; exit}' "$ac" 2>/dev/null || true)"
|
|
637
|
+
if [[ -z "$py" || ! -x "$py" ]]; then py="$(dirname "$ac")/python3"; fi
|
|
638
|
+
[[ -x "$py" ]] || py="$(command -v python3 2>/dev/null || true)"
|
|
639
|
+
printf '%s\n' "$py"
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
# Elapsed wall-time (ms) since this hook sourced cos-env.sh. Pure integer-µs
|
|
643
|
+
# math on the two $EPOCHREALTIME strings (sec.usec) — no float, no locale,
|
|
644
|
+
# no awk. Echoes a non-negative integer; empty when T0 / now is unavailable
|
|
645
|
+
# (older bash without $EPOCHREALTIME) so the dt= field is simply omitted.
|
|
646
|
+
cos_hook_elapsed_ms() {
|
|
647
|
+
local t0="${COS_HOOK_T0:-}" now="${EPOCHREALTIME:-}"
|
|
648
|
+
[[ -z "$t0" || -z "$now" ]] && return 0
|
|
649
|
+
# Split sec.usec; pad/truncate the fractional part to exactly 6 digits.
|
|
650
|
+
local t0_s="${t0%%.*}" t0_f="${t0#*.}" now_s="${now%%.*}" now_f="${now#*.}"
|
|
651
|
+
[[ "$t0_f" == "$t0" ]] && t0_f="0"
|
|
652
|
+
[[ "$now_f" == "$now" ]] && now_f="0"
|
|
653
|
+
t0_f="${t0_f}000000"; t0_f="${t0_f:0:6}"
|
|
654
|
+
now_f="${now_f}000000"; now_f="${now_f:0:6}"
|
|
655
|
+
# Strip leading zeros so bash does not read them as octal.
|
|
656
|
+
local t0_us=$((10#$t0_s * 1000000 + 10#$t0_f))
|
|
657
|
+
local now_us=$((10#$now_s * 1000000 + 10#$now_f))
|
|
658
|
+
local dms=$(((now_us - t0_us) / 1000))
|
|
659
|
+
((dms < 0)) && dms=0
|
|
660
|
+
printf '%s' "$dms"
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
cos_log_hook() {
|
|
664
|
+
local hook_name="${1:-unknown}"
|
|
665
|
+
local action="${2:-fire}"
|
|
666
|
+
shift 2 2>/dev/null || true
|
|
667
|
+
local detail="$*"
|
|
668
|
+
|
|
669
|
+
local ts agent session task model_bit dt_bit
|
|
670
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
671
|
+
agent="${COS_AGENT:-unknown}"
|
|
672
|
+
session="$(cos_current_session)"
|
|
673
|
+
task="$(cos_current_task)"
|
|
674
|
+
model_bit=""
|
|
675
|
+
if [[ -n "${COS_HOOK_RUNTIME_MODEL:-}" ]]; then
|
|
676
|
+
model_bit=" model=${COS_HOOK_RUNTIME_MODEL}"
|
|
677
|
+
fi
|
|
678
|
+
dt_bit=""
|
|
679
|
+
local _dms
|
|
680
|
+
_dms="$(cos_hook_elapsed_ms 2>/dev/null || true)"
|
|
681
|
+
if [[ -n "$_dms" ]]; then
|
|
682
|
+
dt_bit=" dt=${_dms}ms"
|
|
683
|
+
fi
|
|
684
|
+
|
|
685
|
+
# Fail-open: never let a logging error abort the hook.
|
|
686
|
+
{
|
|
687
|
+
mkdir -p "$(dirname "$COS_HOOK_LOG")" 2>/dev/null
|
|
688
|
+
local log_line
|
|
689
|
+
if [[ -n "$detail" ]]; then
|
|
690
|
+
log_line="[${ts}] [${hook_name}] [${action}] agent=${agent} session=${session} task=${task}${model_bit}${dt_bit} ${detail}"
|
|
691
|
+
else
|
|
692
|
+
log_line="[${ts}] [${hook_name}] [${action}] agent=${agent} session=${session} task=${task}${model_bit}${dt_bit}"
|
|
693
|
+
fi
|
|
694
|
+
echo "$log_line" >> "$COS_HOOK_LOG"
|
|
695
|
+
|
|
696
|
+
# Mirror 'block' lines into the block-only durable log so the high-volume
|
|
697
|
+
# main log's cap can't evict them before learn_extract mines them.
|
|
698
|
+
if [[ "$action" == "block" && -n "${COS_HOOK_BLOCK_LOG:-}" ]]; then
|
|
699
|
+
echo "$log_line" >> "$COS_HOOK_BLOCK_LOG"
|
|
700
|
+
local blk_lines
|
|
701
|
+
blk_lines=$(wc -l < "$COS_HOOK_BLOCK_LOG" 2>/dev/null || echo 0)
|
|
702
|
+
if [[ "$blk_lines" -gt $((COS_HOOK_LOG_MAX_LINES * 2)) ]]; then
|
|
703
|
+
tail -n "$COS_HOOK_LOG_MAX_LINES" "$COS_HOOK_BLOCK_LOG" > "${COS_HOOK_BLOCK_LOG}.tmp" \
|
|
704
|
+
&& mv "${COS_HOOK_BLOCK_LOG}.tmp" "$COS_HOOK_BLOCK_LOG"
|
|
705
|
+
fi
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
# F8 (TASK-447): make a BLOCK durable in the SQLite log_events store the
|
|
709
|
+
# logging_os sink owns so cos_log_query / error_sweep surface it — not just
|
|
710
|
+
# the text logs above. Reuse the shared shell→DB writer (DB-only here).
|
|
711
|
+
if [[ "$action" == "block" ]] && command -v python3 >/dev/null 2>&1; then
|
|
712
|
+
local _db_helper
|
|
713
|
+
_db_helper="$(_cos_helpers_dir)/cos_say_json.py"
|
|
714
|
+
if [[ -f "$_db_helper" ]]; then
|
|
715
|
+
python3 "$_db_helper" "$ts" "ERROR" "hook.${hook_name}" "${detail:-blocked}" \
|
|
716
|
+
"action=block session=${session} task=${task}" >/dev/null 2>&1 || true
|
|
717
|
+
fi
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
# Opportunistic truncation — keep only last N lines when file grows past 2x cap.
|
|
721
|
+
if [[ -f "$COS_HOOK_LOG" ]]; then
|
|
722
|
+
local lines
|
|
723
|
+
lines=$(wc -l < "$COS_HOOK_LOG" 2>/dev/null || echo 0)
|
|
724
|
+
if [[ "$lines" -gt $((COS_HOOK_LOG_MAX_LINES * 2)) ]]; then
|
|
725
|
+
tail -n "$COS_HOOK_LOG_MAX_LINES" "$COS_HOOK_LOG" > "${COS_HOOK_LOG}.tmp" \
|
|
726
|
+
&& mv "${COS_HOOK_LOG}.tmp" "$COS_HOOK_LOG"
|
|
727
|
+
fi
|
|
728
|
+
fi
|
|
729
|
+
} 2>/dev/null || true
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
# ---------------------------------------------------------------------------
|
|
734
|
+
# cos_read_stdin_bounded — drain stdin with a hard timeout
|
|
735
|
+
#
|
|
736
|
+
# WHY
|
|
737
|
+
# Hooks read JSON from stdin (PreToolUse / PostToolUse / Stop / etc.).
|
|
738
|
+
# When invoked from a terminal (`bash hook.sh` for testing), stdin is
|
|
739
|
+
# a tty and `cat` would block forever waiting for the user. perl's
|
|
740
|
+
# alarm() gives us a portable stdin read with a hard ceiling — bash's
|
|
741
|
+
# `read -t` doesn't slurp multi-line JSON, and `timeout(1)` is missing
|
|
742
|
+
# on macOS by default. Also: when the agent runtime sends nothing, we
|
|
743
|
+
# want to fall through to defaults rather than hang the hook.
|
|
744
|
+
#
|
|
745
|
+
# USAGE
|
|
746
|
+
# INPUT="$(cos_read_stdin_bounded 2)" # 2-second ceiling
|
|
747
|
+
#
|
|
748
|
+
# CONTRACT
|
|
749
|
+
# - Returns whatever bytes arrived on stdin (possibly empty).
|
|
750
|
+
# - On timeout: prints nothing, returns 0 (fail-open).
|
|
751
|
+
# - When stdin is a tty: returns immediately with empty output.
|
|
752
|
+
# ---------------------------------------------------------------------------
|
|
753
|
+
cos_read_stdin_bounded() {
|
|
754
|
+
local timeout_s="${1:-2}"
|
|
755
|
+
if [[ -t 0 ]]; then
|
|
756
|
+
return 0
|
|
757
|
+
fi
|
|
758
|
+
perl -e '
|
|
759
|
+
my $timeout = shift // 2;
|
|
760
|
+
eval {
|
|
761
|
+
local $SIG{ALRM} = sub { die "cos_stdin_timeout\n" };
|
|
762
|
+
alarm $timeout;
|
|
763
|
+
local $/;
|
|
764
|
+
my $data = <STDIN>;
|
|
765
|
+
alarm 0;
|
|
766
|
+
print $data if defined $data;
|
|
767
|
+
};
|
|
768
|
+
exit 0;
|
|
769
|
+
' "$timeout_s" 2>/dev/null || true
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
# ---------------------------------------------------------------------------
|
|
774
|
+
# cos_require_or_skip — fail-open when a required CLI binary is absent
|
|
775
|
+
#
|
|
776
|
+
# WHY
|
|
777
|
+
# Hooks hard-depend on `jq` and `python3`. Fresh-clone or minimal-CI
|
|
778
|
+
# runtimes may lack one. With `set -euo pipefail` a missing binary
|
|
779
|
+
# would kill the hook (exit ≠ 0 → BLOCK from the agent's POV). This
|
|
780
|
+
# helper logs a `skip reason=missing_dep` event and exits 0 so the
|
|
781
|
+
# hook degrades gracefully.
|
|
782
|
+
#
|
|
783
|
+
# USAGE (top of hook, after sourcing cos-env.sh)
|
|
784
|
+
# cos_require_or_skip jq block-secrets
|
|
785
|
+
#
|
|
786
|
+
# STRICT MODE
|
|
787
|
+
# COS_STRICT_DEPS=1 makes missing deps exit 2 (block) instead of 0.
|
|
788
|
+
# Opt-in for CI that demands a fully-set-up environment.
|
|
789
|
+
# ---------------------------------------------------------------------------
|
|
790
|
+
cos_require_or_skip() {
|
|
791
|
+
local bin="$1"
|
|
792
|
+
local hook_id="${2:-unknown-hook}"
|
|
793
|
+
if command -v "$bin" >/dev/null 2>&1; then
|
|
794
|
+
return 0
|
|
795
|
+
fi
|
|
796
|
+
cos_log_hook "$hook_id" "skip" "reason=missing_dep dep=$bin" 2>/dev/null || true
|
|
797
|
+
if [[ "${COS_STRICT_DEPS:-0}" == "1" ]]; then
|
|
798
|
+
echo "BLOCKED: hook $hook_id needs '$bin' on PATH (COS_STRICT_DEPS=1)" >&2
|
|
799
|
+
exit 2
|
|
800
|
+
fi
|
|
801
|
+
exit 0
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
# ---------------------------------------------------------------------------
|
|
806
|
+
# cos_require_parser <hook_id> — fail-CLOSED dep guard for harm gates.
|
|
807
|
+
#
|
|
808
|
+
# WHY
|
|
809
|
+
# An irreversible/integrity-harm gate (block-secrets, block-dangerous-
|
|
810
|
+
# commands, ...) must be able to read its decision input. The old
|
|
811
|
+
# `jq -r '...' || echo ""` returned empty when jq was missing → the gate
|
|
812
|
+
# exited 0 (allow) and silently disabled itself. observability-eye I8:
|
|
813
|
+
# a guard that cannot evaluate must DENY, not allow.
|
|
814
|
+
#
|
|
815
|
+
# CONTRACT
|
|
816
|
+
# Run at the TOP of a harm gate, OUTSIDE command-substitution (so the
|
|
817
|
+
# exit can actually block). Returns 0 when at least one JSON parser
|
|
818
|
+
# (jq OR python3) is on PATH; otherwise captures + exit 2 (block).
|
|
819
|
+
# python3 is a hard dep of coding-os, so the realistic degraded case
|
|
820
|
+
# (jq absent) still passes — cos_json_field falls back to python3.
|
|
821
|
+
#
|
|
822
|
+
# ESCAPE
|
|
823
|
+
# COS_ALLOW_MISSING_DEPS=1 lets a human bootstrap (install jq/python3)
|
|
824
|
+
# when both are absent.
|
|
825
|
+
# ---------------------------------------------------------------------------
|
|
826
|
+
cos_require_parser() {
|
|
827
|
+
local hook_id="${1:-unknown-hook}"
|
|
828
|
+
if command -v jq >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then
|
|
829
|
+
return 0
|
|
830
|
+
fi
|
|
831
|
+
if [[ "${COS_ALLOW_MISSING_DEPS:-0}" == "1" ]]; then
|
|
832
|
+
cos_log_hook "$hook_id" "skip" "reason=no_parser_override" 2>/dev/null || true
|
|
833
|
+
return 0
|
|
834
|
+
fi
|
|
835
|
+
cos_say error "hook.${hook_id}" "no JSON parser (jq/python3) on PATH — gate fails closed" 2>/dev/null || true
|
|
836
|
+
cos_log_hook "$hook_id" "block" "rule=no-parser-fail-closed" 2>/dev/null || true
|
|
837
|
+
echo "BLOCKED: $hook_id needs jq or python3 to evaluate safety — neither found. Install one, or set COS_ALLOW_MISSING_DEPS=1 to bootstrap." >&2
|
|
838
|
+
exit 2
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
# ---------------------------------------------------------------------------
|
|
843
|
+
# cos_json_field <path...> — extract first non-empty string field from a hook
|
|
844
|
+
# JSON envelope read on stdin. jq fast-path, python3 fallback.
|
|
845
|
+
#
|
|
846
|
+
# Echoes the value (empty if the field is genuinely absent). Does NOT block
|
|
847
|
+
# on a missing parser — that is cos_require_parser's job, which must run
|
|
848
|
+
# outside command-substitution. Replaces the `jq -r '...' || echo ""` idiom
|
|
849
|
+
# whose empty-on-missing-jq result drove the harm-gate fail-open class.
|
|
850
|
+
#
|
|
851
|
+
# USAGE
|
|
852
|
+
# TOOL=$(printf '%s' "$INPUT" | cos_json_field tool_name)
|
|
853
|
+
# CONTENT=$(printf '%s' "$INPUT" | cos_json_field tool_input.new_string tool_input.content)
|
|
854
|
+
# ---------------------------------------------------------------------------
|
|
855
|
+
cos_json_field() {
|
|
856
|
+
local input filter="" p
|
|
857
|
+
input="$(cat)"
|
|
858
|
+
if command -v jq >/dev/null 2>&1; then
|
|
859
|
+
for p in "$@"; do
|
|
860
|
+
[[ -n "$filter" ]] && filter+=" // "
|
|
861
|
+
filter+=".${p}"
|
|
862
|
+
done
|
|
863
|
+
filter+=" // empty"
|
|
864
|
+
printf '%s' "$input" | jq -r "$filter" 2>/dev/null || true
|
|
865
|
+
return 0
|
|
866
|
+
fi
|
|
867
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
868
|
+
printf '%s' "$input" \
|
|
869
|
+
| python3 "$(_cos_helpers_dir)/json_field.py" "$@" 2>/dev/null || true
|
|
870
|
+
return 0
|
|
871
|
+
fi
|
|
872
|
+
return 0
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# ---------------------------------------------------------------------------
|
|
877
|
+
# cos_sanity_check — verify project state before a hook does real work
|
|
878
|
+
#
|
|
879
|
+
# WHY
|
|
880
|
+
# Hooks assume the coding-os layout: $COS_STATE_DIR exists, the project
|
|
881
|
+
# has docs/ and core/, etc. When a hook fires in an unconfigured project
|
|
882
|
+
# (fresh clone, mid-cos-init, accidentally-running-elsewhere) the bare
|
|
883
|
+
# jq/python invocations emit cryptic errors. This helper centralises the
|
|
884
|
+
# "is the world sane?" probe so individual hooks can fail-open cleanly.
|
|
885
|
+
#
|
|
886
|
+
# USAGE
|
|
887
|
+
# cos_sanity_check <hook_id> [check1 check2 ...]
|
|
888
|
+
# - returns 0 if all named checks pass.
|
|
889
|
+
# - on failure: logs "skip reason=sanity_<check>" and exits 0 (fail-open).
|
|
890
|
+
#
|
|
891
|
+
# CHECKS
|
|
892
|
+
# state_dir — $COS_STATE_DIR exists and is writable.
|
|
893
|
+
# agent_dir — $COS_AGENT_DIR exists.
|
|
894
|
+
# db — $COS_DB_PATH exists.
|
|
895
|
+
# tasks_dir — docs/tasks/ exists relative to project root.
|
|
896
|
+
# board_os — src/core/board_os/ exists.
|
|
897
|
+
# git — .git/ exists somewhere up the tree.
|
|
898
|
+
#
|
|
899
|
+
# Default check set when no args: state_dir.
|
|
900
|
+
# ---------------------------------------------------------------------------
|
|
901
|
+
# ---------------------------------------------------------------------------
|
|
902
|
+
# cos_one_shot_override — unified one-shot bypass for blocking hooks
|
|
903
|
+
#
|
|
904
|
+
# WHY
|
|
905
|
+
# Several hooks (block-hardcoded-literals, block-uv-heredoc,
|
|
906
|
+
# enforce-doc-anchor, enforce-memory-check, enforce-template) historically
|
|
907
|
+
# consumed their own bypass dotfile in different directories with
|
|
908
|
+
# different name prefixes. That made overrides:
|
|
909
|
+
# - hard to discover (no central list of what bypasses are active)
|
|
910
|
+
# - impossible to audit (no trail of who/when/why)
|
|
911
|
+
# - error-prone (touching the wrong path silently fails)
|
|
912
|
+
#
|
|
913
|
+
# UNIFIED MODEL
|
|
914
|
+
# Single JSON registry: $COS_STATE_DIR/.overrides.json
|
|
915
|
+
# {
|
|
916
|
+
# "doc-anchor": {"reason": "spike", "ts": 1714234567, "agent": "claude"},
|
|
917
|
+
# "memory-check": {...}
|
|
918
|
+
# }
|
|
919
|
+
# Audit trail (append-only): $COS_STATE_DIR/.overrides.audit.log
|
|
920
|
+
#
|
|
921
|
+
# CONTRACT
|
|
922
|
+
# cos_one_shot_override <key> → returns 0 if override found and consumed,
|
|
923
|
+
# 1 otherwise. On hit, appends to audit log
|
|
924
|
+
# and removes the entry from the registry
|
|
925
|
+
# (or deletes the legacy dotfile).
|
|
926
|
+
#
|
|
927
|
+
# BACK-COMPAT
|
|
928
|
+
# Legacy paths remain consulted so existing tooling and docs keep working:
|
|
929
|
+
# $COS_AGENT_DIR/.<key>-override
|
|
930
|
+
# $COS_STATE_DIR/.<key>-override (literals only, historic)
|
|
931
|
+
# When a legacy file is found, it is consumed identically. New writes
|
|
932
|
+
# should prefer the unified registry, but legacy is permanently supported.
|
|
933
|
+
#
|
|
934
|
+
# SETTING AN OVERRIDE
|
|
935
|
+
# echo '{"doc-anchor": {"reason": "spike-XYZ"}}' > $COS_STATE_DIR/.overrides.json
|
|
936
|
+
# (or simply: touch $COS_AGENT_DIR/.doc-anchor-override)
|
|
937
|
+
# ---------------------------------------------------------------------------
|
|
938
|
+
cos_one_shot_override() {
|
|
939
|
+
local key="${1:-}"
|
|
940
|
+
[[ -z "$key" ]] && return 1
|
|
941
|
+
local reg="$COS_STATE_DIR/.overrides.json"
|
|
942
|
+
local audit="$COS_STATE_DIR/.overrides.audit.log"
|
|
943
|
+
local legacy_agent="$COS_AGENT_DIR/.${key}-override"
|
|
944
|
+
local legacy_shared="$COS_STATE_DIR/.${key}-override"
|
|
945
|
+
local ts
|
|
946
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
947
|
+
|
|
948
|
+
_audit() {
|
|
949
|
+
{
|
|
950
|
+
mkdir -p "$(dirname "$audit")" 2>/dev/null
|
|
951
|
+
echo "[${ts}] consumed key=${key} agent=${COS_AGENT:-unknown} source=$1" >> "$audit"
|
|
952
|
+
} 2>/dev/null || true
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
local consume_helper
|
|
956
|
+
consume_helper="$(_cos_helpers_dir)/consume_override.py"
|
|
957
|
+
if [[ -f "$reg" ]] && command -v python3 >/dev/null 2>&1 && [[ -f "$consume_helper" ]]; then
|
|
958
|
+
if python3 "$consume_helper" "$reg" "$key" >/dev/null 2>&1; then
|
|
959
|
+
_audit "registry"
|
|
960
|
+
return 0
|
|
961
|
+
fi
|
|
962
|
+
fi
|
|
963
|
+
|
|
964
|
+
# 2. Legacy per-agent dotfile.
|
|
965
|
+
if [[ -f "$legacy_agent" ]]; then
|
|
966
|
+
rm -f "$legacy_agent" 2>/dev/null || true
|
|
967
|
+
_audit "legacy_agent"
|
|
968
|
+
return 0
|
|
969
|
+
fi
|
|
970
|
+
|
|
971
|
+
# 3. Legacy shared dotfile (historic literals path).
|
|
972
|
+
if [[ -f "$legacy_shared" ]]; then
|
|
973
|
+
rm -f "$legacy_shared" 2>/dev/null || true
|
|
974
|
+
_audit "legacy_shared"
|
|
975
|
+
return 0
|
|
976
|
+
fi
|
|
977
|
+
|
|
978
|
+
return 1
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
# ---------------------------------------------------------------------------
|
|
982
|
+
# cos_say <level> <scope> <msg> [k=v ...] — shell parity for logging_os.
|
|
983
|
+
#
|
|
984
|
+
# Schema mirrors src/core/logging_os/ (docs/engineering/logging_os.md).
|
|
985
|
+
# Three renders auto-detected from env + isatty(stderr); fans out to stderr,
|
|
986
|
+
# $COS_LOG_FILE (always short text), and ${COS_LOG_FILE}.jsonl (always json).
|
|
987
|
+
# Fail-open: never aborts the parent hook on logging failure.
|
|
988
|
+
# ---------------------------------------------------------------------------
|
|
989
|
+
cos_say() {
|
|
990
|
+
local level scope msg
|
|
991
|
+
level="${1:-info}"
|
|
992
|
+
scope="${2:-shell.unknown}"
|
|
993
|
+
msg="${3:-}"
|
|
994
|
+
shift 3 2>/dev/null || true
|
|
995
|
+
level="$(echo "$level" | tr '[:lower:]' '[:upper:]')"
|
|
996
|
+
local level_value=20
|
|
997
|
+
case "$level" in
|
|
998
|
+
DEBUG) level_value=10 ;;
|
|
999
|
+
INFO) level_value=20 ;;
|
|
1000
|
+
OK) level_value=21 ;;
|
|
1001
|
+
WARN) level_value=30 ;;
|
|
1002
|
+
ERROR) level_value=40 ;;
|
|
1003
|
+
FATAL) level_value=50 ;;
|
|
1004
|
+
*) level="INFO"; level_value=20 ;;
|
|
1005
|
+
esac
|
|
1006
|
+
|
|
1007
|
+
local floor_name floor_value=20
|
|
1008
|
+
floor_name="$(echo "${COS_LOG_LEVEL:-info}" | tr '[:lower:]' '[:upper:]')"
|
|
1009
|
+
case "$floor_name" in
|
|
1010
|
+
DEBUG) floor_value=10 ;;
|
|
1011
|
+
INFO) floor_value=20 ;;
|
|
1012
|
+
OK) floor_value=21 ;;
|
|
1013
|
+
WARN) floor_value=30 ;;
|
|
1014
|
+
ERROR) floor_value=40 ;;
|
|
1015
|
+
FATAL) floor_value=50 ;;
|
|
1016
|
+
esac
|
|
1017
|
+
# Per-sink flooring (TASK-473): the console floor (COS_LOG_LEVEL) gates the
|
|
1018
|
+
# human sinks (stderr/text/jsonl); the durable floor (COS_LOG_DB_MIN_LEVEL,
|
|
1019
|
+
# re-applied inside cos_say_json.py) gates the log_events row independently.
|
|
1020
|
+
# Short-circuit only when the event clears NEITHER floor — flooring at the
|
|
1021
|
+
# console level alone here dropped a WARN before the durable store saw it.
|
|
1022
|
+
local db_floor_name db_floor_value=30
|
|
1023
|
+
db_floor_name="$(echo "${COS_LOG_DB_MIN_LEVEL:-WARN}" | tr '[:lower:]' '[:upper:]')"
|
|
1024
|
+
case "$db_floor_name" in
|
|
1025
|
+
DEBUG) db_floor_value=10 ;;
|
|
1026
|
+
INFO) db_floor_value=20 ;;
|
|
1027
|
+
OK) db_floor_value=21 ;;
|
|
1028
|
+
WARN) db_floor_value=30 ;;
|
|
1029
|
+
ERROR) db_floor_value=40 ;;
|
|
1030
|
+
FATAL) db_floor_value=50 ;;
|
|
1031
|
+
esac
|
|
1032
|
+
local min_floor="$floor_value"
|
|
1033
|
+
[[ "$db_floor_value" -lt "$min_floor" ]] && min_floor="$db_floor_value"
|
|
1034
|
+
if [[ "$level_value" -lt "$min_floor" ]]; then
|
|
1035
|
+
return 0
|
|
1036
|
+
fi
|
|
1037
|
+
local below_console=0
|
|
1038
|
+
[[ "$level_value" -lt "$floor_value" ]] && below_console=1
|
|
1039
|
+
|
|
1040
|
+
local kv=""
|
|
1041
|
+
if [[ $# -gt 0 ]]; then
|
|
1042
|
+
kv="$*"
|
|
1043
|
+
fi
|
|
1044
|
+
|
|
1045
|
+
local mode
|
|
1046
|
+
if [[ "${COS_LOG_JSON:-}" == "1" ]]; then
|
|
1047
|
+
mode="json"
|
|
1048
|
+
elif [[ "${COS_LOG_FORCE_PRETTY:-}" == "1" ]]; then
|
|
1049
|
+
mode="pretty"
|
|
1050
|
+
elif [[ -n "${NO_COLOR:-}" ]]; then
|
|
1051
|
+
mode="short"
|
|
1052
|
+
elif [[ -t 2 ]]; then
|
|
1053
|
+
mode="pretty"
|
|
1054
|
+
else
|
|
1055
|
+
mode="short"
|
|
1056
|
+
fi
|
|
1057
|
+
|
|
1058
|
+
local ts_iso ts_short
|
|
1059
|
+
ts_iso="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
|
|
1060
|
+
ts_short="$(date -u +%H:%M:%S 2>/dev/null || echo "")"
|
|
1061
|
+
|
|
1062
|
+
local emoji="" color="" reset=""
|
|
1063
|
+
case "$level" in
|
|
1064
|
+
DEBUG) emoji="🔍 "; color=$'\e[90m' ;;
|
|
1065
|
+
INFO) emoji="ℹ️ "; color=$'\e[36m' ;;
|
|
1066
|
+
OK) emoji="✅"; color=$'\e[32m' ;;
|
|
1067
|
+
WARN) emoji="⚠️ "; color=$'\e[33m' ;;
|
|
1068
|
+
ERROR) emoji="❌"; color=$'\e[31m' ;;
|
|
1069
|
+
FATAL) emoji="💀"; color=$'\e[1;31m' ;;
|
|
1070
|
+
esac
|
|
1071
|
+
reset=$'\e[0m'
|
|
1072
|
+
|
|
1073
|
+
local level_padded
|
|
1074
|
+
level_padded="$(printf '%-5s' "$level")"
|
|
1075
|
+
local short_line="${ts_short} ${level_padded} ${scope} ${msg}"
|
|
1076
|
+
[[ -n "$kv" ]] && short_line="${short_line} ${kv}"
|
|
1077
|
+
|
|
1078
|
+
local helper
|
|
1079
|
+
helper="$(_cos_helpers_dir)/cos_say_json.py"
|
|
1080
|
+
local json_line=""
|
|
1081
|
+
if command -v python3 >/dev/null 2>&1 && [[ -f "$helper" ]]; then
|
|
1082
|
+
json_line="$(python3 "$helper" "$ts_iso" "$level" "$scope" "$msg" "$kv" 2>/dev/null || true)"
|
|
1083
|
+
fi
|
|
1084
|
+
|
|
1085
|
+
local stderr_line
|
|
1086
|
+
case "$mode" in
|
|
1087
|
+
pretty)
|
|
1088
|
+
local pad
|
|
1089
|
+
pad="$(printf '%-20s' "$scope")"
|
|
1090
|
+
stderr_line="${emoji} ${ts_short} ${color}${level_padded}${reset} ${pad} ${msg}"
|
|
1091
|
+
[[ -n "$kv" ]] && stderr_line="${stderr_line} ${kv}"
|
|
1092
|
+
;;
|
|
1093
|
+
json)
|
|
1094
|
+
stderr_line="${json_line:-$short_line}"
|
|
1095
|
+
;;
|
|
1096
|
+
*)
|
|
1097
|
+
stderr_line="$short_line"
|
|
1098
|
+
;;
|
|
1099
|
+
esac
|
|
1100
|
+
|
|
1101
|
+
# Human sinks respect the console floor; the durable log_events row was already
|
|
1102
|
+
# written above by cos_say_json.py (gated by COS_LOG_DB_MIN_LEVEL) (TASK-473).
|
|
1103
|
+
if [[ "$below_console" -eq 0 ]]; then
|
|
1104
|
+
printf '%s\n' "$stderr_line" >&2 2>/dev/null || true
|
|
1105
|
+
fi
|
|
1106
|
+
|
|
1107
|
+
local log_file="${COS_LOG_FILE:-${COS_STATE_DIR}/.cos.log}"
|
|
1108
|
+
if [[ "$below_console" -eq 0 ]]; then
|
|
1109
|
+
{
|
|
1110
|
+
mkdir -p "$(dirname "$log_file")" 2>/dev/null
|
|
1111
|
+
printf '%s\n' "$short_line" >> "$log_file"
|
|
1112
|
+
} 2>/dev/null || true
|
|
1113
|
+
|
|
1114
|
+
if [[ -n "$json_line" ]]; then
|
|
1115
|
+
{
|
|
1116
|
+
printf '%s\n' "$json_line" >> "${log_file}.jsonl"
|
|
1117
|
+
} 2>/dev/null || true
|
|
1118
|
+
fi
|
|
1119
|
+
fi
|
|
1120
|
+
|
|
1121
|
+
return 0
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
cos_sanity_check() {
|
|
1125
|
+
local hook_id="${1:-unknown-hook}"
|
|
1126
|
+
shift 2>/dev/null || true
|
|
1127
|
+
local checks=("$@")
|
|
1128
|
+
if [[ ${#checks[@]} -eq 0 ]]; then
|
|
1129
|
+
checks=(state_dir)
|
|
1130
|
+
fi
|
|
1131
|
+
|
|
1132
|
+
local check fail
|
|
1133
|
+
for check in "${checks[@]}"; do
|
|
1134
|
+
fail=""
|
|
1135
|
+
case "$check" in
|
|
1136
|
+
state_dir)
|
|
1137
|
+
[[ -d "$COS_STATE_DIR" ]] || fail="state_dir_missing"
|
|
1138
|
+
;;
|
|
1139
|
+
agent_dir)
|
|
1140
|
+
[[ -d "$COS_AGENT_DIR" ]] || fail="agent_dir_missing"
|
|
1141
|
+
;;
|
|
1142
|
+
db)
|
|
1143
|
+
[[ -f "$COS_DB_PATH" ]] || fail="db_missing"
|
|
1144
|
+
;;
|
|
1145
|
+
tasks_dir)
|
|
1146
|
+
[[ -d "${CLAUDE_PROJECT_DIR:-.}/docs/tasks" ]] \
|
|
1147
|
+
|| [[ -d "./docs/tasks" ]] \
|
|
1148
|
+
|| fail="tasks_dir_missing"
|
|
1149
|
+
;;
|
|
1150
|
+
board_os)
|
|
1151
|
+
[[ -d "${CLAUDE_PROJECT_DIR:-.}/src/core/board_os" ]] \
|
|
1152
|
+
|| [[ -d "${CLAUDE_PROJECT_DIR:-.}/core/board_os" ]] \
|
|
1153
|
+
|| [[ -d "./src/core/board_os" ]] \
|
|
1154
|
+
|| [[ -d "./core/board_os" ]] \
|
|
1155
|
+
|| fail="board_os_missing"
|
|
1156
|
+
;;
|
|
1157
|
+
git)
|
|
1158
|
+
local d
|
|
1159
|
+
d="$(pwd)"
|
|
1160
|
+
local found=0
|
|
1161
|
+
while [[ "$d" != "/" ]]; do
|
|
1162
|
+
[[ -d "$d/.git" ]] && { found=1; break; }
|
|
1163
|
+
d="$(dirname "$d")"
|
|
1164
|
+
done
|
|
1165
|
+
[[ "$found" -eq 1 ]] || fail="git_missing"
|
|
1166
|
+
;;
|
|
1167
|
+
*)
|
|
1168
|
+
fail="unknown_check_${check}"
|
|
1169
|
+
;;
|
|
1170
|
+
esac
|
|
1171
|
+
if [[ -n "$fail" ]]; then
|
|
1172
|
+
cos_log_hook "$hook_id" "skip" "reason=sanity_${fail}" 2>/dev/null || true
|
|
1173
|
+
exit 0
|
|
1174
|
+
fi
|
|
1175
|
+
done
|
|
1176
|
+
return 0
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
# ---------------------------------------------------------------------------
|
|
1180
|
+
# cos_state_path <basename-or-path>
|
|
1181
|
+
#
|
|
1182
|
+
# Single-source-of-truth path resolver for state files. Centralises the
|
|
1183
|
+
# per-panel / per-agent routing decision so write-state.sh and check-state.sh
|
|
1184
|
+
# (and any future state-touching hook) never re-implement the allowlist
|
|
1185
|
+
# inline. Behavior:
|
|
1186
|
+
#
|
|
1187
|
+
# - bare basename ".thinking_os-gate" and basename is in
|
|
1188
|
+
# $COS_PER_PANEL_FILES → returns "$COS_PANEL_DIR/.thinking_os-gate"
|
|
1189
|
+
# - bare basename ".task-mode" (not in allowlist) → returns
|
|
1190
|
+
# "$COS_AGENT_DIR/.task-mode"
|
|
1191
|
+
# - path already containing a slash (absolute or relative): if its
|
|
1192
|
+
# basename is in the per-panel allowlist AND the parent dir resolves
|
|
1193
|
+
# to $COS_AGENT_DIR, redirect to $COS_PANEL_DIR. Otherwise return as
|
|
1194
|
+
# given (back-compat for callers that pass shared-dir paths like
|
|
1195
|
+
# "$COS_STATE_DIR/.capture-errors.log").
|
|
1196
|
+
# ---------------------------------------------------------------------------
|
|
1197
|
+
cos_state_path() {
|
|
1198
|
+
local arg="${1:?Usage: cos_state_path <basename-or-path>}"
|
|
1199
|
+
local base parent
|
|
1200
|
+
base="$(basename "$arg")"
|
|
1201
|
+
case " $COS_PER_PANEL_FILES " in
|
|
1202
|
+
*" $base "*)
|
|
1203
|
+
if [[ "$arg" == */* ]]; then
|
|
1204
|
+
parent="$(cd "$(dirname "$arg")" 2>/dev/null && pwd || dirname "$arg")"
|
|
1205
|
+
local agent_real
|
|
1206
|
+
agent_real="$(cd "$COS_AGENT_DIR" 2>/dev/null && pwd || echo "$COS_AGENT_DIR")"
|
|
1207
|
+
if [[ "$parent" == "$agent_real" ]]; then
|
|
1208
|
+
printf '%s/%s' "$COS_PANEL_DIR" "$base"
|
|
1209
|
+
return
|
|
1210
|
+
fi
|
|
1211
|
+
printf '%s' "$arg"
|
|
1212
|
+
return
|
|
1213
|
+
fi
|
|
1214
|
+
printf '%s/%s' "$COS_PANEL_DIR" "$base"
|
|
1215
|
+
;;
|
|
1216
|
+
*)
|
|
1217
|
+
if [[ "$arg" == /* ]] || [[ "$arg" == */* ]]; then
|
|
1218
|
+
printf '%s' "$arg"
|
|
1219
|
+
else
|
|
1220
|
+
printf '%s/%s' "$COS_AGENT_DIR" "$arg"
|
|
1221
|
+
fi
|
|
1222
|
+
;;
|
|
1223
|
+
esac
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
# ---------------------------------------------------------------------------
|
|
1227
|
+
# cos_panel_upgrade_from_payload <json-payload>
|
|
1228
|
+
#
|
|
1229
|
+
# Hook helper. After a hook reads stdin via cos_read_stdin_bounded, call
|
|
1230
|
+
# this with the payload to upgrade $COS_PANEL_ID from the agent runtime's
|
|
1231
|
+
# stdin session_id field — strongest panel signal available. Idempotent;
|
|
1232
|
+
# no-op when the payload lacks session_id, jq is missing, or the id is
|
|
1233
|
+
# already current.
|
|
1234
|
+
#
|
|
1235
|
+
# Why a separate helper instead of consuming stdin in cos-env.sh: stdin is
|
|
1236
|
+
# one-shot. cos-env.sh runs at hook source-time, before the hook itself
|
|
1237
|
+
# reads stdin. Stealing stdin from cos-env would break every hook.
|
|
1238
|
+
# ---------------------------------------------------------------------------
|
|
1239
|
+
cos_panel_upgrade_from_payload() {
|
|
1240
|
+
local payload="${1:-}"
|
|
1241
|
+
[[ -z "$payload" ]] && return 0
|
|
1242
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
1243
|
+
local sid
|
|
1244
|
+
sid="$(printf '%s' "$payload" | jq -r '.session_id // .sessionId // empty' 2>/dev/null || true)"
|
|
1245
|
+
[[ -z "$sid" ]] && return 0
|
|
1246
|
+
sid="$(printf '%s' "$sid" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-64)"
|
|
1247
|
+
[[ -z "$sid" ]] && return 0
|
|
1248
|
+
if [[ "$COS_PANEL_ID" != "$sid" ]]; then
|
|
1249
|
+
COS_PANEL_ID="$sid"
|
|
1250
|
+
COS_PANEL_DIR="${COS_AGENT_DIR}/panels/${COS_PANEL_ID}"
|
|
1251
|
+
COS_SESSION_FILE="${COS_PANEL_DIR}/session-id"
|
|
1252
|
+
export COS_PANEL_ID COS_PANEL_DIR COS_SESSION_FILE
|
|
1253
|
+
mkdir -p "$COS_PANEL_DIR" 2>/dev/null || true
|
|
1254
|
+
fi
|
|
1255
|
+
# Initialize the panel session-id file when missing. Without this, every
|
|
1256
|
+
# reader that goes through $COS_SESSION_FILE (the SSOT for "who am I")
|
|
1257
|
+
# sees an empty value, and the per-session ownership check rejects every
|
|
1258
|
+
# state file as un-owned — the cascade that surfaces as banner ses=? ·
|
|
1259
|
+
# task=none · gate=unset on hooks that only have agent-level legacy
|
|
1260
|
+
# state. SessionStart:startup writes a `ses-<agent>-<ts>-<rand>` id; for
|
|
1261
|
+
# resume/compact/user-prompt-submit (where startup never fires for this
|
|
1262
|
+
# panel), we mirror the agent-level session-id when present, else seed
|
|
1263
|
+
# with the panel id (stable across the conversation).
|
|
1264
|
+
if [[ ! -s "$COS_SESSION_FILE" ]]; then
|
|
1265
|
+
local seed=""
|
|
1266
|
+
if [[ -s "${COS_AGENT_DIR}/session-id" ]]; then
|
|
1267
|
+
seed="$(tr -d '\n\r' < "${COS_AGENT_DIR}/session-id" 2>/dev/null || true)"
|
|
1268
|
+
fi
|
|
1269
|
+
[[ -z "$seed" ]] && seed="ses-${COS_AGENT}-${COS_PANEL_ID}"
|
|
1270
|
+
local _tmp="${COS_SESSION_FILE}.tmp.$$"
|
|
1271
|
+
printf '%s\n' "$seed" > "$_tmp" 2>/dev/null \
|
|
1272
|
+
&& mv -f "$_tmp" "$COS_SESSION_FILE" 2>/dev/null \
|
|
1273
|
+
|| rm -f "$_tmp" 2>/dev/null
|
|
1274
|
+
fi
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
# ---------------------------------------------------------------------------
|
|
1278
|
+
# cos_task_bound_in_live_sibling <task-id>
|
|
1279
|
+
#
|
|
1280
|
+
# rc 0 when ANOTHER panel of this agent binds <task-id> via its .task-current
|
|
1281
|
+
# (last whitespace field — write-state.sh prefixes a session id) AND that
|
|
1282
|
+
# panel's heartbeat is fresh. A task actively driven in a sibling panel is
|
|
1283
|
+
# not stranded: nudging an idle panel about it invites a "rescue" park of
|
|
1284
|
+
# live work (the phantom NULL-reason in_progress→icebox reverts).
|
|
1285
|
+
# ---------------------------------------------------------------------------
|
|
1286
|
+
cos_task_bound_in_live_sibling() {
|
|
1287
|
+
local task="${1:-}" pd ptask hb now
|
|
1288
|
+
[[ -n "$task" && -d "${COS_AGENT_DIR:-}/panels" ]] || return 1
|
|
1289
|
+
now=$(date +%s)
|
|
1290
|
+
for pd in "${COS_AGENT_DIR}/panels"/*/; do
|
|
1291
|
+
[[ -d "$pd" ]] || continue
|
|
1292
|
+
[[ "${pd%/}" == "${COS_PANEL_DIR:-}" ]] && continue
|
|
1293
|
+
ptask="$(awk '{print $NF}' "${pd}.task-current" 2>/dev/null | head -1 || true)"
|
|
1294
|
+
[[ "$ptask" == "$task" ]] || continue
|
|
1295
|
+
hb=$(stat -c %Y "${pd}heartbeat" 2>/dev/null || stat -f %m "${pd}heartbeat" 2>/dev/null || echo 0)
|
|
1296
|
+
if [[ "$hb" -gt 0 && $((now - hb)) -lt "${COS_SIBLING_BIND_TTL:-3600}" ]]; then
|
|
1297
|
+
return 0
|
|
1298
|
+
fi
|
|
1299
|
+
done
|
|
1300
|
+
return 1
|
|
1301
|
+
}
|