From 466d3749fab7199ff767b897fbaa771fc7978c3e Mon Sep 17 00:00:00 2001 From: Ben Fortuna Date: Sat, 6 Jun 2026 13:38:30 +1000 Subject: [PATCH 1/5] Unify command vocabulary across CLI, MCP and agent surfaces Introduce a shared controlled vocabulary (verbs, entity types, loci, serialization families) that all command surfaces name operations from, while keeping each surface's dispatch independent (Option B). - Add ical4j-command-core module defining Verb, EntityType, Locus, SerializationFamily and SendMethod enums (vocabulary only, no dispatch) - CLI: standardise delete->remove, strategy new->generate, and reframe calendar/card CRUD as import/export; restructure command packages - MCP: rename service methods to canonical verbs (list*, add/remove, import*, get*) and drop the dead CLI reach-through in WorkspaceService - Agent: align @Tool names to the generate verb; wire core dependency - Docs: document the canonical vocabulary in command-protocols.md and README.md as the source of truth - OpenSpec: add command-vocabulary spec and archive the change Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/commands/opsx/apply.md | 152 +++++++++ .claude/commands/opsx/archive.md | 157 ++++++++++ .claude/commands/opsx/explore.md | 173 +++++++++++ .claude/commands/opsx/propose.md | 106 +++++++ .claude/skills/openspec-apply-change/SKILL.md | 156 ++++++++++ .../skills/openspec-archive-change/SKILL.md | 114 +++++++ .claude/skills/openspec-explore/SKILL.md | 288 ++++++++++++++++++ .claude/skills/openspec-propose/SKILL.md | 110 +++++++ README.md | 131 +++++--- build.gradle | 1 + docs/command-protocols.md | 118 +++++++ gradle/libs.versions.toml | 5 +- ical4j-command-agent/build.gradle | 5 + .../command/agent/CalendarUserAgent.java | 24 +- .../command/agent/CalendarValidator.java | 53 ++++ .../ical4j/command/agent/EntityBuilder.java | 51 ++++ .../ical4j/command/agent/EventGenerator.java | 58 ++++ .../agent/CalendarValidatorTest.groovy | 38 +++ ical4j-command-cli/build.gradle | 2 + .../src/main/java/module-info.java | 13 +- .../java/org/ical4j/command/CommandMain.java | 42 ++- .../org/ical4j/command/ConfigureCommand.java | 12 - .../org/ical4j/command/ConverterCommand.java | 37 ++- .../java/org/ical4j/command/CopyCommand.java | 43 +++ .../ical4j/command/DefaultOutputHandlers.java | 7 +- .../CreateEvent.java => DeleteCommand.java} | 18 +- .../java/org/ical4j/command/EditCommand.java | 38 +++ .../org/ical4j/command/ExportCommand.java | 38 +++ .../org/ical4j/command/ImportCalendars.java | 36 +++ .../command/{collection => }/ImportCards.java | 3 +- .../java/org/ical4j/command/InputOptions.java | 28 +- .../java/org/ical4j/command/ListCommand.java | 112 +++++++ .../java/org/ical4j/command/MoveCommand.java | 43 +++ .../org/ical4j/command/OutputOptions.java | 46 +++ .../java/org/ical4j/command/PrintCommand.java | 38 +++ .../org/ical4j/command/ValidatorCommand.java | 8 +- .../org/ical4j/command/VersionProvider.java | 2 +- .../calendar/AbstractCalendarCommand.java | 4 +- .../DeleteCalendar.java | 5 +- .../command/calendar/FilterCalendar.java | 4 + .../{collection => calendar}/GetCalendar.java | 5 +- .../ical4j/command/calendar/Serializer.java | 8 +- .../UpdateCalendar.java | 7 +- .../ical4j/command/calendar/Validator.java | 4 + .../AbstractEntityCommand.java} | 14 +- .../{entity => card}/CardCommandGroup.java | 2 +- .../{collection => card}/DeleteCard.java | 5 +- .../command/{collection => card}/GetCard.java | 5 +- .../command/{vcard => card}/Serializer.java | 13 +- .../{collection => card}/UpdateCard.java | 7 +- .../command/collection/AddCollection.java | 71 +++++ .../collection/CollectionCommandGroup.java | 7 +- .../GetCollectionDetails.java | 3 +- .../command/collection/ImportCalendars.java | 31 -- .../command/collection/ListCalendars.java | 54 ---- .../command/collection/QueryCollection.java | 33 -- .../command/collection/RemoveCollection.java | 56 ++++ .../UpdateCollection.java | 3 +- .../ical4j/command/config/CommandConfig.java | 132 ++++++++ .../command/config/ConfigureCommand.java | 30 ++ .../command/metadata/AddAttachment.java | 38 +++ .../command/metadata/AddCategories.java | 38 +++ .../command/metadata/AddCommandGroup.java | 44 +++ .../ical4j/command/metadata/AddComment.java | 38 +++ .../ical4j/command/metadata/AddRelated.java | 38 +++ .../ical4j/command/metadata/GetProperty.java | 38 +++ .../metadata/PropertyCommandGroup.java | 35 +++ .../command/metadata/SetCommandGroup.java | 38 +++ .../command/metadata/UnsetProperty.java | 38 +++ .../command/revisions/ListRevisions.java | 38 +++ .../command/revisions/RevertRevision.java | 38 +++ .../command/revisions/UndoRevisions.java | 38 +++ .../ical4j/command/strategy/CreateAction.java | 104 +++++++ .../CreateCalendar.java | 7 +- .../{collection => strategy}/CreateCard.java | 7 +- .../ical4j/command/strategy/CreateEntity.java | 66 ++++ .../{EventCalendar.java => CreateEvent.java} | 21 +- .../ical4j/command/strategy/CreateIssue.java | 69 +++++ .../ical4j/command/strategy/CreateNote.java | 71 +++++ .../command/strategy/CreateObservance.java | 68 +++++ .../strategy/StrategyCommandGroup.java | 5 +- .../ical4j/command/strategy/StrategyInfo.java | 79 +++++ .../workspace/AbstractWorkspaceCommand.java | 10 + .../command/workspace/ActivateWorkspace.java | 50 +++ .../command/workspace/CreateCollection.java | 57 ---- .../command/workspace/DeleteCollection.java | 53 ---- .../command/workspace/ListCollections.java | 39 --- .../command/workspace/ListWorkspaces.java | 69 +++++ .../command/workspace/PrintWorkspace.java | 80 +++++ .../workspace/WorkspaceCommandGroup.java | 7 +- .../src/main/resources/config.default.toml | 10 + .../org/ical4j/command/ListCommandTest.groovy | 50 +++ .../calendar/CreateCalendarTest.groovy | 2 +- .../calendar/DeleteCalendarTest.groovy | 2 +- .../command/calendar/GetCalendarTest.groovy | 2 +- .../calendar/UpdateCalendarTest.groovy | 1 - .../{vcard => card}/CreateCardTest.groovy | 4 +- .../{vcard => card}/DeleteCardTest.groovy | 4 +- .../{vcard => card}/GetCardTest.groovy | 4 +- .../{vcard => card}/UpdateCardTest.groovy | 3 +- ...onTest.groovy => AddCollectionTest.groovy} | 6 +- .../GetCollectionDetailsTest.groovy | 2 +- ...est.groovy => RemoveCollectionTest.groovy} | 6 +- ical4j-command-core/build.gradle | 12 + .../src/main/java/module-info.java | 10 + .../org/ical4j/command/core/EntityType.java | 53 ++++ .../java/org/ical4j/command/core/Locus.java | 46 +++ .../org/ical4j/command/core/SendMethod.java | 49 +++ .../command/core/SerializationFamily.java | 47 +++ .../java/org/ical4j/command/core/Verb.java | 85 ++++++ ical4j-command-mcp/build.gradle | 17 ++ .../ical4j/command/mcp/CalendarService.java | 38 +++ .../ical4j/command/mcp/ChannelService.java | 38 +++ .../ical4j/command/mcp/CollectionService.java | 115 +++++++ .../org/ical4j/command/mcp/EntityService.java | 47 +++ .../org/ical4j/command/mcp/StoreService.java | 57 ++++ .../ical4j/command/mcp/WorkspaceService.java | 65 ++++ .../command/mcp/iCal4jMcpServerMain.java | 52 ++++ .../src/main/resources/application.properties | 11 + .../.openspec.yaml | 2 + .../README.md | 3 + .../design.md | 60 ++++ .../proposal.md | 31 ++ .../specs/command-vocabulary/spec.md | 112 +++++++ .../tasks.md | 40 +++ openspec/config.yaml | 20 ++ openspec/specs/command-vocabulary/spec.md | 121 ++++++++ settings.gradle | 3 +- 128 files changed, 4903 insertions(+), 457 deletions(-) create mode 100644 .claude/commands/opsx/apply.md create mode 100644 .claude/commands/opsx/archive.md create mode 100644 .claude/commands/opsx/explore.md create mode 100644 .claude/commands/opsx/propose.md create mode 100644 .claude/skills/openspec-apply-change/SKILL.md create mode 100644 .claude/skills/openspec-archive-change/SKILL.md create mode 100644 .claude/skills/openspec-explore/SKILL.md create mode 100644 .claude/skills/openspec-propose/SKILL.md create mode 100644 docs/command-protocols.md create mode 100644 ical4j-command-agent/build.gradle rename ical4j-command-cli/src/main/java/org/ical4j/command/config/StoreConfiguration.java => ical4j-command-agent/src/main/java/org/ical4j/command/agent/CalendarUserAgent.java (66%) create mode 100644 ical4j-command-agent/src/main/java/org/ical4j/command/agent/CalendarValidator.java create mode 100644 ical4j-command-agent/src/main/java/org/ical4j/command/agent/EntityBuilder.java create mode 100644 ical4j-command-agent/src/main/java/org/ical4j/command/agent/EventGenerator.java create mode 100644 ical4j-command-agent/src/test/groovy/org/ical4j/command/agent/CalendarValidatorTest.groovy delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/ConfigureCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/CopyCommand.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection/CreateEvent.java => DeleteCommand.java} (80%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/EditCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/ExportCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/ImportCalendars.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => }/ImportCards.java (91%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/ListCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/MoveCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/OutputOptions.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/PrintCommand.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => calendar}/DeleteCalendar.java (88%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => calendar}/GetCalendar.java (87%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => calendar}/UpdateCalendar.java (87%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{vcard/AbstractCardCommand.java => card/AbstractEntityCommand.java} (54%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{entity => card}/CardCommandGroup.java (83%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => card}/DeleteCard.java (88%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => card}/GetCard.java (86%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{vcard => card}/Serializer.java (90%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => card}/UpdateCard.java (84%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/collection/AddCollection.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{workspace => collection}/GetCollectionDetails.java (93%) delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/collection/ImportCalendars.java delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/collection/ListCalendars.java delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/collection/QueryCollection.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/collection/RemoveCollection.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{workspace => collection}/UpdateCollection.java (87%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/config/CommandConfig.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/config/ConfigureCommand.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/AddAttachment.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/AddCategories.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/AddCommandGroup.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/AddComment.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/AddRelated.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/GetProperty.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/PropertyCommandGroup.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/SetCommandGroup.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/metadata/UnsetProperty.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/revisions/ListRevisions.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/revisions/RevertRevision.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/revisions/UndoRevisions.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/CreateAction.java rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => strategy}/CreateCalendar.java (92%) rename ical4j-command-cli/src/main/java/org/ical4j/command/{collection => strategy}/CreateCard.java (84%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/CreateEntity.java rename ical4j-command-cli/src/main/java/org/ical4j/command/strategy/{EventCalendar.java => CreateEvent.java} (59%) create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/CreateIssue.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/CreateNote.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/CreateObservance.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/strategy/StrategyInfo.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/ActivateWorkspace.java delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/CreateCollection.java delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/DeleteCollection.java delete mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/ListCollections.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/ListWorkspaces.java create mode 100644 ical4j-command-cli/src/main/java/org/ical4j/command/workspace/PrintWorkspace.java create mode 100644 ical4j-command-cli/src/main/resources/config.default.toml create mode 100644 ical4j-command-cli/src/test/groovy/org/ical4j/command/ListCommandTest.groovy rename ical4j-command-cli/src/test/groovy/org/ical4j/command/{vcard => card}/CreateCardTest.groovy (88%) rename ical4j-command-cli/src/test/groovy/org/ical4j/command/{vcard => card}/DeleteCardTest.groovy (88%) rename ical4j-command-cli/src/test/groovy/org/ical4j/command/{vcard => card}/GetCardTest.groovy (88%) rename ical4j-command-cli/src/test/groovy/org/ical4j/command/{vcard => card}/UpdateCardTest.groovy (89%) rename ical4j-command-cli/src/test/groovy/org/ical4j/command/collection/{CreateCollectionTest.groovy => AddCollectionTest.groovy} (65%) rename ical4j-command-cli/src/test/groovy/org/ical4j/command/collection/{DeleteCollectionTest.groovy => RemoveCollectionTest.groovy} (76%) create mode 100644 ical4j-command-core/build.gradle create mode 100644 ical4j-command-core/src/main/java/module-info.java create mode 100644 ical4j-command-core/src/main/java/org/ical4j/command/core/EntityType.java create mode 100644 ical4j-command-core/src/main/java/org/ical4j/command/core/Locus.java create mode 100644 ical4j-command-core/src/main/java/org/ical4j/command/core/SendMethod.java create mode 100644 ical4j-command-core/src/main/java/org/ical4j/command/core/SerializationFamily.java create mode 100644 ical4j-command-core/src/main/java/org/ical4j/command/core/Verb.java create mode 100644 ical4j-command-mcp/build.gradle create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/CalendarService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/ChannelService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/CollectionService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/EntityService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/StoreService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/WorkspaceService.java create mode 100644 ical4j-command-mcp/src/main/java/org/ical4j/command/mcp/iCal4jMcpServerMain.java create mode 100644 ical4j-command-mcp/src/main/resources/application.properties create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/.openspec.yaml create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/README.md create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/design.md create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/proposal.md create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/specs/command-vocabulary/spec.md create mode 100644 openspec/changes/archive/2026-06-06-unify-command-vocabulary/tasks.md create mode 100644 openspec/config.yaml create mode 100644 openspec/specs/command-vocabulary/spec.md diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md new file mode 100644 index 0000000..ae14f0f --- /dev/null +++ b/.claude/commands/opsx/apply.md @@ -0,0 +1,152 @@ +--- +name: "OPSX: Apply" +description: Implement tasks from an OpenSpec change (Experimental) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). + +2. **Check status to understand the schema** + ```bash + openspec status --change "" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "" --json + ``` + + This returns: + - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read every file path listed under `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: (schema: ) + +Working on task 3/7: +[...implementation happening...] +✓ Task complete + +Working on task 4/7: +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** +**Schema:** +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** +**Schema:** +**Progress:** 4/7 tasks complete + +### Issue Encountered + + +**Options:** +1.