subroutine 0.1.0__tar.gz → 0.1.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.
- {subroutine-0.1.0 → subroutine-0.1.2}/.claude-plugin/marketplace.json +1 -1
- {subroutine-0.1.0 → subroutine-0.1.2}/.github/workflows/ci.yml +15 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/.github/workflows/release.yml +23 -7
- {subroutine-0.1.0 → subroutine-0.1.2}/CHANGELOG.md +40 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/PKG-INFO +26 -12
- {subroutine-0.1.0 → subroutine-0.1.2}/README.md +25 -11
- {subroutine-0.1.0 → subroutine-0.1.2}/plugins/subroutine/.claude-plugin/plugin.json +3 -3
- {subroutine-0.1.0 → subroutine-0.1.2}/plugins/subroutine/skills/subroutine/SKILL.md +22 -7
- {subroutine-0.1.0 → subroutine-0.1.2}/pyproject.toml +14 -2
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/cli/personal.py +5 -15
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/directory.py +41 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/mcp/tools.py +28 -6
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_cli_connections.py +15 -3
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_directory.py +57 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_mcp.py +37 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_plugin.py +45 -8
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_reach.py +21 -3
- {subroutine-0.1.0 → subroutine-0.1.2}/.gitignore +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/CLA.md +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/CONTRIBUTING.md +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/LICENSE +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/alembic.ini +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/docs/errors.md +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/docs/hosting.md +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/plugins/subroutine/.mcp.json +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/scripts/check_licences.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/scripts/check_release_notes.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/__main__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/addressing.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/admin.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/agenda.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/app.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/comments.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/concurrency.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/dependencies.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/documents.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/events.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/health.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/identity.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/meta.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/middleware.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/pagination.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/problems.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/projects.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/query.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/routing.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/schemas.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/security.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/shaping.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/subjects.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/tasks.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/tokens.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/users.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/api/workspaces.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/auth.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/cli/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/cli/main.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/cli/topics.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/clients/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/clients/base.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/clients/http.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/clients/local.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/clients/opening.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/config.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/connections.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/context.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/credentials.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/backup.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/base.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrate.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/env.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/script.py.mako +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/versions/0c8f7a7027e6_refs_are_workspace_sequential_integers.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/versions/233f898a2bee_instance_timezone_workspace_timezone_.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/versions/2fee457e5b0b_initial_schema.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/versions/547fe53b263c_an_event_carries_the_subject_it_.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/migrations/versions/ea3e86ad12c4_workspace_slug_frees_on_soft_delete.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/mixins.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/activity.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/identity.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/project.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/system.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/vocabulary.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/models/work.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/seed.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/session.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/transfer.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/db/types.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/agenda.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/authentication.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/authorization.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/bootstrap.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/capture.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/comments.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/dates.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/documents.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/durations.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/events.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/hierarchy.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/instances.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/links.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/local.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/mentions.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/ordering.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/paging.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/patch.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/projects.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/readiness.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/refs.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/schedule.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/scoping.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/search.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/selection.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/tags.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/tasks.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/text.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/tokens.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/users.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/versions.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/domain/workspaces.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/errors.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/fanout.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/mcp/__init__.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/mcp/protocol.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/mcp/session.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/permissions.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/py.typed +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/src/subroutine/views.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/api_support.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/conftest.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/sample_models.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_actor_discipline.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_agenda.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_app.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_authentication.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_comments.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_concurrency.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_documents.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_events.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_examples.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_meta.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_problems.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_projects.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_routing.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_shaping.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_tasks.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_tokens.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_workspaces.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_api_writability.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_auth.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_authentication.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_authorization.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_bootstrap.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_capture.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_cli_help.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_config.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_connections.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_context.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_dates.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_db_types.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_documentation.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_durations.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_errors.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_fanout.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_fixtures.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_imports.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_instances.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_isolation.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_local_mode.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_migrations.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_models.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_multi_user.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_ordering.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_personal_path.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_release_notes.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_schedule.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_scoping.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_seed.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_services.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_smoke.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_spec_endpoints.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_sqlite_concurrency.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_transfer.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_transport_equivalence.py +0 -0
- {subroutine-0.1.0 → subroutine-0.1.2}/tests/test_upgrade.py +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "subroutine",
|
|
12
12
|
"source": "./plugins/subroutine",
|
|
13
|
-
"description": "Give Claude a shared, durable task list: file work, find what is ready, record what happened, and leave the next session something to read. Needs Subroutine itself installed separately
|
|
13
|
+
"description": "Give Claude a shared, durable task list: file work, find what is ready, record what happened, and leave the next session something to read. Needs Subroutine itself installed separately and on your PATH \u2014 'uv tool install subroutine' or 'pipx install subroutine', then 'subroutine init'.",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Simon Holliday",
|
|
16
16
|
"email": "simon.holliday@protonmail.com"
|
|
@@ -68,6 +68,11 @@ jobs:
|
|
|
68
68
|
|
|
69
69
|
steps:
|
|
70
70
|
- uses: actions/checkout@v4
|
|
71
|
+
with:
|
|
72
|
+
# Tags, because the version is derived from them (`SR#234`). A shallow clone makes
|
|
73
|
+
# the build warn and produce `0.1.dev1+g…` rather than fail, so this job would
|
|
74
|
+
# install a package quietly claiming to be a version nobody released.
|
|
75
|
+
fetch-depth: 0
|
|
71
76
|
|
|
72
77
|
- uses: actions/setup-python@v5
|
|
73
78
|
with:
|
|
@@ -139,6 +144,11 @@ jobs:
|
|
|
139
144
|
|
|
140
145
|
steps:
|
|
141
146
|
- uses: actions/checkout@v4
|
|
147
|
+
with:
|
|
148
|
+
# Tags, because the version is derived from them (`SR#234`). A shallow clone makes
|
|
149
|
+
# the build warn and produce `0.1.dev1+g…` rather than fail, so this job would
|
|
150
|
+
# install a package quietly claiming to be a version nobody released.
|
|
151
|
+
fetch-depth: 0
|
|
142
152
|
|
|
143
153
|
- uses: actions/setup-python@v5
|
|
144
154
|
with:
|
|
@@ -168,6 +178,11 @@ jobs:
|
|
|
168
178
|
|
|
169
179
|
steps:
|
|
170
180
|
- uses: actions/checkout@v4
|
|
181
|
+
with:
|
|
182
|
+
# Tags, because the version is derived from them (`SR#234`). A shallow clone makes
|
|
183
|
+
# the build warn and produce `0.1.dev1+g…` rather than fail, so this job would
|
|
184
|
+
# install a package quietly claiming to be a version nobody released.
|
|
185
|
+
fetch-depth: 0
|
|
171
186
|
|
|
172
187
|
- uses: actions/setup-python@v5
|
|
173
188
|
with:
|
|
@@ -52,6 +52,11 @@ jobs:
|
|
|
52
52
|
|
|
53
53
|
steps:
|
|
54
54
|
- uses: actions/checkout@v4
|
|
55
|
+
with:
|
|
56
|
+
# Tags, because the version is derived from them (`SR#234`). A shallow clone makes
|
|
57
|
+
# the build warn and produce `0.1.dev1+g…` rather than fail, so this job would
|
|
58
|
+
# install a package quietly claiming to be a version nobody released.
|
|
59
|
+
fetch-depth: 0
|
|
55
60
|
|
|
56
61
|
- uses: actions/setup-python@v5
|
|
57
62
|
with:
|
|
@@ -100,20 +105,31 @@ jobs:
|
|
|
100
105
|
- name: Check the metadata
|
|
101
106
|
run: twine check dist/*
|
|
102
107
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
|
|
108
|
+
# **Two things, and only the second can now be wrong** (`SR#234`). The package derives its
|
|
109
|
+
# version from the tag, so the first comparison is close to tautological — it is kept
|
|
110
|
+
# because it is not *quite* one: a shallow clone, a missing tag or a dirty tree all make
|
|
111
|
+
# the backend produce a `.devN+g…` version instead, and this is where that would be caught
|
|
112
|
+
# rather than at the upload. The plugin manifest is static JSON with no build step, so it
|
|
113
|
+
# is the half that a release can genuinely publish out of step with itself.
|
|
114
|
+
- name: The tag names what is being built and what the plugin claims
|
|
107
115
|
if: startsWith(github.ref, 'refs/tags/')
|
|
108
116
|
run: |
|
|
109
117
|
set -euo pipefail
|
|
110
118
|
tagged="${GITHUB_REF_NAME#v}"
|
|
111
|
-
|
|
119
|
+
|
|
120
|
+
built="$(python -c 'import importlib.metadata; print(importlib.metadata.version("subroutine"))')"
|
|
112
121
|
if [ "$tagged" != "$built" ]; then
|
|
113
|
-
echo "the tag $GITHUB_REF_NAME
|
|
122
|
+
echo "the tag $GITHUB_REF_NAME built version $built — expected $tagged" >&2
|
|
114
123
|
exit 1
|
|
115
124
|
fi
|
|
116
|
-
|
|
125
|
+
|
|
126
|
+
declared="$(python -c 'import json, pathlib; print(json.loads(pathlib.Path("plugins/subroutine/.claude-plugin/plugin.json").read_text())["version"])')"
|
|
127
|
+
if [ "$tagged" != "$declared" ]; then
|
|
128
|
+
echo "the plugin manifest says $declared, and this release is $tagged" >&2
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
echo "tag, package and plugin all agree on $tagged"
|
|
117
133
|
|
|
118
134
|
# A release that moves the schema has to say so in the changelog (SPEC.md §12.4a). CI
|
|
119
135
|
# checks this on every commit; it is checked again here because this is the run where
|
|
@@ -12,6 +12,46 @@ The point of it is that you can *plan* a database upgrade instead of meeting one
|
|
|
12
12
|
through installing something. See [docs/hosting.md](docs/hosting.md#upgrading) for what the
|
|
13
13
|
upgrade involves.
|
|
14
14
|
|
|
15
|
+
## 0.1.2 — 2026-08-02
|
|
16
|
+
|
|
17
|
+
No migration notice: the schema head has not moved since 0.1.0. Nothing in the package itself
|
|
18
|
+
changed — this release is the plugin and the documentation around installing it.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Installing the Claude Code plugin now gives you working tools without your having to supply
|
|
23
|
+
a path. Your editor launches `subroutine` itself, so it has to be on your `PATH` — and
|
|
24
|
+
`pip install` into a virtualenv satisfies "installed" while leaving every tool missing, with
|
|
25
|
+
no error at the point of the mistake. The plugin route now tells you to install it as a
|
|
26
|
+
tool, with `uv tool install subroutine` or `pipx install subroutine`, which is what puts the
|
|
27
|
+
command where something other than your shell can find it.
|
|
28
|
+
- The plugin's skill sent that failure the wrong way. It could not tell "not installed" from
|
|
29
|
+
"installed where the editor cannot see it" — both look the same from inside a session — so it
|
|
30
|
+
told people who had already installed it to install it again. It now names `claude mcp list`,
|
|
31
|
+
which is the one command that separates them.
|
|
32
|
+
|
|
33
|
+
`pip install subroutine` remains exactly right if you are only going to type commands
|
|
34
|
+
yourself.
|
|
35
|
+
|
|
36
|
+
## 0.1.1 — 2026-08-02
|
|
37
|
+
|
|
38
|
+
No migration notice: the schema head has not moved since 0.1.0.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- An agent working in a checkout marked for a different instance could not file anything.
|
|
43
|
+
Committing a `.subroutine` file is how a team says which project a repository belongs to, so
|
|
44
|
+
a colleague who cloned the repository and ran `subroutine init` had a marker naming a project
|
|
45
|
+
that was not on their own instance — and `subroutine_add` refused every call rather than
|
|
46
|
+
ignoring it, while the CLI beside it carried on. Reads were never affected. A renamed project
|
|
47
|
+
is now also followed by its id over the Model Context Protocol, as it already was on the
|
|
48
|
+
command line.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- The version is derived from the git tag rather than written into `pyproject.toml`, so what
|
|
53
|
+
the package reports and what was released cannot drift apart.
|
|
54
|
+
|
|
15
55
|
## 0.1.0 — 2026-08-01
|
|
16
56
|
|
|
17
57
|
The first public release. Everything here is new, so this section says what exists rather than
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subroutine
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Project management for people and agents, in equal measure.
|
|
5
5
|
Project-URL: Homepage, https://github.com/simonholliday/subroutine
|
|
6
6
|
Project-URL: Issues, https://github.com/simonholliday/subroutine/issues
|
|
@@ -84,6 +84,7 @@ practice — it keeps the backlog, records what it did, and adopts Subroutine in
|
|
|
84
84
|
are already working on. **You never have to learn the CLI.**
|
|
85
85
|
|
|
86
86
|
```console
|
|
87
|
+
$ uv tool install subroutine # or pipx — your editor starts it, so it must be on your PATH
|
|
87
88
|
$ claude plugin marketplace add simonholliday/subroutine
|
|
88
89
|
$ claude plugin install subroutine@subroutine
|
|
89
90
|
```
|
|
@@ -96,11 +97,8 @@ $ subroutine serve
|
|
|
96
97
|
$ subroutine token create --title "CI" --scope task:read # a credential that can only read
|
|
97
98
|
```
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
command on this page was verified against. The full hosting recipe is in
|
|
102
|
-
[docs/hosting.md](docs/hosting.md); `subroutine help` lists the commands and `subroutine explain
|
|
103
|
-
dates` covers the ideas behind them.
|
|
100
|
+
The full hosting recipe is in [docs/hosting.md](docs/hosting.md); `subroutine help` lists the
|
|
101
|
+
commands and `subroutine explain dates` covers the ideas behind them.
|
|
104
102
|
|
|
105
103
|
> **My context window ends. The instance does not.**
|
|
106
104
|
>
|
|
@@ -119,10 +117,21 @@ dates` covers the ideas behind them.
|
|
|
119
117
|
## Install
|
|
120
118
|
|
|
121
119
|
Python 3.11+ and thirteen dependencies. Nothing to create, nothing to configure, no server to
|
|
122
|
-
start — SQLite is the default and `subroutine init` makes it.
|
|
120
|
+
start — SQLite is the default and `subroutine init` makes it.
|
|
121
|
+
|
|
122
|
+
`pip install subroutine` is fine if you are only going to type commands yourself. **If an
|
|
123
|
+
editor or an agent is going to launch it, install it as a tool instead** — that is what puts
|
|
124
|
+
`subroutine` on your `PATH`, where something other than your shell can find it:
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
$ uv tool install subroutine
|
|
128
|
+
$ pipx install subroutine # the same thing, if you have pipx rather than uv
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
PostgreSQL when you outgrow SQLite — the extra goes on whichever of the three you used:
|
|
123
132
|
|
|
124
133
|
```console
|
|
125
|
-
$
|
|
134
|
+
$ uv tool install "subroutine[postgres]"
|
|
126
135
|
```
|
|
127
136
|
|
|
128
137
|
## The shape of it
|
|
@@ -211,10 +220,15 @@ $ claude plugin marketplace add simonholliday/subroutine
|
|
|
211
220
|
$ claude plugin install subroutine@subroutine
|
|
212
221
|
```
|
|
213
222
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
**Your editor launches `subroutine` itself, so it has to be on your `PATH`** — which is what
|
|
224
|
+
`uv tool install` and `pipx install` guarantee and a virtualenv does not. If the tools do not
|
|
225
|
+
appear afterwards, that is nearly always why, and `claude mcp list` says so in one line:
|
|
226
|
+
installing a plugin and starting its server are separate moments, and only the first one
|
|
227
|
+
reports. `/plugin configure subroutine` takes the absolute path when you would rather point at
|
|
228
|
+
a virtualenv than install it again.
|
|
229
|
+
|
|
230
|
+
It can also be given a connection and a token, both only needed for somebody else's instance.
|
|
231
|
+
The token is kept in your system keychain, never in a settings file.
|
|
218
232
|
|
|
219
233
|
Without the plugin, or for another MCP client:
|
|
220
234
|
|
|
@@ -39,6 +39,7 @@ practice — it keeps the backlog, records what it did, and adopts Subroutine in
|
|
|
39
39
|
are already working on. **You never have to learn the CLI.**
|
|
40
40
|
|
|
41
41
|
```console
|
|
42
|
+
$ uv tool install subroutine # or pipx — your editor starts it, so it must be on your PATH
|
|
42
43
|
$ claude plugin marketplace add simonholliday/subroutine
|
|
43
44
|
$ claude plugin install subroutine@subroutine
|
|
44
45
|
```
|
|
@@ -51,11 +52,8 @@ $ subroutine serve
|
|
|
51
52
|
$ subroutine token create --title "CI" --scope task:read # a credential that can only read
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
command on this page was verified against. The full hosting recipe is in
|
|
57
|
-
[docs/hosting.md](docs/hosting.md); `subroutine help` lists the commands and `subroutine explain
|
|
58
|
-
dates` covers the ideas behind them.
|
|
55
|
+
The full hosting recipe is in [docs/hosting.md](docs/hosting.md); `subroutine help` lists the
|
|
56
|
+
commands and `subroutine explain dates` covers the ideas behind them.
|
|
59
57
|
|
|
60
58
|
> **My context window ends. The instance does not.**
|
|
61
59
|
>
|
|
@@ -74,10 +72,21 @@ dates` covers the ideas behind them.
|
|
|
74
72
|
## Install
|
|
75
73
|
|
|
76
74
|
Python 3.11+ and thirteen dependencies. Nothing to create, nothing to configure, no server to
|
|
77
|
-
start — SQLite is the default and `subroutine init` makes it.
|
|
75
|
+
start — SQLite is the default and `subroutine init` makes it.
|
|
76
|
+
|
|
77
|
+
`pip install subroutine` is fine if you are only going to type commands yourself. **If an
|
|
78
|
+
editor or an agent is going to launch it, install it as a tool instead** — that is what puts
|
|
79
|
+
`subroutine` on your `PATH`, where something other than your shell can find it:
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
$ uv tool install subroutine
|
|
83
|
+
$ pipx install subroutine # the same thing, if you have pipx rather than uv
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
PostgreSQL when you outgrow SQLite — the extra goes on whichever of the three you used:
|
|
78
87
|
|
|
79
88
|
```console
|
|
80
|
-
$
|
|
89
|
+
$ uv tool install "subroutine[postgres]"
|
|
81
90
|
```
|
|
82
91
|
|
|
83
92
|
## The shape of it
|
|
@@ -166,10 +175,15 @@ $ claude plugin marketplace add simonholliday/subroutine
|
|
|
166
175
|
$ claude plugin install subroutine@subroutine
|
|
167
176
|
```
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
**Your editor launches `subroutine` itself, so it has to be on your `PATH`** — which is what
|
|
179
|
+
`uv tool install` and `pipx install` guarantee and a virtualenv does not. If the tools do not
|
|
180
|
+
appear afterwards, that is nearly always why, and `claude mcp list` says so in one line:
|
|
181
|
+
installing a plugin and starting its server are separate moments, and only the first one
|
|
182
|
+
reports. `/plugin configure subroutine` takes the absolute path when you would rather point at
|
|
183
|
+
a virtualenv than install it again.
|
|
184
|
+
|
|
185
|
+
It can also be given a connection and a token, both only needed for somebody else's instance.
|
|
186
|
+
The token is kept in your system keychain, never in a settings file.
|
|
173
187
|
|
|
174
188
|
Without the plugin, or for another MCP client:
|
|
175
189
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subroutine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Project management for people and agents, in equal measure. Wires this session to a Subroutine instance and carries the working practice for using it well.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Simon Holliday",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"command": {
|
|
19
19
|
"type": "string",
|
|
20
20
|
"title": "The subroutine command",
|
|
21
|
-
"description": "Usually just 'subroutine'. Give the full path if
|
|
21
|
+
"description": "Usually just 'subroutine'. Your editor launches it, so it has to be on your PATH \u2014 'uv tool install subroutine' or 'pipx install subroutine' put it there. Give the full path instead if it lives in a virtualenv your editor does not activate, for example /home/you/.venvs/subroutine/bin/subroutine. Not installed yet? Install it in a terminal, run 'subroutine init', then come back here.",
|
|
22
22
|
"default": "subroutine"
|
|
23
23
|
},
|
|
24
24
|
"connection": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"token": {
|
|
31
31
|
"type": "string",
|
|
32
32
|
"title": "Agent token (optional)",
|
|
33
|
-
"description": "A token issued for this agent, if you want it to have less access than you do
|
|
33
|
+
"description": "A token issued for this agent, if you want it to have less access than you do \u2014 'subroutine token create --service-account claude --scope task:read'. Leave empty to let it act as you. Stored in your system keychain, never in a settings file.",
|
|
34
34
|
"sensitive": true,
|
|
35
35
|
"default": ""
|
|
36
36
|
}
|
|
@@ -9,14 +9,30 @@ Subroutine is a task and project tracker that a person and an agent use as equal
|
|
|
9
9
|
write in it is attributed to you, addressable by a number, and still there after your context
|
|
10
10
|
is gone — which is the whole reason to spend calls on it.
|
|
11
11
|
|
|
12
|
-
**If the `subroutine_*` tools are not available, stop and say so
|
|
13
|
-
but does not install the program
|
|
12
|
+
**If the `subroutine_*` tools are not available, stop and say so — and do not guess which of the
|
|
13
|
+
two causes it is.** The plugin configures the tools but does not install the program, so either
|
|
14
|
+
it is not installed, or it is installed somewhere the editor cannot see. Both look identical
|
|
15
|
+
from here: no tools, and no error, because installing a plugin and starting its server are
|
|
16
|
+
separate moments and only the first one reports. One command tells them apart:
|
|
14
17
|
|
|
15
18
|
```
|
|
16
|
-
|
|
19
|
+
claude mcp list
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`✘ Failed to connect` beside `plugin:subroutine:subroutine` means the program was not found on
|
|
23
|
+
the `PATH` the editor passes down — nearly always a virtualenv. Say so, and offer the two ways
|
|
24
|
+
out: install it as a tool so it is on the `PATH` for good, or point the plugin at the copy that
|
|
25
|
+
already exists.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
uv tool install subroutine # or: pipx install subroutine
|
|
17
29
|
subroutine init
|
|
18
30
|
```
|
|
19
31
|
|
|
32
|
+
`/plugin configure subroutine` takes the absolute path instead — `<venv>/bin/subroutine` — for
|
|
33
|
+
somebody who would rather not install it twice. If the server is not listed at all, the plugin
|
|
34
|
+
itself is not installed or is disabled.
|
|
35
|
+
|
|
20
36
|
**If the tools are there but every call fails, read what the failure says — it names the
|
|
21
37
|
remedy.** This is the ordinary case on a fresh install and it is not the one above: the MCP
|
|
22
38
|
server starts perfectly well against an instance nobody has created yet, so the tools appear
|
|
@@ -24,10 +40,9 @@ and then refuse. `no Subroutine instance has been set up here yet.` means the us
|
|
|
24
40
|
`subroutine init` once; a schema message means `subroutine upgrade`. Pass the failure on
|
|
25
41
|
verbatim rather than diagnosing it — the sentence is written for them.
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
tracker nobody can reach is worse than an honest failure.
|
|
43
|
+
**Whichever of these it is, do not work around it by writing to a file instead.** A tracker
|
|
44
|
+
nobody can reach is worse than an honest failure, and a to-do list in a scratch file is a
|
|
45
|
+
tracker nobody can reach.
|
|
31
46
|
|
|
32
47
|
`GET /v1/docs/agent` on any served instance is the reference for the HTTP API. This page is
|
|
33
48
|
about *when* to reach for it and what good practice looks like — it does not repeat the API.
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "subroutine"
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
# Derived from the git tag, never written here (`#234`). `v0.1.1` builds `0.1.1`; anything
|
|
9
|
+
# further along builds a `.devN+g<sha>` version, which PyPI refuses — so an untagged commit
|
|
10
|
+
# cannot be published even by accident. `subroutine.__version__` already read this back
|
|
11
|
+
# through `importlib.metadata`, so nothing at runtime changed.
|
|
12
|
+
#
|
|
13
|
+
# **The cost is that a shallow clone lies rather than fails.** Without tags the backend warns
|
|
14
|
+
# and produces `0.1.dev1+g…`, so every workflow job that installs this package checks out with
|
|
15
|
+
# `fetch-depth: 0`.
|
|
16
|
+
dynamic = ["version"]
|
|
8
17
|
description = "Project management for people and agents, in equal measure."
|
|
9
18
|
readme = "README.md"
|
|
10
19
|
requires-python = ">=3.11"
|
|
@@ -41,6 +50,9 @@ dependencies = [
|
|
|
41
50
|
"uvicorn[standard]>=0.30",
|
|
42
51
|
]
|
|
43
52
|
|
|
53
|
+
[tool.hatch.version]
|
|
54
|
+
source = "vcs"
|
|
55
|
+
|
|
44
56
|
[project.optional-dependencies]
|
|
45
57
|
postgres = ["psycopg[binary]>=3.1"]
|
|
46
58
|
dev = [
|
|
@@ -2606,26 +2606,16 @@ def register (
|
|
|
2606
2606
|
def _project_named_by (world: World, marker: subroutine.directory.Marker) -> str | None:
|
|
2607
2607
|
"""Return the current key of the project a marker names, or ``None`` if there is none.
|
|
2608
2608
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2609
|
+
The matching itself is `subroutine.directory.resolve`, which is shared with the MCP
|
|
2610
|
+
server — this half is only the fetching, because that is the part that needs a world
|
|
2611
|
+
to fetch through. It was one function here until `#232` found the other surface had no
|
|
2612
|
+
equivalent at all.
|
|
2613
2613
|
"""
|
|
2614
2614
|
|
|
2615
2615
|
where = world.writing_to()
|
|
2616
2616
|
found = where.client.projects(workspace=_writing_workspace(world))
|
|
2617
2617
|
|
|
2618
|
-
|
|
2619
|
-
for row in found:
|
|
2620
|
-
if str(row.id) == marker.project_id:
|
|
2621
|
-
return row.key
|
|
2622
|
-
|
|
2623
|
-
if marker.project is not None:
|
|
2624
|
-
for row in found:
|
|
2625
|
-
if row.key.upper() == marker.project.upper():
|
|
2626
|
-
return row.key
|
|
2627
|
-
|
|
2628
|
-
return None
|
|
2618
|
+
return subroutine.directory.resolve(marker, found)
|
|
2629
2619
|
|
|
2630
2620
|
@app.command(hidden=not _worth_showing(settings))
|
|
2631
2621
|
def connections () -> None:
|
|
@@ -81,6 +81,47 @@ class Marker(typing.NamedTuple):
|
|
|
81
81
|
return f"{named or 'nothing'}, from {self.path}"
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
class Named(typing.Protocol):
|
|
85
|
+
"""The two fields a marker is resolved against, on whatever a client hands back."""
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def id (self) -> typing.Any:
|
|
89
|
+
"""The project's permanent identifier."""
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def key (self) -> str:
|
|
93
|
+
"""The project's current key."""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def resolve (marker: Marker, projects: typing.Iterable[Named]) -> str | None:
|
|
97
|
+
"""Return the current key of the project a marker names, or ``None`` if there is none.
|
|
98
|
+
|
|
99
|
+
By id where the marker carries one, because that is the half that survives a rename
|
|
100
|
+
(`#177`); by key otherwise, which is every marker written before that change — including
|
|
101
|
+
the one in this repository. A marker that predates it must go on working, or the upgrade
|
|
102
|
+
is the outage.
|
|
103
|
+
|
|
104
|
+
**Returning ``None`` is an answer, not a failure** (`#166`). A marker is advisory context
|
|
105
|
+
written by a machine, so a checkout marked for one instance must not stop work being filed
|
|
106
|
+
against another — the caller reports that it was ignored and carries on. This lives here,
|
|
107
|
+
client-free and taking rows rather than fetching them, because both surfaces need the same
|
|
108
|
+
answer and the CLI had the only copy: `subroutine_add` passed the marker's key straight to
|
|
109
|
+
the server and refused whenever it did not exist there (`#228`'s neighbour, `#232`).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
if marker.project_id is not None:
|
|
113
|
+
for row in projects:
|
|
114
|
+
if str(row.id) == marker.project_id:
|
|
115
|
+
return row.key
|
|
116
|
+
|
|
117
|
+
if marker.project is not None:
|
|
118
|
+
for row in projects:
|
|
119
|
+
if row.key.upper() == marker.project.upper():
|
|
120
|
+
return row.key
|
|
121
|
+
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
84
125
|
def find (start: pathlib.Path | None = None) -> Marker | None:
|
|
85
126
|
"""Return the nearest marker at or above ``start``, or ``None``.
|
|
86
127
|
|
|
@@ -472,27 +472,49 @@ def _added (
|
|
|
472
472
|
# need it restarted — which is the one thing an agent cannot do to itself.
|
|
473
473
|
marker = subroutine.directory.find()
|
|
474
474
|
line = _text(arguments, "text") or ""
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
475
|
+
workspace = _text(arguments, "workspace")
|
|
476
|
+
|
|
477
|
+
consulted = (
|
|
478
|
+
marker is not None
|
|
479
|
+
and (marker.project is not None or marker.project_id is not None)
|
|
479
480
|
and not subroutine.domain.capture.names_a_project(line)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# **Resolved against this instance, never passed through** (`#232`). The marker's key went
|
|
484
|
+
# straight to the server until 0.1.0, so a checkout marked for somebody else's instance —
|
|
485
|
+
# which is what committing this file is *for* — refused every `subroutine_add` with "there
|
|
486
|
+
# is no project 'SR' here", while the CLI beside it filed the task and said it had ignored
|
|
487
|
+
# the marker. `#166` settled that the marker is advisory; only one surface implemented it.
|
|
488
|
+
# Resolving also buys `#177`: a renamed project is followed by id, which this never did.
|
|
489
|
+
filed = (
|
|
490
|
+
subroutine.directory.resolve(marker, client.projects(workspace=workspace))
|
|
491
|
+
if consulted and marker is not None
|
|
480
492
|
else None
|
|
481
493
|
)
|
|
482
494
|
|
|
483
495
|
captured = client.capture(
|
|
484
496
|
text=line,
|
|
485
|
-
workspace=
|
|
497
|
+
workspace=workspace,
|
|
486
498
|
type=_text(arguments, "type"),
|
|
487
499
|
project=filed,
|
|
488
500
|
)
|
|
489
501
|
answer = "Added " + _line(captured.task)
|
|
490
502
|
|
|
491
503
|
# Said out loud for the same reason the CLI says it: nobody typed it, and an agent that
|
|
492
|
-
# cannot see where its work went cannot tell a person either.
|
|
504
|
+
# cannot see where its work went cannot tell a person either. That argument applies just
|
|
505
|
+
# as much when the marker was *not* used — more so, because the agent is then holding a
|
|
506
|
+
# repository whose file says one thing and an instance that says another.
|
|
493
507
|
if filed is not None:
|
|
494
508
|
answer = f"{answer}\n in {filed}, from {subroutine.directory.FILE_NAME}"
|
|
495
509
|
|
|
510
|
+
elif consulted and marker is not None:
|
|
511
|
+
shown = marker.project or marker.project_id
|
|
512
|
+
|
|
513
|
+
answer = (
|
|
514
|
+
f"{answer}\n {subroutine.directory.FILE_NAME} here names {shown!r}, which is not "
|
|
515
|
+
f"on this instance. Ignoring it."
|
|
516
|
+
)
|
|
517
|
+
|
|
496
518
|
# Both halves of §6.13's obligation, and `#135` is why the second one is here: an agent is
|
|
497
519
|
# the caller most likely to have written something it believes was understood, and telling
|
|
498
520
|
# it only what was *left* answers the rarer question.
|
|
@@ -21,6 +21,7 @@ import subprocess
|
|
|
21
21
|
import sys
|
|
22
22
|
import time
|
|
23
23
|
import typing
|
|
24
|
+
import zoneinfo
|
|
24
25
|
|
|
25
26
|
import httpx
|
|
26
27
|
import pytest
|
|
@@ -30,6 +31,13 @@ import typer.testing
|
|
|
30
31
|
import subroutine.cli.main
|
|
31
32
|
import subroutine.credentials
|
|
32
33
|
|
|
34
|
+
#: The zone every instance in this file is created in, named once because a test that asks
|
|
35
|
+
#: what day it is has to ask *this* clock rather than the machine's (`#233`). Deliberately
|
|
36
|
+
#: not UTC: a zone that is offset from the runner's is what makes a whole-day expiry mean
|
|
37
|
+
#: something to assert.
|
|
38
|
+
INSTANCE_ZONE = "Europe/London"
|
|
39
|
+
|
|
40
|
+
|
|
33
41
|
#: How long to wait for a freshly started server before giving up on it.
|
|
34
42
|
STARTUP_TIMEOUT_SECONDS = 20.0
|
|
35
43
|
|
|
@@ -47,7 +55,7 @@ def home (tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Pa
|
|
|
47
55
|
if name.startswith(("SUBROUTINE_TOKEN", "SUBROUTINE_WORKSPACE", "SUBROUTINE_CONNECTION")):
|
|
48
56
|
monkeypatch.delenv(name, raising=False)
|
|
49
57
|
|
|
50
|
-
monkeypatch.setenv("SUBROUTINE_DEFAULT_TIMEZONE",
|
|
58
|
+
monkeypatch.setenv("SUBROUTINE_DEFAULT_TIMEZONE", INSTANCE_ZONE)
|
|
51
59
|
|
|
52
60
|
return root
|
|
53
61
|
|
|
@@ -121,7 +129,7 @@ def served (tmp_path: pathlib.Path) -> typing.Iterator[Remote]:
|
|
|
121
129
|
"XDG_CONFIG_HOME": str(root / "config"),
|
|
122
130
|
"XDG_DATA_HOME": str(root / "data"),
|
|
123
131
|
"XDG_STATE_HOME": str(root / "state"),
|
|
124
|
-
"SUBROUTINE_DEFAULT_TIMEZONE":
|
|
132
|
+
"SUBROUTINE_DEFAULT_TIMEZONE": INSTANCE_ZONE,
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
for name in list(environment):
|
|
@@ -929,7 +937,11 @@ def test_an_expiry_names_a_whole_day_and_the_token_lives_through_it (
|
|
|
929
937
|
|
|
930
938
|
run("init")
|
|
931
939
|
|
|
932
|
-
today
|
|
940
|
+
# **The instance's today, not the machine's** (`#233`). The runner is UTC and the instance
|
|
941
|
+
# is an hour ahead of it, so between 23:00 and midnight `date.today()` names a day that
|
|
942
|
+
# ended here an hour ago — and the token issued "through today" is already expired when the
|
|
943
|
+
# listing is read. One hour a night, every night the offset is not zero.
|
|
944
|
+
today = datetime.datetime.now(zoneinfo.ZoneInfo(INSTANCE_ZONE)).date().isoformat()
|
|
933
945
|
issued = run("token", "create", "--title", "through today", "--expires", today)
|
|
934
946
|
secret = next(word for word in issued.output.split() if word.startswith("sr_"))
|
|
935
947
|
|