capt-hook 12.76.1__tar.gz → 12.76.2__tar.gz
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.
- {capt_hook-12.76.1 → capt_hook-12.76.2}/PKG-INFO +1 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/.claude-plugin/plugin.json +1 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/bindings.py +62 -14
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/_sessions.py +3 -3
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/commands.py +4 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/deletions.py +17 -5
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/sessions.py +121 -30
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/performance/hooks/filesystem.py +11 -9
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/cmd.py +1 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/command_schema.py +1 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/pyproject.toml +1 -1
- {capt_hook-12.76.1 → capt_hook-12.76.2}/.gitignore +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/LICENSE +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/README.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/capt_hook_client/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/capt_hook_client/__main__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/capt_hook_client/client.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/.mcp.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/__init__.pyi +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/__main__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/app.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/ast_grep.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/fixes/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/fixes/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/fixes/hooks/_lib.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/fixes/hooks/scratch_writes.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/fixes/hooks/teammate_permissions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/_lib.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/comments.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/detours.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/docs.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/hook_files.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/models.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/plain_english.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/plans.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/fragments/deliverable_rubric.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/fragments/workflow_script_header.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/browser_delegation_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/implementation_spawn_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/inline_edit_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/prose_spawn_gate.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/prose_workflow_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/review_routing_spawn_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/review_routing_workflow_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/writing_docs_spawn_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/models/writing_docs_workflow_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/plain_english_rules.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts/tools/preload_nudge.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/prompts.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/questions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/review.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/tasks.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/tombstones.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/tooling.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/tools.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/pack.toml +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/go/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/go/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/go/hooks/testing.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/go/hooks/toolchain.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/gitdir/.graphite_repo_config +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/hooks/_lib.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/hooks/queue.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/graphite/hooks/vcs.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/performance/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/performance/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/performance/hooks/pipelining.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/python/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/python/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/python/hooks/style.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/python/hooks/testing.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/python/hooks/toolchain.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/hooks/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/hooks/steering.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/hooks/teammates.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/hooks/workarounds.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/steering/pack.toml +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/classifiers/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/classifiers/conductor.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/classifiers/droid.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/classifiers/lane.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/classifiers/native.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/cli.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/command_schemas.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/conditions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/context.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/contexts.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/context.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/decision_writer.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/logsink.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/registry.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/daemon/tree.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/decisions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/desktop/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/desktop/cli.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/desktop/client.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/dispatch.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/doc_conventions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/durable.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/events.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/faults.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/fields.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/file.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/guard_literal.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/guard_literal.sh +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/heartbeat.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/hook_lint.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/hooks/hooks.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/loader.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/log.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/mcp_server.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/packs/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/packs/manager.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/packs/plugins.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/commands.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/lint.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/llm.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/nudge.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/permissions.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/provision.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/rewrite.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/primitives/workflow.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/prompt.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/py.typed +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/announce.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/cli.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/dashboard.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/fix.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/formats.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/judge.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/notify.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/pipeline.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/prompts.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/repo.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/routing.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/scan.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/settings.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/snapshot.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/status.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/store.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/sync.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/review/triage.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/scripts/install-binary.sh +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/scripts/install-mcp.sh +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/session.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/settings.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/signals/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/signals/nlp.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/authoring-hooks/SKILL.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/authoring-hooks/references/capt-hook-api.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/authoring-hooks/references/pattern-catalog.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/authoring-hooks/references/pitfalls.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/authoring-hooks/references/testing-hooks.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/bootstrapping-hooks/SKILL.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/scanning-sessions/SKILL.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/scanning-sessions/references/pr-workflow.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/scanning-sessions/references/review-cli.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/translating-styleguides/SKILL.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/translating-styleguides/references/llm-rule-patterns.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/translating-styleguides/references/matcher-reference.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/skills/translating-styleguides/references/tier-rubric.md +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/client.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/contracts.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/review.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schema.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/config.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/context.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/host-request.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/host-response.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/request.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/response.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/schemas/tool_registry.schema.json +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/validation.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/snapshots/worker.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/state.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/style/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/style/ast_grep.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/style/matchers.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/style/scope.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/style/types.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/tasks.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/templates/example_hook.py.tmpl +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/fixtures.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/helpers.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/session_cache.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/snapshots.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/testing/types.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/transcripts.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/turn.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/types.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/update/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/update/cli.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/update/settings.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/update/updater.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/automode.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/caching.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/fs.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/globbing.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/http.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/model_cache.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/paths.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/payload.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/proc.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/reqenv.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/scratch.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/shell.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/text.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/userpath.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/util/vcs.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/__init__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/__main__.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/fail_open.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/protocol.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/runtime.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/worker/service.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/hatch_build.py +0 -0
- {capt_hook-12.76.1 → capt_hook-12.76.2}/internal/wireproto/guard.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "captain-hook",
|
|
3
|
-
"version": "12.76.
|
|
3
|
+
"version": "12.76.1",
|
|
4
4
|
"displayName": "Captain Hook",
|
|
5
5
|
"description": "Skills for the capt-hook declarative hook framework: bootstrap gates and nudges from a repo's conventions, and translate style guides into enforced StyleRules.",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import glob
|
|
3
4
|
import re
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from itertools import product
|
|
6
7
|
from math import prod
|
|
8
|
+
from pathlib import PurePath
|
|
7
9
|
from typing import TYPE_CHECKING
|
|
8
10
|
|
|
9
11
|
if TYPE_CHECKING:
|
|
@@ -49,6 +51,7 @@ PREFIXES = frozenset({"!", "time", "builtin", "command"})
|
|
|
49
51
|
DECLARERS = frozenset({"local", "declare", "typeset", "readonly", "let"})
|
|
50
52
|
READERS = frozenset({"read", "mapfile", "readarray", "getopts"})
|
|
51
53
|
UNREADABLE_BINDERS = frozenset({"eval", "source", ".", "case", "select", "coproc"})
|
|
54
|
+
MUTATORS = READERS | DECLARERS | UNREADABLE_BINDERS | frozenset({"unset", "export", "printf"})
|
|
52
55
|
UNREADABLE_IN_SUBSTITUTION = ("#", "<<", "case")
|
|
53
56
|
SHELL_OWNED = frozenset({"PWD", "OLDPWD", "RANDOM", "SECONDS", "LINENO", "REPLY", "IFS", "BASH_COMMAND", "PIPESTATUS"})
|
|
54
57
|
ENVIRONMENT = {"HOME": "~"}
|
|
@@ -338,7 +341,7 @@ class Scope:
|
|
|
338
341
|
def __post_init__(self) -> None:
|
|
339
342
|
self.mentioned = frozenset(match.group(1) for match in ASSIGNMENT.finditer(self.text))
|
|
340
343
|
self.mutated = frozenset(name for match in MUTATION.finditer(self.text) for name in match.groups() if name)
|
|
341
|
-
if "IFS" in self.mentioned:
|
|
344
|
+
if "IFS" in self.mentioned or "IFS" in self.mutated:
|
|
342
345
|
self.unreadable_from = 0
|
|
343
346
|
Walk(self).run()
|
|
344
347
|
|
|
@@ -412,7 +415,7 @@ class Scope:
|
|
|
412
415
|
)
|
|
413
416
|
if any(len(candidate) > CANDIDATE_LENGTH for candidate in candidates):
|
|
414
417
|
return Unresolved(None)
|
|
415
|
-
splittable = any(not
|
|
418
|
+
splittable = any(not part.quoted for part in parts if isinstance(part, Ref))
|
|
416
419
|
mixed = splittable and any(part.quoted for part in parts)
|
|
417
420
|
if mixed and any(candidate.split() != [candidate] for candidate in candidates):
|
|
418
421
|
return Unresolved(None)
|
|
@@ -425,6 +428,18 @@ def head_of(words: list[Token]) -> tuple[str, list[Token]]:
|
|
|
425
428
|
return (dequote(words[0].raw), words[1:]) if words else ("", [])
|
|
426
429
|
|
|
427
430
|
|
|
431
|
+
def literal_name(word: Token) -> str | None:
|
|
432
|
+
parts = segments(word.raw)
|
|
433
|
+
if parts is None or not all(isinstance(part, Literal) for part in parts):
|
|
434
|
+
return None
|
|
435
|
+
match = NAME.search("".join(part.text for part in parts))
|
|
436
|
+
return None if match is None else match.group()
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def program_name(candidate: str) -> str:
|
|
440
|
+
return PurePath(pieces[0] if (pieces := candidate.split()) else candidate).name
|
|
441
|
+
|
|
442
|
+
|
|
428
443
|
@dataclass
|
|
429
444
|
class Walk:
|
|
430
445
|
scope: Scope
|
|
@@ -483,15 +498,42 @@ class Walk:
|
|
|
483
498
|
self.scope.functions.append((start, token.end))
|
|
484
499
|
self.previous = token.raw
|
|
485
500
|
|
|
501
|
+
def bind(self, event: Event) -> None:
|
|
502
|
+
if event.name == "IFS":
|
|
503
|
+
self.scope.mark_unreadable(event.offset)
|
|
504
|
+
self.scope.events.append(event)
|
|
505
|
+
|
|
486
506
|
def bind_unreadable(self, word: Token) -> None:
|
|
487
507
|
if (match := ASSIGNMENT.match(word.raw)) is not None:
|
|
488
|
-
self.
|
|
508
|
+
self.bind(Event(word.start, match.group(1), Unknown(None)))
|
|
509
|
+
|
|
510
|
+
def computed_mutator(self, word: Token) -> bool:
|
|
511
|
+
parts = segments(word.raw)
|
|
512
|
+
if parts is not None and all(isinstance(part, Literal) for part in parts):
|
|
513
|
+
return False
|
|
514
|
+
match self.scope.resolve(word.raw, word.start):
|
|
515
|
+
case Resolved(candidates, _):
|
|
516
|
+
return any(program_name(candidate) in MUTATORS for candidate in candidates)
|
|
517
|
+
case Unresolved():
|
|
518
|
+
return True
|
|
489
519
|
|
|
490
520
|
def flush(self, *, sure: bool) -> None:
|
|
491
521
|
words, self.words = self.words, []
|
|
492
522
|
if not words:
|
|
493
523
|
return
|
|
494
524
|
head, rest = head_of(words)
|
|
525
|
+
head_word = next(
|
|
526
|
+
(
|
|
527
|
+
word
|
|
528
|
+
for word in words
|
|
529
|
+
if dequote(word.raw) not in PREFIXES
|
|
530
|
+
and ASSIGNMENT.match(word.raw) is None
|
|
531
|
+
and not word.raw.startswith("-")
|
|
532
|
+
),
|
|
533
|
+
None,
|
|
534
|
+
)
|
|
535
|
+
if head_word is not None and self.computed_mutator(head_word):
|
|
536
|
+
self.scope.mark_unreadable(head_word.start)
|
|
495
537
|
if head in BODY_OPENERS:
|
|
496
538
|
if head == "else" and self.stack and self.stack[-1][0] == "then":
|
|
497
539
|
self.stack.pop()
|
|
@@ -530,14 +572,18 @@ class Walk:
|
|
|
530
572
|
self.bind_assignment(word, sure=sure)
|
|
531
573
|
|
|
532
574
|
def bind_names(self, head: str, words: list[Token]) -> None:
|
|
533
|
-
names = [
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
575
|
+
names: list[str] = []
|
|
576
|
+
for word in words:
|
|
577
|
+
if word.raw.startswith("-"):
|
|
578
|
+
continue
|
|
579
|
+
if (name := literal_name(word)) is None:
|
|
580
|
+
self.scope.mark_unreadable(word.start)
|
|
581
|
+
return
|
|
582
|
+
names.append(name)
|
|
538
583
|
if head == "read" and not names:
|
|
539
584
|
names = ["REPLY"]
|
|
540
|
-
|
|
585
|
+
for name in names:
|
|
586
|
+
self.bind(Event(words[0].start if words else 0, name, Unknown(None)))
|
|
541
587
|
|
|
542
588
|
def bind_exports(self, words: list[Token], sure: bool) -> None:
|
|
543
589
|
options = any(word.raw.startswith("-") for word in words)
|
|
@@ -567,7 +613,7 @@ class Walk:
|
|
|
567
613
|
binding = Unknown(" ".join(candidates))
|
|
568
614
|
case Unresolved(source):
|
|
569
615
|
binding = Unknown(source)
|
|
570
|
-
self.
|
|
616
|
+
self.bind(Event(word.start, name, binding))
|
|
571
617
|
|
|
572
618
|
def bind_loop(self, words: list[Token]) -> None:
|
|
573
619
|
match [word.raw for word in words]:
|
|
@@ -575,7 +621,9 @@ class Walk:
|
|
|
575
621
|
candidates: list[str] = []
|
|
576
622
|
for item in words[2:]:
|
|
577
623
|
match self.scope.resolve(item.raw, item.start):
|
|
578
|
-
case Resolved(values, splittable) if not any(
|
|
624
|
+
case Resolved(values, splittable) if not any(
|
|
625
|
+
"{" in value or glob.has_magic(value) for value in values
|
|
626
|
+
):
|
|
579
627
|
candidates.extend(
|
|
580
628
|
piece for value in values for piece in (value.split() if splittable else [value])
|
|
581
629
|
)
|
|
@@ -588,16 +636,16 @@ class Walk:
|
|
|
588
636
|
else Unknown(" ".join(word.raw for word in words[2:]) or None)
|
|
589
637
|
)
|
|
590
638
|
self.pending_loop = len(self.scope.events)
|
|
591
|
-
self.
|
|
639
|
+
self.bind(Event(words[0].start, name, binding))
|
|
592
640
|
case [name, *_] if NAME.fullmatch(name) is not None:
|
|
593
|
-
self.
|
|
641
|
+
self.bind(Event(words[0].start, name, Unknown(None)))
|
|
594
642
|
case _:
|
|
595
643
|
pass
|
|
596
644
|
|
|
597
645
|
def close_loop(self, index: int, end: int) -> None:
|
|
598
646
|
event = self.scope.events[index]
|
|
599
647
|
self.scope.events[index] = Event(event.offset, event.name, event.binding, end)
|
|
600
|
-
self.
|
|
648
|
+
self.bind(Event(end, event.name, Unknown(source_of(event.binding))))
|
|
601
649
|
|
|
602
650
|
|
|
603
651
|
def printf_destination(words: list[Token]) -> list[Token]:
|
{capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/_sessions.py
RENAMED
|
@@ -341,10 +341,10 @@ def head_reason(call: Call) -> str | None:
|
|
|
341
341
|
f"BLOCKED: `{spelling}` runs a command named at run time (`{clip(head.raw, 40)}`), so the guard "
|
|
342
342
|
"cannot tell what runs. Spell the command name literally."
|
|
343
343
|
)
|
|
344
|
-
case Unresolved(source) if
|
|
344
|
+
case Unresolved(source) if source:
|
|
345
345
|
return (
|
|
346
|
-
f"BLOCKED: `{spelling}` runs a command named at run time (`{clip(head.raw, 40)}`) from text "
|
|
347
|
-
f"
|
|
346
|
+
f"BLOCKED: `{spelling}` runs a command named at run time (`{clip(head.raw, 40)}`) built from text "
|
|
347
|
+
f"the guard cannot read (`{clip(source, 40)}`). Spell the command name literally."
|
|
348
348
|
)
|
|
349
349
|
case _:
|
|
350
350
|
return None
|
{capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/commands.py
RENAMED
|
@@ -438,7 +438,10 @@ hook(
|
|
|
438
438
|
Input(command="cd / && find . -name '*.pyc' -exec rm {} +"): Block(),
|
|
439
439
|
Input(command="find / -newermt 2020-01-01 -exec echo {} +"): Block(),
|
|
440
440
|
Input(command="find . -name '*.pyc' -exec rm {} +", cwd="/Users/alice"): Block(),
|
|
441
|
-
Input(command=
|
|
441
|
+
Input(command='D=$(printf /); cd "$D" && find . -exec echo {} +'): Block(),
|
|
442
|
+
Input(command="find . -name '*.pyc' -exec rm {} +"): Block(),
|
|
443
|
+
Input(command=r"find . -name '*gil*.json' -exec cat {} \;", cwd="/tmp/reco-box-size"): Allow(),
|
|
444
|
+
Input(command=r"cd /tmp && find . -name '*gil*.json' -exec cat {} \;"): Allow(),
|
|
442
445
|
Input(command=r"find . -name '*.pyc' -exec rm {} \;", cwd="/repo"): Allow(),
|
|
443
446
|
Input(command="R=~/.claude/worktrees/monorepo/x; find $R -name targets.yaml -exec cat {} +"): Allow(),
|
|
444
447
|
Input(command="find . -name '*.pyc'"): Allow(),
|
{capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/deletions.py
RENAMED
|
@@ -22,6 +22,8 @@ if TYPE_CHECKING:
|
|
|
22
22
|
from captain_hook.cmd import Call
|
|
23
23
|
from captain_hook.events import PreToolUseEvent
|
|
24
24
|
|
|
25
|
+
SCRATCH_GLOB_LIMIT = 2000
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
@dataclass(frozen=True)
|
|
27
29
|
class Recoverable:
|
|
@@ -99,11 +101,18 @@ def check_target(
|
|
|
99
101
|
f"The glob '{token}' is too broad to verify before deleting. "
|
|
100
102
|
"Narrow the pattern or run `rm -r <dir>` on a specific directory."
|
|
101
103
|
)
|
|
102
|
-
if len(expansion) > GLOB_LIMIT
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if len(expansion) > GLOB_LIMIT:
|
|
105
|
+
if not scratch_glob(target):
|
|
106
|
+
return evt.block(
|
|
107
|
+
f"The glob '{token}' matches more than {GLOB_LIMIT} files. "
|
|
108
|
+
f"Run `ls {token}`, then narrow the pattern or run `rm -r <dir>` on a named directory."
|
|
109
|
+
)
|
|
110
|
+
expansion = target.expand(limit=SCRATCH_GLOB_LIMIT)
|
|
111
|
+
if expansion.exhausted or len(expansion) > SCRATCH_GLOB_LIMIT:
|
|
112
|
+
return evt.block(
|
|
113
|
+
f"The glob '{token}' matches more than {SCRATCH_GLOB_LIMIT} files, too many to check before "
|
|
114
|
+
"deleting. Run `rm -r <dir>` on a named scratch directory instead."
|
|
115
|
+
)
|
|
107
116
|
recovery: Recoverable | None = None
|
|
108
117
|
for match in expansion:
|
|
109
118
|
result = check_resolved(evt, Target(match, match, cwd), rewritable=rewritable)
|
|
@@ -164,6 +173,9 @@ ROOT_RM: Block = Block(pattern="filesystem root") if trash_binary() else Block(p
|
|
|
164
173
|
Input(command="rm -rf $d/*", cwd="/"): Block(pattern="built at run time"),
|
|
165
174
|
Input(command='X="/tmp/x /"; rm -rf $X', cwd="/"): ROOT_RM,
|
|
166
175
|
Input(command='X="/tmp/x /outside"; rm -rf $X', cwd="/"): RECOVERABLE_RM,
|
|
176
|
+
Input(command='X="/tmp/a /Users/yasyf /tmp/b"; rm -rf $X"$X"', cwd="/"): Block(pattern="repository"),
|
|
177
|
+
Input(command='read IFS <<<:; X="/tmp/x:/Users/yasyf"; rm -rf $X', cwd="/"): Block(pattern="repository"),
|
|
178
|
+
Input(command='IFS=:; X="/tmp/x:/Users/yasyf"; rm -rf $X', cwd="/"): Block(pattern="repository"),
|
|
167
179
|
Input(command="rm -f /tmp/../etc/*", cwd="/"): Block(),
|
|
168
180
|
Input(command="rm /outside/{a,b}", cwd="/"): Block(pattern="repository"),
|
|
169
181
|
Input(command="rm foo\\\nbar", cwd="/"): Block(pattern="repository"),
|
{capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/general/hooks/sessions.py
RENAMED
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
|
|
|
8
8
|
from cc_transcript.command import PAYLOAD_DEPTH_LIMIT
|
|
9
9
|
|
|
10
10
|
from captain_hook import Allow, Block
|
|
11
|
-
from captain_hook.bindings import
|
|
11
|
+
from captain_hook.bindings import Resolved
|
|
12
12
|
from captain_hook.builtin_packs.general.hooks._sessions import (
|
|
13
13
|
ARG_TOKEN_BREAK,
|
|
14
14
|
GUARDED_PROGRAMS,
|
|
@@ -34,18 +34,49 @@ from captain_hook.builtin_packs.general.hooks._sessions import (
|
|
|
34
34
|
spell,
|
|
35
35
|
unresolvable,
|
|
36
36
|
)
|
|
37
|
-
from captain_hook.command_schemas import KILL, LAUNCHCTL, ORCA, PMSET, RENICE,
|
|
37
|
+
from captain_hook.command_schemas import KILL, LAUNCHCTL, ORCA, PMSET, RENICE, SOFTWAREUPDATE, TMUX
|
|
38
38
|
from captain_hook.guard_literal import QUOTING_CHARS
|
|
39
39
|
from captain_hook.util.shell import SHELLS
|
|
40
40
|
|
|
41
41
|
if TYPE_CHECKING:
|
|
42
|
+
from cc_transcript.command import Word
|
|
43
|
+
|
|
42
44
|
from captain_hook import HookResult, ToolRewriteEvent
|
|
43
45
|
from captain_hook.builtin_packs.general.hooks._sessions import Facts, Unparsed
|
|
44
46
|
from captain_hook.cmd import Call
|
|
45
47
|
from captain_hook.command_schema import Arguments, Scalar
|
|
46
48
|
|
|
47
49
|
CRITERIA_PROGRAMS = frozenset({"pkill", "killall", "killall5", "skill", "snice", "kill-port"})
|
|
48
|
-
|
|
50
|
+
PKILL_CRITERIA_SHORT = frozenset("fxnoiluUgGtPacvF")
|
|
51
|
+
PKILL_CRITERIA_LONG = frozenset(
|
|
52
|
+
{
|
|
53
|
+
"--full",
|
|
54
|
+
"--exact",
|
|
55
|
+
"--newest",
|
|
56
|
+
"--oldest",
|
|
57
|
+
"--ignore-case",
|
|
58
|
+
"--inverse",
|
|
59
|
+
"--count",
|
|
60
|
+
"--list-name",
|
|
61
|
+
"--list-full",
|
|
62
|
+
"--euid",
|
|
63
|
+
"--uid",
|
|
64
|
+
"--group",
|
|
65
|
+
"--pgroup",
|
|
66
|
+
"--session",
|
|
67
|
+
"--terminal",
|
|
68
|
+
"--parent",
|
|
69
|
+
"--ns",
|
|
70
|
+
"--nslist",
|
|
71
|
+
"--pidfile",
|
|
72
|
+
"--logpidfile",
|
|
73
|
+
"--cgroup",
|
|
74
|
+
"--runstates",
|
|
75
|
+
"--ignore-ancestors",
|
|
76
|
+
"--require-handler",
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
ORCA_VM_RUN_FLAGS = frozenset({"--provision", "--connect"})
|
|
49
80
|
PACKAGE_RUNNERS = frozenset({"npx", "bunx", "pnpx", "pnpm", "yarn"})
|
|
50
81
|
CRITERIA_FIX = (
|
|
51
82
|
"Find the pid with `pgrep -fl <pattern>`, verify it with `ps -o pid,ppid,pgid,lstart,command -p <pid>`, and "
|
|
@@ -138,10 +169,25 @@ STDIN_SCRIPTS = frozenset({"-", "/dev/stdin", "/dev/fd/0"})
|
|
|
138
169
|
@guard(
|
|
139
170
|
tests={
|
|
140
171
|
guarded(command="read p; $p sleep; ls ~/.orca"): Block(pattern="named at run time"),
|
|
141
|
-
guarded(command="p=$(echo pkill); $p sleep"): Block(pattern="from text
|
|
172
|
+
guarded(command="p=$(echo pkill); $p sleep"): Block(pattern="built from text the guard cannot read"),
|
|
142
173
|
guarded(command='P=$(ls | head -1); [ -z "$P" ] && P=$(find ~ -name python); "$P" -c x'): Block(
|
|
143
|
-
pattern="
|
|
174
|
+
pattern="text the guard cannot read"
|
|
175
|
+
),
|
|
176
|
+
guarded(command="C=$(printf '%s%s' pk ill); \"$C\" claude; orca status"): Block(
|
|
177
|
+
pattern="text the guard cannot read"
|
|
178
|
+
),
|
|
179
|
+
guarded(command='X=echo; V=X; read "$V" <<<pkill; "$X" claude; ls ~/.orca'): Block(pattern="named at run time"),
|
|
180
|
+
guarded(command='X=echo; V=X; printf -v "$V" pkill; "$X" claude; ls ~/.orca'): Block(
|
|
181
|
+
pattern="named at run time"
|
|
182
|
+
),
|
|
183
|
+
guarded(command='X=echo; R=read; $R X <<<pkill; "$X" claude; ls ~/.orca'): Block(pattern="named at run time"),
|
|
184
|
+
guarded(command='X=echo; R=$(echo read); $R X <<<pkill; "$X" claude; ls ~/.orca'): Block(
|
|
185
|
+
pattern="named at run time"
|
|
144
186
|
),
|
|
187
|
+
guarded(command='for P in /usr/bin/[p]kill; do "$P" -f claude; done'): Block(
|
|
188
|
+
pattern="text the guard cannot read"
|
|
189
|
+
),
|
|
190
|
+
guarded(command='P=echo; sh -c "P=pkill; \\$P claude"'): Block(pattern="named at run time"),
|
|
145
191
|
guarded(command="$W kill 14575"): Block(pattern="`kill` among its arguments"),
|
|
146
192
|
guarded(command="W=timeout; $W 5 kill 14575"): Block(pattern="among its arguments"),
|
|
147
193
|
guarded(command="$SHELL -c 'kill 14575'"): Block(pattern="among its arguments"),
|
|
@@ -149,11 +195,11 @@ STDIN_SCRIPTS = frozenset({"-", "/dev/stdin", "/dev/fd/0"})
|
|
|
149
195
|
guarded(command='$W "$1"; ls ~/.orca'): Block(pattern='`"\\$1"` among its arguments'),
|
|
150
196
|
guarded(command="W='timeout 5'; $W pkill sleep"): Block(pattern="`pkill` among its arguments"),
|
|
151
197
|
guarded(command="f() { $CMD 14575; }; CMD=kill; f"): Block(pattern="named at run time"),
|
|
152
|
-
guarded(command="p=ls; (p=kill); $p 14575"): Block(pattern="
|
|
153
|
-
guarded(command="false && p=kill; $p 14575"): Block(pattern="
|
|
198
|
+
guarded(command="p=ls; (p=kill); $p 14575"): Block(pattern="text the guard cannot read"),
|
|
199
|
+
guarded(command="false && p=kill; $p 14575"): Block(pattern="text the guard cannot read"),
|
|
154
200
|
guarded(command="eval 'p=ls'; $p 14575; ls ~/.orca"): Block(pattern="named at run time"),
|
|
155
201
|
guarded(command='[ -z "$P" ] && kill -l'): Allow(),
|
|
156
|
-
guarded(command="false && p=ls; $p 14575; ls ~/.orca"):
|
|
202
|
+
guarded(command="false && p=ls; $p 14575; ls ~/.orca"): Block(pattern="text the guard cannot read"),
|
|
157
203
|
guarded(command="cat <<'EOF'\np=ls\nEOF\n$p kill 14575"): Block(),
|
|
158
204
|
guarded(command="{kill,14575}"): Block(pattern="expands at run time"),
|
|
159
205
|
guarded(command="{pkill,-x,sleep}"): Block(),
|
|
@@ -185,6 +231,13 @@ STDIN_SCRIPTS = frozenset({"-", "/dev/stdin", "/dev/fd/0"})
|
|
|
185
231
|
'skills/codex/1.14.0/skills/codex/../../bin/codex-ask"; "$CA" --lane review --schema findings - '
|
|
186
232
|
"<<'Q' 2>&1 | tail -40\nReview the builtin packs.\nQ"
|
|
187
233
|
)
|
|
234
|
+
): Block(pattern="text the guard cannot read"),
|
|
235
|
+
guarded(
|
|
236
|
+
command=(
|
|
237
|
+
'CA="/Users/yasyf/.claude/plugins/cache/skills/codex/1.14.0/skills/codex/../../bin/codex-ask"; '
|
|
238
|
+
'cd ~/.claude/worktrees/captain-hook/hook-lint-builtins && "$CA" --lane review --schema findings - '
|
|
239
|
+
"<<'Q' 2>&1 | tail -40\nReview the builtin packs.\nQ"
|
|
240
|
+
)
|
|
188
241
|
): Allow(),
|
|
189
242
|
guarded(
|
|
190
243
|
command=(
|
|
@@ -203,21 +256,21 @@ STDIN_SCRIPTS = frozenset({"-", "/dev/stdin", "/dev/fd/0"})
|
|
|
203
256
|
"cd /Users/yasyf/.orca/workspaces/monorepo/monorepo/v3-incident-api-1n80-fix-base && "
|
|
204
257
|
"B=$(realpath ../v3-incident-api-1n7z-fix-base/node_modules/.bin/biome); $B --version"
|
|
205
258
|
)
|
|
206
|
-
):
|
|
259
|
+
): Block(pattern="text the guard cannot read"),
|
|
207
260
|
guarded(command='timeout 20 "$CP" outcomes --no-doc --session d0bf 2>&1; ls ~/.orca'): Allow(),
|
|
208
261
|
guarded(
|
|
209
262
|
command=(
|
|
210
263
|
"B=$(ls /Users/yasyf/.orca/workspaces/monorepo/monorepo/sole/node_modules/.bin/biome 2>/dev/null || "
|
|
211
264
|
"command -v biome) && $B check infra/ci/src/verbs/release-stacks.ts 2>&1 | tail -25"
|
|
212
265
|
)
|
|
213
|
-
):
|
|
266
|
+
): Block(pattern="text the guard cannot read"),
|
|
214
267
|
guarded(
|
|
215
268
|
command=(
|
|
216
269
|
'for c in "bun infra/graph.ts" "bun infra/k8s.ts delivery"; do eval $c || echo "FAILED $c"; done; '
|
|
217
270
|
"ls ~/.orca"
|
|
218
271
|
)
|
|
219
272
|
): Allow(),
|
|
220
|
-
guarded(command="X=echo; X[0]=pkill; $X claude"): Block(pattern="
|
|
273
|
+
guarded(command="X=echo; X[0]=pkill; $X claude"): Block(pattern="text the guard cannot read"),
|
|
221
274
|
guarded(command="X=echo; printf -v X pkill; $X claude"): Block(pattern="named at run time"),
|
|
222
275
|
guarded(command="X=echo; read 'X' <<<pkill; $X claude"): Block(pattern="named at run time"),
|
|
223
276
|
guarded(command="X=echo; declare -n Y=X; Y=pkill; $X claude"): Block(pattern="named at run time"),
|
|
@@ -228,7 +281,7 @@ STDIN_SCRIPTS = frozenset({"-", "/dev/stdin", "/dev/fd/0"})
|
|
|
228
281
|
guarded(command="X='{p,q}kill'; $X -x claude"): Block(pattern="expands at run time"),
|
|
229
282
|
guarded(command='X=echo; function f() { "$X" claude; }; X=pkill; f'): Block(pattern="named at run time"),
|
|
230
283
|
guarded(command='X=echo; f() { echo }; "$X" claude; }; X=pkill; f'): Block(pattern="named at run time"),
|
|
231
|
-
guarded(command='for X in {echo,pkill}; do "$X" claude; done'): Block(pattern="
|
|
284
|
+
guarded(command='for X in {echo,pkill}; do "$X" claude; done'): Block(pattern="text the guard cannot read"),
|
|
232
285
|
guarded(command="PWD=/tmp; cd /; $PWD/kill 14575"): Block(pattern="named at run time"),
|
|
233
286
|
guarded(command='cd ~/.orca/workspaces/x && for g in a.ts b.ts; do eval "timeout 120 bun $g"; done'): Allow(),
|
|
234
287
|
guarded(command="~/bin/kill 14575"): Allow(),
|
|
@@ -261,10 +314,12 @@ def unread_reason(call: Call, arguments: Arguments) -> str:
|
|
|
261
314
|
return "it names an option or signal at run time"
|
|
262
315
|
|
|
263
316
|
|
|
264
|
-
def
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
317
|
+
def probe_pid(call: Call, word: Word, value: Scalar | None) -> bool:
|
|
318
|
+
if literal_pid(word, value) is not None:
|
|
319
|
+
return True
|
|
320
|
+
match call.resolve(word):
|
|
321
|
+
case Resolved(candidates, _):
|
|
322
|
+
return all(candidate.isdecimal() for candidate in candidates)
|
|
268
323
|
case _:
|
|
269
324
|
return False
|
|
270
325
|
|
|
@@ -286,10 +341,7 @@ def kill_verdict(call: Call, facts: Facts) -> str | None:
|
|
|
286
341
|
return None
|
|
287
342
|
targets = tuple(zip(arguments.words.get("targets", ()), values.get("targets", ()), strict=True))
|
|
288
343
|
probing = ("probe" in values and "signal" not in values) or values.get("signal") == ("0",)
|
|
289
|
-
if probing and (
|
|
290
|
-
all(literal_pid(word, value) is not None for word, value in targets)
|
|
291
|
-
or (len(targets) == 1 and (double_quoted(targets[0][0].raw) or one_variable(targets[0][0].raw)))
|
|
292
|
-
):
|
|
344
|
+
if probing and all(probe_pid(call, word, value) for word, value in targets):
|
|
293
345
|
return None
|
|
294
346
|
if not targets:
|
|
295
347
|
return deny("a wrapper supplies its targets") if call.wrappers else None
|
|
@@ -299,7 +351,7 @@ def kill_verdict(call: Call, facts: Facts) -> str | None:
|
|
|
299
351
|
if probing:
|
|
300
352
|
return (
|
|
301
353
|
f"BLOCKED: `{spelling}` cannot be verified: {describe_target(word, value)}, so the probe could expand "
|
|
302
|
-
|
|
354
|
+
"into a real signal. Pass the literal pid, or a variable the same line sets to one."
|
|
303
355
|
)
|
|
304
356
|
return deny(describe_target(word, value))
|
|
305
357
|
|
|
@@ -350,7 +402,17 @@ def kill_verdict(call: Call, facts: Facts) -> str | None:
|
|
|
350
402
|
guarded(command="kill -STOP 14575"): Block(),
|
|
351
403
|
guarded(command="kill -s KILL 14575"): Block(),
|
|
352
404
|
guarded(command="kill -0 -9 14575"): Block(),
|
|
405
|
+
guarded(command="kill -0 14575 -9"): Block(),
|
|
353
406
|
guarded(command="PID='-s 9 14575'; kill -0 $PID"): Block(pattern="an agent session"),
|
|
407
|
+
guarded(command='bash -c \'PID=$(printf "%s" "-s KILL 14575"); kill -0 $PID\''): Block(
|
|
408
|
+
pattern="probe could expand into a real signal"
|
|
409
|
+
),
|
|
410
|
+
guarded(command='kill -0 "$worker" 2>/dev/null || break'): Block(pattern="probe could expand"),
|
|
411
|
+
guarded(command="kill -0 ${PID}"): Block(pattern="probe could expand"),
|
|
412
|
+
guarded(command="PID=$(awk '{print $2}' /tmp/apply.pid); kill -0 $PID 2>/dev/null && echo alive"): Block(
|
|
413
|
+
pattern="probe could expand"
|
|
414
|
+
),
|
|
415
|
+
guarded(command="PID=14575x; kill -0 $PID"): Block(pattern="probe could expand"),
|
|
354
416
|
guarded(command="X=-9; kill $X $(printf 14575)"): Block(pattern="command substitution"),
|
|
355
417
|
guarded(command="kill -0 $pid 14575"): Block(pattern="probe could expand into a real signal"),
|
|
356
418
|
guarded(command="kill -0 $pid$x"): Block(pattern="probe could expand into a real signal"),
|
|
@@ -369,15 +431,9 @@ def kill_verdict(call: Call, facts: Facts) -> str | None:
|
|
|
369
431
|
guarded(tool="mcp__runner__exec", tool_input={"argv": ["kill", 14575]}): Block(pattern="an agent session"),
|
|
370
432
|
guarded(tool="mcp__x__exec", tool_input={"command": "kill 14575 \udc80"}): Block(),
|
|
371
433
|
guarded(command="kill -0 14575"): Allow(),
|
|
372
|
-
guarded(command='kill -0 "$worker" 2>/dev/null || break'): Allow(),
|
|
373
|
-
guarded(
|
|
374
|
-
command=(
|
|
375
|
-
"LOG=~/.claude/scratch/release-v3/sandsql-aig-migration/apply-a9922c25.log; PID=$(awk '{print $2}' "
|
|
376
|
-
"~/.claude/scratch/release-v3/sandsql-aig-migration/apply.pid); kill -0 $PID 2>/dev/null && echo alive"
|
|
377
|
-
)
|
|
378
|
-
): Allow(),
|
|
379
|
-
guarded(command="kill -0 ${PID}"): Allow(),
|
|
380
434
|
guarded(command="PID=31337; kill -0 $PID"): Allow(),
|
|
435
|
+
guarded(command='PID=31337; kill -0 "$PID" 2>/dev/null || break'): Allow(),
|
|
436
|
+
guarded(command="for p in 31337 31338; do kill -0 $p; done"): Allow(),
|
|
381
437
|
guarded(command="kill -s 0 14575"): Allow(),
|
|
382
438
|
guarded(command="kill -l"): Allow(),
|
|
383
439
|
guarded(command="kill -l 9"): Allow(),
|
|
@@ -437,7 +493,17 @@ def renice_unverified_pid(evt: ToolRewriteEvent) -> HookResult | None:
|
|
|
437
493
|
|
|
438
494
|
|
|
439
495
|
def probes_only(call: Call) -> bool:
|
|
440
|
-
|
|
496
|
+
if call.args[:1] != ("-0",):
|
|
497
|
+
return False
|
|
498
|
+
for arg in call.args[1:]:
|
|
499
|
+
if not arg.startswith("-"):
|
|
500
|
+
continue
|
|
501
|
+
flag = arg.partition("=")[0]
|
|
502
|
+
if flag in PKILL_CRITERIA_LONG:
|
|
503
|
+
continue
|
|
504
|
+
if flag.startswith("--") or len(flag) < 2 or not set(flag[1:]) <= PKILL_CRITERIA_SHORT:
|
|
505
|
+
return False
|
|
506
|
+
return True
|
|
441
507
|
|
|
442
508
|
|
|
443
509
|
def criteria_program(call: Call) -> str | None:
|
|
@@ -481,6 +547,12 @@ def criteria_verdict(call: Call) -> str | None:
|
|
|
481
547
|
guarded(command="X=pkill; cat <<EOF\n\tEOF\nX=echo\nEOF\n$X claude"): Block(pattern="signals every process"),
|
|
482
548
|
guarded(command="X=pkill; cat <<-EOF\n\tEOF\nX=echo\nEOF\n$X claude"): Allow(),
|
|
483
549
|
guarded(command="pkill node"): Block(),
|
|
550
|
+
guarded(command="pkill -0 --signal KILL -f claude"): Block(pattern="signals every process matching"),
|
|
551
|
+
guarded(command="pkill -0 --signal=KILL -f claude"): Block(),
|
|
552
|
+
guarded(command="pkill -0 -s KILL -f claude"): Block(),
|
|
553
|
+
guarded(command="pkill -0 -SIGKILL -f claude"): Block(),
|
|
554
|
+
guarded(command="pkill -0 -fs KILL claude"): Block(),
|
|
555
|
+
guarded(command="pkill -0 -e claude"): Block(),
|
|
484
556
|
guarded(command="pkill -0 -9 x"): Block(),
|
|
485
557
|
guarded(command="pkill -f -0 x"): Block(),
|
|
486
558
|
guarded(command="pkill -TERM -0 x"): Block(),
|
|
@@ -516,6 +588,7 @@ def criteria_verdict(call: Call) -> str | None:
|
|
|
516
588
|
guarded(command="fuser -v 3000/tcp"): Allow(),
|
|
517
589
|
guarded(command="gh api rate_limit -q '.resources.core' ; date +%s; pkill -0 x 2>/dev/null; echo"): Allow(),
|
|
518
590
|
guarded(command="pkill -0 -f 'ledger.py watch'"): Allow(),
|
|
591
|
+
guarded(command="pkill -0 -fx --newest claude"): Allow(),
|
|
519
592
|
guarded(command="pgrep -f claude"): Allow(),
|
|
520
593
|
guarded(command="echo pkill -f never"): Allow(),
|
|
521
594
|
guarded(tool="mcp__runner__exec", tool_input={"cmd": "pgrep -f claude"}): Allow(),
|
|
@@ -624,10 +697,19 @@ def orca_ending_verdict(call: Call) -> str | None:
|
|
|
624
697
|
)
|
|
625
698
|
case (str() as group_name, str() as verb_name) if verb_name in ORCA_ENDINGS.get(group_name, ()):
|
|
626
699
|
return orca_ending(spelling, group_name, verb_name, arguments)
|
|
700
|
+
case ("vm", verb_name) if (flag := orca_vm_run_flag(call)) is not None or verb_name != "recipe":
|
|
701
|
+
return (
|
|
702
|
+
f"BLOCKED: `{spelling}` passes `{flag or verb_name}`, which runs a recipe's commands on a VM, and a "
|
|
703
|
+
"recipe may end sessions. Run `orca vm recipe doctor <recipe-id>` without `--provision` or `--connect`."
|
|
704
|
+
)
|
|
627
705
|
case _:
|
|
628
706
|
return None
|
|
629
707
|
|
|
630
708
|
|
|
709
|
+
def orca_vm_run_flag(call: Call) -> str | None:
|
|
710
|
+
return next((arg for arg in call.args if arg.partition("=")[0] in ORCA_VM_RUN_FLAGS), None)
|
|
711
|
+
|
|
712
|
+
|
|
631
713
|
@guard(
|
|
632
714
|
tests={
|
|
633
715
|
guarded(
|
|
@@ -678,6 +760,15 @@ def orca_ending_verdict(call: Call) -> str | None:
|
|
|
678
760
|
guarded(command="orca nuke everything"): Block(pattern="does not know"),
|
|
679
761
|
guarded(command="orca orchestration worker-release --help 2>&1 | head -15"): Block(pattern="worker-release"),
|
|
680
762
|
guarded(command="orca terminal close --help"): Block(pattern="closes the current terminal"),
|
|
763
|
+
guarded(
|
|
764
|
+
command="orca vm recipe doctor guard-probe --provision --repo-path /Users/yasyf/.claude/scratch/x/vm-probe"
|
|
765
|
+
): Block(pattern="passes `--provision`"),
|
|
766
|
+
guarded(command="orca vm recipe doctor guard-probe --connect --json"): Block(pattern="passes `--connect`"),
|
|
767
|
+
guarded(command="orca vm recipe doctor guard-probe --provision=true"): Block(
|
|
768
|
+
pattern="passes `--provision=true`"
|
|
769
|
+
),
|
|
770
|
+
guarded(command="orca vm create guard-probe"): Block(pattern="passes `create`"),
|
|
771
|
+
guarded(command="orca vm recipe doctor cloud-sandbox --repo-path /path/to/repo --json"): Allow(),
|
|
681
772
|
guarded(command="'orca' terminal close"): Block(),
|
|
682
773
|
guarded(
|
|
683
774
|
tool="mcp__runner__exec", tool_input={"command": "orca", "args": ["terminal", "close", "--terminal", "t"]}
|
{capt_hook-12.76.1 → capt_hook-12.76.2}/captain_hook/builtin_packs/performance/hooks/filesystem.py
RENAMED
|
@@ -91,6 +91,15 @@ hook(
|
|
|
91
91
|
Input(command="cd / && for d in *; do find $d -name daemonkit; done"): Block(),
|
|
92
92
|
Input(command="find ~/.claude/worktrees/*/ -name daemonkit"): Block(),
|
|
93
93
|
Input(command="find /Users/* -maxdepth 3 -name daemonkit"): Block(),
|
|
94
|
+
Input(command='D=$(printf /); cd "$D" && find . -name daemonkit'): Block(),
|
|
95
|
+
Input(command="find . -name daemonkit"): Block(),
|
|
96
|
+
Input(
|
|
97
|
+
command=(
|
|
98
|
+
"for d in go/ci/internal/release/*/; do "
|
|
99
|
+
"echo \"$(find $d -name '*.go' ! -name '*_test.go' | xargs cat | wc -l) $d\"; done"
|
|
100
|
+
),
|
|
101
|
+
cwd="/Users/yasyf/.claude/worktrees/monorepo/ethos-audit",
|
|
102
|
+
): Block(),
|
|
94
103
|
Input(command="find / -maxdepth 1 -type d"): Allow(),
|
|
95
104
|
Input(command="find ~ -maxdepth 2 -type d -name daemonkit"): Allow(),
|
|
96
105
|
Input(command="find / -maxdepth 0"): Allow(),
|
|
@@ -103,13 +112,6 @@ hook(
|
|
|
103
112
|
"find $R -name 'targets.yaml' -not -path '*/node_modules/*' | head"
|
|
104
113
|
)
|
|
105
114
|
): Allow(),
|
|
106
|
-
Input(
|
|
107
|
-
command=(
|
|
108
|
-
"for d in go/ci/internal/release/*/; do "
|
|
109
|
-
"echo \"$(find $d -name '*.go' ! -name '*_test.go' | xargs cat | wc -l) $d\"; done"
|
|
110
|
-
),
|
|
111
|
-
cwd="/Users/yasyf/.claude/worktrees/monorepo/ethos-audit",
|
|
112
|
-
): Allow(),
|
|
113
115
|
Input(
|
|
114
116
|
command="for v in 0.2.9 0.2.11; do find $v -maxdepth 3; done",
|
|
115
117
|
cwd="/Users/yasyf/.claude/plugins/cache/forge",
|
|
@@ -129,9 +131,9 @@ hook(
|
|
|
129
131
|
): Allow(),
|
|
130
132
|
Input(command="find $HOME/.claude/worktrees/captain-hook/x -name daemonkit"): Allow(),
|
|
131
133
|
Input(command="find ~/.claude/worktrees/captain-hook/* -name daemonkit"): Allow(),
|
|
132
|
-
Input(command="find src -name daemonkit"): Allow(),
|
|
134
|
+
Input(command="find src -name daemonkit", cwd="/repo"): Allow(),
|
|
133
135
|
Input(command="find src -newermt 2026-09-17", cwd="/repo"): Allow(),
|
|
134
|
-
Input(command="find src -path / -prune"): Allow(),
|
|
136
|
+
Input(command="find src -path / -prune", cwd="/repo"): Allow(),
|
|
135
137
|
Input(command="echo 'find / -name daemonkit'"): Allow(),
|
|
136
138
|
Input(command="cat <<'EOF'\nfind / -name daemonkit\nEOF"): Allow(),
|
|
137
139
|
Input(command="ccx repo locate daemonkit"): Allow(),
|
|
@@ -580,7 +580,7 @@ class Cmd:
|
|
|
580
580
|
return self.scope, None
|
|
581
581
|
quote = call.occurrence.quote_contexts[-1:]
|
|
582
582
|
if quote == ('"',):
|
|
583
|
-
return host_scope, host_offset
|
|
583
|
+
return (self.scope, None) if "\\$" in payload.raw else (host_scope, host_offset)
|
|
584
584
|
within = None if word.span is None else self.char_offset(word.span[0]) - self.char_offset(payload.span[0])
|
|
585
585
|
if quote == ("'",) and payload.value is not None:
|
|
586
586
|
text, start = payload.value, 1
|
|
@@ -263,7 +263,7 @@ class PathMatches:
|
|
|
263
263
|
path = Path(value)
|
|
264
264
|
if not path.is_absolute():
|
|
265
265
|
if target.cwd is None:
|
|
266
|
-
return
|
|
266
|
+
return self.unresolved
|
|
267
267
|
path = target.cwd / path
|
|
268
268
|
return any(self.matches(candidate) for candidate in (path, glob_prefix(path)))
|
|
269
269
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "capt-hook"
|
|
3
3
|
# Inert sentinel — the real version is stamped from the release tag (uv version --frozen).
|
|
4
|
-
version = "12.76.
|
|
4
|
+
version = "12.76.2"
|
|
5
5
|
description = "Stop repeating yourself to Claude."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "PolyForm-Noncommercial-1.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|