From ec631646930a55cba93410ab2b48c043f5f8d25b Mon Sep 17 00:00:00 2001 From: bitc0der <59016822+bitc0der@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:24:03 +0700 Subject: [PATCH 1/8] Add recovery config spec --- .../per-place-recovery-options/.openspec.yaml | 2 + .../per-place-recovery-options/design.md | 71 ++++++++ .../per-place-recovery-options/proposal.md | 45 +++++ .../specs/connection-recovery/spec.md | 170 ++++++++++++++++++ .../specs/opentelemetry-metrics/spec.md | 6 + .../per-place-recovery-options/tasks.md | 50 ++++++ 6 files changed, 344 insertions(+) create mode 100644 openspec/changes/per-place-recovery-options/.openspec.yaml create mode 100644 openspec/changes/per-place-recovery-options/design.md create mode 100644 openspec/changes/per-place-recovery-options/proposal.md create mode 100644 openspec/changes/per-place-recovery-options/specs/connection-recovery/spec.md create mode 100644 openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md create mode 100644 openspec/changes/per-place-recovery-options/tasks.md diff --git a/openspec/changes/per-place-recovery-options/.openspec.yaml b/openspec/changes/per-place-recovery-options/.openspec.yaml new file mode 100644 index 0000000..db47328 --- /dev/null +++ b/openspec/changes/per-place-recovery-options/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-02 diff --git a/openspec/changes/per-place-recovery-options/design.md b/openspec/changes/per-place-recovery-options/design.md new file mode 100644 index 0000000..787796a --- /dev/null +++ b/openspec/changes/per-place-recovery-options/design.md @@ -0,0 +1,71 @@ +## Context + +Connection recovery (PR archived 2026-05-24) introduced two deliberately asymmetric decisions: + +1. **Retry logic is per-plugin.** Each plugin that owns a reconnect loop (Postgres LISTEN, Kafka publisher/consumer) hand-rolls ~20 lines of exponential-backoff math. There is **no** shared `ConnectionRetry` helper in Core — extracting one was explicitly rejected because it would force `InternalsVisibleTo` exposure (plugins reaching into Core internals) or a public-API commitment to one retry shape. The exception classifiers (`PostgresFault.IsConnectionFault`, Kafka's `Error.IsFatal` check) are likewise per-plugin. + +2. **Config shape is shared.** A single `ConnectionRecoveryOptions` record lives in `RayTree.Core.Resilience` and is referenced by `NotificationBasedPublisherOptions`, `KafkaPublisherOptions`, and `KafkaConsumerOptions`. `RayTree.Hosting` binds it generically from `ChangeTracking:{Publisher,Subscriber}:ConnectionRecovery` into named options via `ChangeTrackingRecoveryKeys`. + +The asymmetry is the problem. The shared record is the *only* remaining config coupling from Core to recovery-capable plugins, and it sits directly on top of duplicated loops that consume it. It also leaks: RabbitMQ never references it (SDK owns recovery), and `Factor`/`MaxAttempts`/`InitialDelay` are only meaningful to a hand-rolled exponential loop — a future library-managed plugin would inherit fields that silently do nothing. The host-bound default is also notional: `AddChangeTracking` binds the record into named options, but nothing auto-injects it into plugin construction — callers must resolve `IOptionsMonitor.Get(key)` and merge by hand (documented in `ChangeTrackingRecoveryKeys`). So the "shared default" buys almost nothing in practice. + +This change moves the config next to the loop that consumes it, completing decision (1)'s logic: if the loop is per-plugin, the loop's tuning record should be too. + +## Goals / Non-Goals + +**Goals:** +- Remove `ConnectionRecoveryOptions` from `RayTree.Core`. Give each recovery-owning plugin its own options type carrying only the fields its loop uses: `PostgresConnectionRecoveryOptions` (PostgreSQL), `KafkaConnectionRecoveryOptions` (Kafka). +- Repoint the three plugin options classes' `ConnectionRecovery` property to the plugin-local type, preserving field names, defaults, and validation semantics (type-identity move, not behavioral). +- Remove the Hosting generic binding (`ChangeTrackingRecoveryKeys` + the two `Configure` calls), since the shared type it bound no longer exists and the merge was manual anyway. +- Remove the connection-recovery **metrics** entirely: all four `raytree.connection.*` instruments, the three `RayTreeMeter` facade methods, the internal state-gauge registry, and every emission call site. The other `RayTreeMeter` instruments stay; `RayTreeMeter` itself stays a required constructor dependency where it already was (it still serves outbox/subscriber metrics). +- Preserve all recovery *behavior* and *logs*: reconnect loops, exception classifiers, the `IOutbox` connection-fault members, the `Error→Warning` outbox log demotion, and every recovery log entry are untouched. + +**Non-Goals:** +- Changing the retry/backoff math, the exception classifiers, or any log levels/messages (beyond deleting the now-orphaned metric calls inside otherwise-unchanged log handlers). +- Removing recovery *behavior* or recovery *logs*. Only metric emission is removed. +- Adding per-plugin configuration binding in Hosting (callers bind their own sections, or configure in code). +- Extracting a shared backoff helper or value type. (See Decisions — explicitly rejected to avoid re-introducing the coupling we're removing.) +- Removing other `RayTreeMeter` instruments or the meter itself. + +## Decisions + +### 1. Two plugin-local options types, not one shared and not one per consumer + +`RayTree.Plugins.PostgreSQL` defines `PostgresConnectionRecoveryOptions`; `RayTree.Plugins.Kafka` defines `KafkaConnectionRecoveryOptions`. Both are near-verbatim copies of the current `ConnectionRecoveryOptions` (same six members, same per-field init guards, same `Validate()` cross-field check). + +- **Why two and not three** (publisher vs consumer in Kafka): both Kafka loops have identical tuning needs and live in the same assembly. One `KafkaConnectionRecoveryOptions` shared by `KafkaPublisherOptions` and `KafkaConsumerOptions` keeps the Kafka surface DRY *within its own boundary* — that's intra-assembly reuse, not cross-package coupling, so it's free. +- **Why copy the validation** rather than share a Core helper: the project already accepted this exact trade for the retry loop ("two short copies is cheaper than `InternalsVisibleTo` or a public-API retry-shape commitment"). The validated options record is ~90 lines of pure, stable data with no plugin-specific branching; duplicating it across two assemblies is the same honest cost as the duplicated loops it pairs with. **Alternative considered:** a shared public `BackoffSchedule` value type in Core. Rejected *for this change* — it re-introduces the Core→plugin type dependency we're removing, just under a different name. If a third recovery-owning plugin ever lands and a true common shape is proven, extract then (YAGNI). + +### 2. Keep field names and defaults identical + +`Enabled=true`, `InitialDelay=1s`, `MaxDelay=30s`, `Factor=2.0`, `JitterFraction=0.2`, `MaxAttempts=null`. Validation: `InitialDelay>0`, `MaxDelay>=InitialDelay` (via `Validate()`), `Factor>=1`, `0<=JitterFraction<=1`, `MaxAttempts==null||>0`. Identical names mean the appsettings *keys* a caller binds don't change — only the section's parent and the bound CLR type do. Identical defaults mean no behavioral drift for callers who never set the options. + +### 3. Remove Hosting binding outright (no replacement plumbing) + +Delete `ChangeTrackingRecoveryKeys` and the two `services.Configure(...)` calls in `ServiceCollectionExtensions`. Callers who want config-driven recovery set it on the plugin options directly in the `UseKafka`/`UsePostgreSqlOutbox` configure lambda, optionally reading their own bound section. **Why no per-plugin binding:** wiring bound defaults into plugin construction would require `IServiceProvider` plumbing through every plugin builder-extension signature — out of scope when this change was archived, and still out of scope. The manual-merge pattern the old keys documented had no automatic effect; removing it loses no working behavior. + +### 4. Remove the connection metrics, keep the logs + +The connection-recovery metrics were the *second* piece of Core that plugins reach into (via the `RayTreeMeter` facade). Removing them alongside the config completes the decoupling: after this change, recovery is observed through logs only, and plugins touch Core's recovery surface not at all. + +- **All four instruments go** (`disconnects`, `recoveries`, `recovery.duration`, `state`) plus `RecordConnectionDisconnect` / `RecordConnectionRecovery` / `RegisterConnectionStateGauge`, the `_connectionStateSources` list, the `_connectionStateGate` lock, the `ObserveConnectionStates` callback, and the `ConnectionStateSubscription` nested type. +- **Logs are the surviving signal.** Postgres/Kafka already log retry attempts, recovery, and exhaustion at `Information`/`Error`; RabbitMQ publisher logs `Warning` on shutdown and `Information` on recovery. None of that changes. The `Error→Warning` outbox-batch demotion (driven by `IOutbox.IsConnectionFault`) also stays — it is a logging behavior, not a metric one, and the classifier members it depends on remain. +- **RabbitMQ is asymmetric and must be handled as two cases:** + - *Publisher* keeps all three event handlers — they emit the recovery logs. Strip only the `_meter` field, the constructor `RayTreeMeter?` parameter, the gauge registration, and the two `RecordConnection*` calls. The `_lastShutdownAt` duration tracking stays (the recovery log prints `{Duration}`). + - *Consumer* has **no logger**; its event subscriptions existed solely to feed metrics. Remove the `ConnectionShutdownAsync`/`RecoverySucceededAsync` subscriptions, their handlers, the gauge registration, and the `RayTreeMeter? meter` constructor parameter outright. The consumer stops observing recovery entirely — which is acceptable because it never produced any operator-visible signal except the metrics now being removed. +- **`OutboxPublisherService` / `NotificationBasedPublisher`** keep their `_outboxUnhealthy` / per-outbox unhealthy-state tracking because it still gates the log demotion and the single "outbox connection recovered" `Information` log. Only the `RecordConnectionDisconnect` / `RecordConnectionRecovery` calls are deleted from those paths. `RayTreeMeter` remains a required constructor parameter on both (still used for outbox metrics). +- **Builder/extension plumbing:** the RabbitMQ builder and subscriber extensions stop forwarding a `RayTreeMeter` into the publisher/consumer constructors. Wherever the meter was passed only for connection metrics, the argument is dropped. + +**Alternative considered:** keep the instruments but stop emitting (no-op). Rejected — dead instruments on the public meter mislead operators into thinking the series exist; a clean removal is honest and shrinks `RayTreeMeter`. **Alternative considered:** move the metrics per-plugin (each plugin owns its own meter). Rejected — the user's intent is removal, not relocation, and per-plugin meters would fragment the single `"RayTree"` meter that the OTel integration deliberately exposes. + +### 5. Test relocation + +`ConnectionRecoveryOptionsTests` (validation/defaults) splits into the PostgreSQL and Kafka test projects, asserting against the plugin-local type. `ConnectionRecoveryConfigurationTests` (Hosting binding) is removed — the binding it tested is gone. `NotificationBasedPublisherRecoveryTests` and the Kafka recovery tests update the referenced type name only. + +## Risks / Trade-offs + +- **[Loss of metric-based recovery observability]** → Deliberate, per the request. Operators relying on `raytree.connection.*` dashboards/alerts lose them; mitigation is the retained log signal (recovery logs at `Warning`/`Information`/`Error` with `{Component}`/`{Endpoint}`/`{Duration}`). Call this out prominently in CHANGELOG as a breaking observability change, since it fails silently — a metric that stops existing produces no error, just a flat-lined chart. The RabbitMQ *consumer* becomes entirely unobservable for recovery (no logger, no metrics); accept, as it matches its pre-existing no-logger posture. +- **[Breaking change for callers referencing the type or the bound config sections]** → Migration is mechanical: rename `ConnectionRecoveryOptions` → `PostgresConnectionRecoveryOptions` / `KafkaConnectionRecoveryOptions` at the use site; move `ChangeTracking:Publisher:ConnectionRecovery` appsettings under the plugin's own options binding (or set in code). Field names are unchanged, so JSON key paths below the section parent are unaffected. Documented in CHANGELOG with a before/after snippet. +- **[Removing the meter param from RabbitMQ constructors is a source break for direct constructor callers]** → The param was optional (`RayTreeMeter? meter = null`); most callers go through the builder extensions and are unaffected. Direct constructor callers drop the argument. Documented in CHANGELOG. +- **[Validation logic now lives in two files; a future fix could be applied to one and missed in the other]** → Accept, consistent with the existing duplicated-loop decision. The records are stable (no churn since introduction). A shared unit-test helper asserting the invariant set can run against both types to catch drift without sharing production code. +- **[Loss of a single cross-plugin "recovery default" knob]** → Accept. The knob was manual-merge-only and never auto-applied; no caller relied on it taking effect without writing the merge themselves. Per-plugin configuration is more explicit and matches how callers already tune brokers individually. +- **[Two new public types increase the plugin API surface]** → Net public surface is roughly flat: one Core type + one Hosting type removed, two plugin types added. Each new type lives in the assembly that owns its behavior, which is the point. diff --git a/openspec/changes/per-place-recovery-options/proposal.md b/openspec/changes/per-place-recovery-options/proposal.md new file mode 100644 index 0000000..6025764 --- /dev/null +++ b/openspec/changes/per-place-recovery-options/proposal.md @@ -0,0 +1,45 @@ +## Why + +`RayTree.Core` owns a shared `ConnectionRecoveryOptions` record consumed by the Postgres and Kafka plugins. This is the **last remaining piece of config coupling** between Core and the plugins that own a reconnect loop — and it sits awkwardly next to a design decision that already went the other way: the retry *loop* is deliberately **not** shared (each plugin hand-rolls ~20 lines so plugins consume Core via its public API only). The shared options record contradicts that stance: it makes every recovery-capable plugin depend on the exact shape of a Core type, even though the "universal" record is already not universal — RabbitMQ ignores it entirely, and fields like `Factor`/`MaxAttempts` are meaningless to any plugin that doesn't hand-roll an exponential loop. Moving the options next to the loop that consumes them makes the coupling honest and lets each plugin evolve its recovery surface independently. + +## What Changes + +### Per-place recovery options + +- **BREAKING** — Remove `ConnectionRecoveryOptions` from `RayTree.Core` (`src/RayTree.Core/Resilience/`). +- Each plugin that owns a reconnect loop defines its **own** recovery options type in its own assembly, carrying only the fields that loop actually uses: + - `RayTree.Plugins.PostgreSQL` → `PostgresConnectionRecoveryOptions` (consumed by `NotificationBasedPublisher`'s LISTEN reconnect). + - `RayTree.Plugins.Kafka` → `KafkaConnectionRecoveryOptions` (consumed by both `KafkaPublisher` and `KafkaConsumer` rebuild loops). +- Repoint the `ConnectionRecovery` property on `NotificationBasedPublisherOptions`, `KafkaPublisherOptions`, and `KafkaConsumerOptions` from the Core type to the plugin-local type. Field names, defaults, and validation semantics are preserved so this is a type-identity break, not a behavioral one. +- **BREAKING** — `RayTree.Hosting` drops the generic shared-type binding: `ChangeTrackingRecoveryKeys` and the two `services.Configure(...)` calls are removed. The host no longer binds a single cross-plugin recovery default; callers configure recovery per plugin in code (or bind their own sections to the plugin-local type). This removes `IServiceProvider` plumbing that was never wired into plugin construction anyway — the bound defaults required callers to merge them manually, so the convenience was largely notional. + +### Remove connection-recovery metrics + +- **BREAKING** — Remove all four connection-recovery metric instruments from `RayTreeMeter` and the three public facade methods that emit them: `raytree.connection.disconnects`, `raytree.connection.recoveries`, `raytree.connection.recovery.duration`, `raytree.connection.state` (observable gauge), and `RecordConnectionDisconnect` / `RecordConnectionRecovery` / `RegisterConnectionStateGauge` (plus the internal `_connectionStateSources` registry and `ConnectionStateSubscription`). The other `RayTreeMeter` instruments (outbox, subscriber, etc.) are untouched. +- Remove every metric-emission call site across the plugins: `OutboxPublisherService`, `NotificationBasedPublisher`, `KafkaPublisher`, `KafkaConsumer`, `RabbitMqPublisher`, `RabbitMqConsumer`. +- **`RabbitMqConsumer`** subscribed to the SDK recovery events *only* to emit metrics (it has no logger); those event subscriptions and its `RayTreeMeter? meter` constructor parameter are removed entirely. +- **`RabbitMqPublisher`** keeps its `ConnectionShutdownAsync` / `RecoverySucceededAsync` / `ConnectionRecoveryErrorAsync` handlers because they also emit `Warning`/`Information` recovery **logs** — only the `_meter` field, its constructor `RayTreeMeter?` parameter, the state-gauge registration, and the `RecordConnection*` calls are removed. The duration tracking stays (still used in the log message). +- **Behavior and logs are otherwise unchanged.** All reconnect loops (Postgres LISTEN, Kafka rebuild), the `IOutbox` connection-fault classification (`IsConnectionFault` / `ConnectionComponent` / `ConnectionEndpoint`), the `Error→Warning` outbox log demotion, and every recovery log entry are retained. This change removes observability-via-metrics only. +- RabbitMQ still exposes no recovery options (SDK-owned). + +## Capabilities + +### New Capabilities + + +### Modified Capabilities +- `connection-recovery`: (a) the options-shape, options-exposure, and config-binding requirements change — the validated backoff record moves out of Core to per-plugin types and the Hosting binding is removed; (b) the requirements that emit connection metrics change — `NotificationBasedPublisher` reconnect, Kafka publisher/consumer rebuild, "RabbitMQ recovery is observed", "Outbox connection-fault observability", and "Recovery logs are emitted at documented levels" drop all metric clauses while retaining their reconnect/log behavior. +- `opentelemetry-metrics`: the "Connection-recovery instruments are emitted" requirement is removed — those four instruments no longer exist. + +## Impact + +- **Public API (breaking):** + - Removed: `RayTree.Core.Resilience.ConnectionRecoveryOptions`, `RayTree.Hosting.ChangeTrackingRecoveryKeys`, and the `ChangeTracking:*:ConnectionRecovery` configuration binding. + - Removed: `RayTreeMeter.RecordConnectionDisconnect`, `RecordConnectionRecovery`, `RegisterConnectionStateGauge`, and the four `raytree.connection.*` instruments. + - Removed: the `RayTreeMeter?` constructor parameter on `RabbitMqConsumer` and `RabbitMqPublisher` (and the corresponding meter forwarding in the RabbitMQ builder/subscriber extensions). + - Added: `PostgresConnectionRecoveryOptions` (PostgreSQL plugin), `KafkaConnectionRecoveryOptions` (Kafka plugin). + - Changed property types: `ConnectionRecovery` on the three plugin options classes now returns the plugin-local type. +- **Affected packages:** `RayTree.Core` (type + instrument removal), `RayTree.Plugins.PostgreSQL` (new type, drop metric calls), `RayTree.Plugins.Kafka` (new type, drop metric calls), `RayTree.Plugins.RabbitMQ` (drop meter param + consumer event handlers, keep publisher logs), `RayTree.Hosting` (binding removal). No new dependencies. +- **Observability change:** dashboards/alerts built on any `raytree.connection.*` series break — those series cease to exist. Disconnect/recovery visibility is now log-only (Postgres/Kafka retry + recovery logs; RabbitMQ publisher Warning/Information; RabbitMQ consumer becomes silent, as before for logs). The `IOutbox` connection-fault members and the `Error→Warning` log demotion remain. +- **Callers** referencing `ConnectionRecoveryOptions`, the host-bound config sections, or the removed meter facade methods must update. Type migration is mechanical; metric consumers must drop the series. +- **Tests:** delete `RecoveryMetricsTests` and `RabbitMqRecoveryMetricsTests`; strip metric assertions from `KafkaRecoveryMetricsTests`, `OutboxPublisherServiceConnectionFaultTests`, and `NotificationBasedPublisherRecoveryTests` (keep their log/behavior assertions); split `ConnectionRecoveryOptionsTests` into the plugin test projects; delete `ConnectionRecoveryConfigurationTests`. CHANGELOG, CLAUDE.md, AGENTS.md, `docs/opentelemetry-metrics.md`, and plugin READMEs updated. diff --git a/openspec/changes/per-place-recovery-options/specs/connection-recovery/spec.md b/openspec/changes/per-place-recovery-options/specs/connection-recovery/spec.md new file mode 100644 index 0000000..6212b0d --- /dev/null +++ b/openspec/changes/per-place-recovery-options/specs/connection-recovery/spec.md @@ -0,0 +1,170 @@ +## MODIFIED Requirements + +### Requirement: Connection recovery options shape +Each plugin that owns a connection-recovery loop SHALL define its **own** public recovery-options type in its own assembly; `RayTree.Core` SHALL NOT define a shared recovery-options type. `RayTree.Plugins.PostgreSQL` SHALL expose `PostgresConnectionRecoveryOptions`; `RayTree.Plugins.Kafka` SHALL expose `KafkaConnectionRecoveryOptions`. Each type SHALL carry the fields `Enabled` (bool, default `true`), `InitialDelay` (TimeSpan, default `1s`), `MaxDelay` (TimeSpan, default `30s`), `Factor` (double, default `2.0`), `JitterFraction` (double, default `0.2`), and `MaxAttempts` (int?, default `null` — unlimited). Each type SHALL validate per-field invariants at init time — `InitialDelay > TimeSpan.Zero`, `Factor >= 1.0`, `0 <= JitterFraction <= 1`, and `MaxAttempts == null || MaxAttempts > 0` — throwing `ArgumentOutOfRangeException`, and SHALL expose a `Validate()` method enforcing the cross-field invariant `MaxDelay >= InitialDelay` (also throwing `ArgumentOutOfRangeException`), which the consuming plugin calls before entering its retry loop. + +#### Scenario: Defaults match documented values +- **WHEN** `new PostgresConnectionRecoveryOptions()` or `new KafkaConnectionRecoveryOptions()` is constructed with no overrides +- **THEN** the field values SHALL be exactly `Enabled = true`, `InitialDelay = 1s`, `MaxDelay = 30s`, `Factor = 2.0`, `JitterFraction = 0.2`, `MaxAttempts = null`. + +#### Scenario: Invalid factor is rejected +- **WHEN** `Factor = 0.5` is supplied to either plugin-local options type +- **THEN** construction SHALL throw `ArgumentOutOfRangeException` with `paramName = "Factor"`. + +#### Scenario: Cross-field invariant is rejected on Validate +- **WHEN** an options instance with `MaxDelay < InitialDelay` is constructed AND `Validate()` is called +- **THEN** `Validate()` SHALL throw `ArgumentOutOfRangeException` with `paramName = "MaxDelay"`. + +#### Scenario: Core exposes no shared recovery-options type +- **WHEN** a caller inspects `RayTree.Core` (e.g. via reflection or autocomplete) +- **THEN** no `ConnectionRecoveryOptions` type SHALL be present in the `RayTree.Core.Resilience` namespace +- **AND** the connection-metric facade (`RayTreeMeter.RecordConnectionDisconnect` / `RecordConnectionRecovery` / `RegisterConnectionStateGauge`) SHALL remain unchanged. + +#### Scenario: Disabled options short-circuit recovery +- **WHEN** `Enabled = false` AND the owning Postgres or Kafka plugin observes a connection-fault exception +- **THEN** the plugin SHALL surface the exception on the next caller-facing call (or, for the Kafka consumer poll thread, on the next iteration) — no rebuild attempt SHALL be made. + +### Requirement: Recovery options exposure on plugin options classes +`NotificationBasedPublisherOptions` SHALL expose a `ConnectionRecovery` property of type `PostgresConnectionRecoveryOptions` initialised to `new PostgresConnectionRecoveryOptions()`. `KafkaPublisherOptions` and `KafkaConsumerOptions` SHALL each expose a `ConnectionRecovery` property of type `KafkaConnectionRecoveryOptions` initialised to `new KafkaConnectionRecoveryOptions()`. `RabbitMqPublisherOptions` and `RabbitMqConsumerOptions` SHALL NOT expose this property — RabbitMQ recovery is owned by the SDK and is not configurable through RayTree. + +#### Scenario: Default property value is enabled +- **WHEN** `NotificationBasedPublisherOptions`, `KafkaPublisherOptions`, or `KafkaConsumerOptions` is constructed via its parameterless constructor +- **THEN** `ConnectionRecovery.Enabled` SHALL be `true` and the other fields SHALL match the documented defaults. + +#### Scenario: Property type is the plugin-local options type +- **WHEN** a caller reads `KafkaPublisherOptions.ConnectionRecovery` +- **THEN** the value SHALL be a `KafkaConnectionRecoveryOptions` instance +- **AND** reading `NotificationBasedPublisherOptions.ConnectionRecovery` SHALL yield a `PostgresConnectionRecoveryOptions` instance. + +#### Scenario: RabbitMQ options do not expose ConnectionRecovery +- **WHEN** a caller inspects `RabbitMqPublisherOptions` / `RabbitMqConsumerOptions` via reflection or autocomplete +- **THEN** no `ConnectionRecovery` property SHALL be present +- **AND** the SDK's recovery options (e.g. `NetworkRecoveryInterval`) remain accessible through the underlying `ConnectionFactory` only if the caller constructs one explicitly. + +### Requirement: PostgreSQL NotificationBasedPublisher reconnects LISTEN +`NotificationBasedPublisher` SHALL, upon catching an exception from `NpgsqlConnection.WaitAsync` for which `IsConnectionFault(ex)` returns `true`, dispose the broken connection and run an inline exponential-backoff reconnect loop bounded by `NotificationBasedPublisherOptions.ConnectionRecovery`. The loop SHALL open a fresh `NpgsqlConnection`, re-attach the `Notification` event handler, issue `LISTEN {ChannelName}`, and resume `WaitAsync` against the new connection. The fallback polling loop SHALL continue running throughout, providing the safety net for records written during reconnect. + +`IsConnectionFault` for the Postgres plugin SHALL return `true` for: `NpgsqlException` with `IsTransient = true`; `NpgsqlException` whose inner exception is `SocketException` or `IOException`; `PostgresException` with `SqlState` in `{"57P01", "57P02", "57P03"}` (admin_shutdown, crash_shutdown, cannot_connect_now); and `ObjectDisposedException`. All other exceptions SHALL propagate without triggering reconnect. + +#### Scenario: LISTEN connection drop triggers reconnect +- **WHEN** `WaitAsync` throws an exception that `IsConnectionFault` classifies as a connection fault +- **THEN** the loop SHALL dispose the broken connection, open a fresh `NpgsqlConnection`, issue `LISTEN {ChannelName}`, and resume `WaitAsync` against the new connection. + +#### Scenario: Fallback polling continues during reconnect +- **WHEN** the LISTEN connection is being rebuilt +- **THEN** `FallbackPollingLoopAsync` SHALL continue processing unpublished records at `FallbackPollingInterval` cadence until `_listenerHealthy` returns to `true`. + +#### Scenario: Non-connection exception propagates +- **WHEN** `WaitAsync` throws an exception that `IsConnectionFault` returns `false` for (e.g. an unexpected internal error) +- **THEN** the exception SHALL propagate out of `ListenLoopAsync` — no reconnect SHALL be attempted. + +#### Scenario: Recovery is logged at Information +- **WHEN** reconnect completes successfully +- **THEN** the existing `Information` "LISTEN connection on {ChannelName} recovered" log SHALL be emitted (unchanged from current behaviour) +- **AND** no connection metric SHALL be recorded (the `raytree.connection.*` instruments no longer exist). + +### Requirement: Kafka publisher rebuilds on fatal error +`KafkaPublisher` SHALL register `IProducerBuilder.SetErrorHandler` during producer construction. When the handler receives an error with `Error.IsFatal = true`, the publisher SHALL dispose the current `IProducer` and null out the cached reference. The next `PublishAsync` call SHALL re-enter the existing lazy `GetProducerAsync` build path — which re-runs the `WaitForTopic` probe when enabled — bounded by `KafkaPublisherOptions.ConnectionRecovery`. Non-fatal errors SHALL NOT trigger rebuild; librdkafka recovers those internally. No connection metric SHALL be emitted (the `raytree.connection.*` instruments no longer exist). + +#### Scenario: Fatal error disposes the producer +- **WHEN** the error handler receives an error with `Error.IsFatal = true` +- **THEN** the cached `IProducer` SHALL be disposed and the cached reference SHALL be set to `null` +- **AND** no connection metric SHALL be recorded. + +#### Scenario: Next publish rebuilds via existing path +- **WHEN** a subsequent `PublishAsync` is invoked after a fatal-error dispose +- **THEN** the existing `GetProducerAsync` path SHALL build a fresh producer +- **AND** the probe SHALL re-run when `WaitForTopic = true`. + +#### Scenario: Non-fatal error is ignored +- **WHEN** the error handler receives a non-fatal error +- **THEN** the publisher SHALL log at `Warning` and SHALL NOT dispose the producer. + +### Requirement: RabbitMQ recovery is observed via logs, not metrics +The RabbitMQ publisher and consumer SHALL rely on `RabbitMQ.Client.AutomaticRecoveryEnabled = true` and `TopologyRecoveryEnabled = true` (the library defaults) for the actual recovery mechanism. RayTree SHALL NOT disable these flags. + +The `RabbitMqPublisher` SHALL subscribe to the SDK's `ConnectionShutdownAsync`, `RecoverySucceededAsync`, and `ConnectionRecoveryErrorAsync` events to emit the documented log entries (`Warning` on non-application shutdown, `Information` on recovery with elapsed `{Duration}`, `Information` per failed internal recovery attempt). It SHALL NOT emit any connection metric and SHALL NOT accept a `RayTreeMeter` constructor parameter. + +The `RabbitMqConsumer` has no logger and therefore SHALL NOT subscribe to any recovery event: with the connection metrics removed, those subscriptions produced no operator-visible signal. The consumer SHALL NOT accept a `RayTreeMeter` constructor parameter. + +#### Scenario: Publisher logs a Warning on non-application shutdown +- **WHEN** the `RabbitMqPublisher`'s `IConnection` raises `ConnectionShutdownAsync` with `Initiator != Application` +- **THEN** a `Warning` log SHALL be emitted with the shutdown reason, `{Endpoint}`, `{ReplyCode}`, and `{ReplyText}` +- **AND** no metric SHALL be recorded. + +#### Scenario: Publisher logs Information on recovery +- **WHEN** the publisher's `IConnection` raises `RecoverySucceededAsync` +- **THEN** an `Information` log SHALL be emitted with `{Endpoint}` and `{Duration}` (seconds since the most recent non-application shutdown) +- **AND** no metric SHALL be recorded. + +#### Scenario: Application-initiated shutdown is silent +- **WHEN** `RabbitMqPublisher.DisposeAsync` is invoked and the resulting shutdown event has `Initiator = Application` +- **THEN** no warning SHALL be logged and no metric SHALL be recorded. + +#### Scenario: Consumer does not observe recovery +- **WHEN** the `RabbitMqConsumer`'s connection shuts down and later recovers +- **THEN** the consumer SHALL emit neither a log entry nor a metric +- **AND** the consumer constructor SHALL expose no `RayTreeMeter` parameter. + +### Requirement: Outbox connection-fault observability +`IOutbox` SHALL expose three default-implemented members so consumers of an arbitrary `IOutbox` can classify transient connection faults without retry code: + +- `bool IsConnectionFault(Exception ex)` — default `false`. Concrete implementations override to classify connection-level exceptions versus application-level exceptions. +- `string? ConnectionComponent { get; }` — default `null`. Returns a stable component identifier used as a structured-log property, or `null` when the outbox has no observable external connection. +- `string? ConnectionEndpoint { get; }` — default `null`. Returns the endpoint identifier used as a structured-log property. + +`PostgreSqlOutbox` SHALL override these: `IsConnectionFault` returns `true` for `NpgsqlException { IsTransient: true }`, `NpgsqlException` with `SocketException`/`IOException` inner, `PostgresException` with `SqlState` in `{"57P01", "57P02", "57P03", "08000", "08003", "08006", "08001", "08004", "08007"}`, and `ObjectDisposedException`. `ConnectionComponent` returns `"postgres.outbox"`. `ConnectionEndpoint` returns `"{Host}:{Port}"`. The classifier used by `PostgreSqlOutbox` SHALL be the same `static bool PostgresFault.IsConnectionFault(Exception)` used by `NotificationBasedPublisher`. + +`OutboxPublisherService.ProcessBatchAsync`'s batch-error catch block SHALL consult `_outbox.IsConnectionFault(ex)` and `_outbox.ConnectionComponent`. When the classifier returns `true` AND `ConnectionComponent` is non-null, the failure SHALL be logged at `Warning` instead of `Error`, with `{Component}` and `{Endpoint}` structured properties, and a per-service `_outboxUnhealthy` flag SHALL be set. On the first subsequent batch that completes without throwing, an `Information` "outbox connection recovered" log SHALL be emitted with `{Duration}` (wall-clock seconds since the first failure) and the flag SHALL be cleared. When the classifier returns `false` OR `ConnectionComponent` is `null`, the existing `Error` log path SHALL be preserved unchanged. No connection metric SHALL be emitted in any of these paths. No retry code is added — the existing polling cadence is the retry. + +`NotificationBasedPublisher.FallbackPollingLoopAsync` SHALL apply the same `Warning`/`Information` log pattern per outbox, keyed by entity type in a `ConcurrentDictionary`, with no metric emission. + +#### Scenario: Outbox publisher log demotion on connection fault +- **WHEN** `OutboxPublisherService.ProcessBatchAsync` catches an exception and `_outbox.IsConnectionFault(ex)` returns `true` AND `ConnectionComponent` is non-null +- **THEN** the failure SHALL be logged at `Warning` (not `Error`) with `{Component}` and `{Endpoint}` +- **AND** no connection metric SHALL be emitted. + +#### Scenario: Outbox publisher recovery log on first success after failure +- **WHEN** `OutboxPublisherService.ProcessBatchAsync` completes without throwing AND the service was previously unhealthy +- **THEN** an `Information` "outbox connection recovered" log SHALL be emitted with `{Duration}` +- **AND** the `_outboxUnhealthy` flag SHALL be cleared +- **AND** no connection metric SHALL be emitted. + +#### Scenario: Non-connection-fault exception preserves existing Error log +- **WHEN** `OutboxPublisherService.ProcessBatchAsync` catches an exception for which `_outbox.IsConnectionFault(ex)` returns `false` +- **THEN** the existing `Error` log path SHALL be unchanged. + +#### Scenario: Write-path exceptions still propagate to the caller +- **WHEN** a caller invokes `EntityChangeTracker.TrackInsertAsync` and the underlying `PostgreSqlOutbox.WriteAsync` throws a connection-fault exception +- **THEN** the exception SHALL propagate to the caller unchanged +- **AND** no automatic retry SHALL be performed by the library at the write path. + +### Requirement: Recovery logs are emitted at documented levels +Each plugin that participates in connection recovery and owns an `ILogger` SHALL emit the following log entries: + +- **First detection of disconnect per recovery cycle**: `Warning`, with the underlying exception (where available), `{Component}`, and `{Endpoint}`. +- **Each retry attempt** (Postgres and Kafka only — the two plugins owning their own retry loop): `Information`, with `{Component}`, `{Endpoint}`, `{AttemptNumber}`, and `{Delay}` (the actually scheduled delay including jitter, in seconds). +- **Successful recovery**: `Information`, with `{Component}`, `{Endpoint}`, `{AttemptCount}` (for Postgres/Kafka), and `{Duration}` (wall-clock seconds elapsed since the first detection in this cycle). +- **Exhausted attempts** (Postgres and Kafka only): `Error`, with the most recent exception, `{Component}`, `{Endpoint}`, and `{AttemptCount}`. + +No connection metric SHALL accompany any of these logs — the `raytree.connection.*` instruments no longer exist. `RabbitMqConsumer` has no `ILogger` field and SHALL produce neither logs nor metrics for connection recovery. + +#### Scenario: First disconnect emits Warning with exception +- **WHEN** a logger-owning participating plugin observes the first disconnect of a recovery cycle (Kafka error handler fires, Postgres `WaitAsync` throws a connection fault, RabbitMQ publisher `ConnectionShutdownAsync` fires non-application) +- **THEN** a `Warning` log SHALL be emitted with `{Component}` and `{Endpoint}` and the underlying exception attached where available. + +#### Scenario: Retry attempt emits Information with delay +- **WHEN** Postgres or Kafka schedules the Nth retry attempt +- **THEN** an `Information` log SHALL be emitted with `{AttemptNumber} = N` and `{Delay}` equal to the actually-scheduled delay (including jitter) in seconds. + +#### Scenario: RabbitMqConsumer recovery is fully silent +- **WHEN** a recovery cycle runs for `RabbitMqConsumer` +- **THEN** no log entries SHALL be emitted +- **AND** no connection metric SHALL be recorded. + +## REMOVED Requirements + +### Requirement: Recovery options bound from configuration +**Reason**: The shared `ConnectionRecoveryOptions` type that this binding produced no longer exists; recovery options are now plugin-local. The bound named-options defaults were never auto-injected into plugin construction — callers had to resolve `IOptionsMonitor.Get(key)` and merge manually — so the binding provided no automatic behavior to preserve. + +**Migration**: Configure recovery per plugin in the plugin's `Use*` configure lambda. To drive it from configuration, bind your own section to the plugin-local type and assign it, e.g. `UseKafka(o => o.ConnectionRecovery = config.GetSection("ChangeTracking:Kafka:ConnectionRecovery").Get() ?? new())`. The `ChangeTrackingRecoveryKeys` constants and the `ChangeTracking:{Publisher,Subscriber}:ConnectionRecovery` host-bound sections are removed. diff --git a/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md b/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md new file mode 100644 index 0000000..aeb5572 --- /dev/null +++ b/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md @@ -0,0 +1,6 @@ +## REMOVED Requirements + +### Requirement: Connection-recovery instruments are emitted +**Reason**: The connection-recovery metrics are removed entirely. Recovery is now observed through logs only (Postgres/Kafka retry, recovery, and exhaustion logs; RabbitMQ publisher `Warning`/`Information` logs). The four `raytree.connection.*` instruments and the three `RayTreeMeter` facade methods that emitted them (`RecordConnectionDisconnect`, `RecordConnectionRecovery`, `RegisterConnectionStateGauge`) no longer exist, and every plugin emission call site is removed. + +**Migration**: Dashboards, alerts, and OTel views referencing `raytree.connection.disconnects`, `raytree.connection.recoveries`, `raytree.connection.recovery.duration`, or `raytree.connection.state` must be removed — those series cease to exist. To observe disconnect/recovery activity, consume the recovery log entries instead (filter on `{Component}` / `{Endpoint}` structured properties; recovery duration is available as the `{Duration}` property on the "recovered" `Information` log). No replacement metric is provided. diff --git a/openspec/changes/per-place-recovery-options/tasks.md b/openspec/changes/per-place-recovery-options/tasks.md new file mode 100644 index 0000000..8a02a00 --- /dev/null +++ b/openspec/changes/per-place-recovery-options/tasks.md @@ -0,0 +1,50 @@ +## 1. PostgreSQL plugin-local options type + +- [ ] 1.1 Add `PostgresConnectionRecoveryOptions` to `src/RayTree.Plugins.PostgreSQL` (new file under `Outbox/Notification/` or a `Resilience/` folder): copy the six members, per-field init guards, and `Validate()` from the current Core `ConnectionRecoveryOptions`; update the XML doc to reference Postgres LISTEN reconnect only. +- [ ] 1.2 Repoint `NotificationBasedPublisherOptions.ConnectionRecovery` to `PostgresConnectionRecoveryOptions` (property type + `new()` initializer); remove the `using RayTree.Core.Resilience;` if now unused. +- [ ] 1.3 Update `NotificationBasedPublisher` to consume the plugin-local type (variable/parameter types, `Validate()` call site); confirm no other Core-type reference remains. +- [ ] 1.4 Build `dotnet build src/RayTree.Plugins.PostgreSQL -c Release` clean (warnings-as-errors). + +## 2. Kafka plugin-local options type + +- [ ] 2.1 Add `KafkaConnectionRecoveryOptions` to `src/RayTree.Plugins.Kafka` (new file): same six members, guards, and `Validate()`; XML doc references the Kafka publisher/consumer fatal-error rebuild loops. +- [ ] 2.2 Repoint `KafkaPublisherOptions.ConnectionRecovery` and `KafkaConsumerOptions.ConnectionRecovery` to `KafkaConnectionRecoveryOptions`; remove now-unused `using RayTree.Core.Resilience;`. +- [ ] 2.3 Update `KafkaPublisher` and `KafkaConsumer` to consume the plugin-local type at all use sites (rebuild loop tuning, `Validate()` calls). +- [ ] 2.4 Build `dotnet build src/RayTree.Plugins.Kafka -c Release` clean. + +## 3. Remove Core type and Hosting binding + +- [ ] 3.1 Delete `src/RayTree.Core/Resilience/ConnectionRecoveryOptions.cs`. +- [ ] 3.2 Delete `src/RayTree.Hosting/ChangeTrackingRecoveryKeys.cs` and remove the two `services.Configure(...)` calls (plus the explanatory comment block) from `ServiceCollectionExtensions.AddChangeTracking`; remove now-unused usings. +- [ ] 3.3 Build `dotnet build RayTree.slnx -c Release` to surface every remaining reference to the removed type/keys across the solution; fix any stragglers. + +## 4. Remove connection-recovery metrics from RayTreeMeter + +- [ ] 4.1 In `src/RayTree.Core/Telemetry/RayTreeMeter.cs` remove the four `raytree.connection.*` instruments (`ConnectionDisconnects`, `ConnectionRecoveries`, `ConnectionRecoveryDuration`, the `raytree.connection.state` observable gauge), their `CreateCounter`/`CreateHistogram`/`CreateObservableGauge` registrations, the `_connectionStateSources` list, the `_connectionStateGate` lock, the `ObserveConnectionStates` callback, and the nested `ConnectionStateSubscription` type. +- [ ] 4.2 Remove the three public facade methods `RecordConnectionDisconnect`, `RecordConnectionRecovery`, and `RegisterConnectionStateGauge`. Leave all other instruments and the `ComponentTag`/`EndpointTag`/`OutcomeTag` helpers only if still used elsewhere; otherwise remove the now-orphaned tag helpers. +- [ ] 4.3 Build `dotnet build src/RayTree.Core -c Release` to surface every plugin/test call site of the removed members. + +## 5. Remove metric emission at all call sites + +- [ ] 5.1 `src/RayTree.Core/Distribution/OutboxPublisherService.cs`: delete the `RecordConnectionDisconnect`/`RecordConnectionRecovery` calls in `HandleBatchError`/`EmitOutboxRecovered`; keep the `_outboxUnhealthy` tracking, the `Error→Warning` log demotion, and the "outbox connection recovered" `Information` log. +- [ ] 5.2 `src/RayTree.Plugins.PostgreSQL/Outbox/Notification/NotificationBasedPublisher.cs`: delete connection-metric emission in the LISTEN reconnect and fallback-poll paths; keep the reconnect loop, `_listenerHealthy` handling, per-outbox unhealthy tracking, and all logs. +- [ ] 5.3 `src/RayTree.Plugins.Kafka/KafkaPublisher.cs` and `KafkaConsumer.cs`: delete connection-metric emission in the fatal-error/rebuild paths; keep rebuild behavior and logs. +- [ ] 5.4 `src/RayTree.Plugins.RabbitMQ/RabbitMqPublisher.cs`: remove the `_meter` field, the constructor `RayTreeMeter?` parameter, the `RegisterConnectionStateGauge` subscription, and the two `RecordConnection*` calls; keep the three event handlers, the `_lastShutdownAt` duration tracking, and the `Warning`/`Information` logs. +- [ ] 5.5 `src/RayTree.Plugins.RabbitMQ/RabbitMqConsumer.cs`: remove the `_meter` field, the constructor `RayTreeMeter? meter` parameter, the `_stateGaugeSubscription`, the `ConnectionShutdownAsync`/`RecoverySucceededAsync` subscriptions and their `OnConnectionShutdownAsync`/`OnRecoverySucceededAsync` handlers, and the matching `-=` detach in dispose. +- [ ] 5.6 Update the RabbitMQ builder/subscriber extensions (`RabbitMqBuilderExtensions.cs`, `RabbitMqSubscriberExtensions.cs`) and any Kafka extensions that forwarded a `RayTreeMeter` solely for connection metrics so they no longer pass it to the publisher/consumer constructors. +- [ ] 5.7 Build `dotnet build RayTree.slnx -c Release` clean. + +## 6. Tests + +- [ ] 6.1 Move the validation/defaults assertions from `tests/RayTree.Core.Tests/Resilience/ConnectionRecoveryOptionsTests.cs` into the PostgreSQL and Kafka test projects, retargeting `PostgresConnectionRecoveryOptions` / `KafkaConnectionRecoveryOptions`; delete the Core test file. +- [ ] 6.2 Delete `tests/RayTree.Core.Tests/Resilience/ConnectionRecoveryConfigurationTests.cs` (host binding removed) and `tests/RayTree.Core.Tests/Resilience/RecoveryMetricsTests.cs` (instruments removed). +- [ ] 6.3 Delete `tests/RayTree.Plugins.RabbitMQ.Tests/RabbitMqRecoveryMetricsTests.cs` (RabbitMQ recovery metrics removed). +- [ ] 6.4 Strip metric assertions from `tests/RayTree.Plugins.Kafka.Tests/KafkaRecoveryMetricsTests.cs`, `tests/RayTree.Core.Tests/Resilience/OutboxPublisherServiceConnectionFaultTests.cs`, and `tests/RayTree.Plugins.PostgreSQL.Tests/NotificationBasedPublisherRecoveryTests.cs`; retarget the plugin-local option types and keep the log/behavior assertions. +- [ ] 6.5 Run `dotnet test tests/RayTree.Core.Tests`, the PostgreSQL/Kafka/RabbitMQ test projects (unit-only filter where Docker is unavailable); all green. + +## 7. Docs and changelog + +- [ ] 7.1 Update `CLAUDE.md` (Connection recovery section + the `RayTreeMeter` "18 instruments" count and the four shared connection instruments paragraph) and `AGENTS.md`: describe the per-plugin options types, the removed Hosting binding, and the removed connection metrics (recovery is log-only). +- [ ] 7.2 Update `docs/opentelemetry-metrics.md` (remove the four `raytree.connection.*` rows and bucket guidance), and the plugin READMEs (`src/RayTree.Plugins.RabbitMQ/README.md`, `src/RayTree.Plugins.PostgreSQL/README.md`, `src/RayTree.Plugins.Kafka/README.md`) where they reference the Core type, the bound config sections, or the connection metrics. +- [ ] 7.3 Add a `CHANGELOG.md` entry under a new version: BREAKING — `ConnectionRecoveryOptions` + `ChangeTrackingRecoveryKeys` removed (replaced by `PostgresConnectionRecoveryOptions` / `KafkaConnectionRecoveryOptions`); all `raytree.connection.*` metrics + `RayTreeMeter.RecordConnection*`/`RegisterConnectionStateGauge` removed; RabbitMQ publisher/consumer constructors no longer take `RayTreeMeter`. Include the before/after migration snippet and the metrics-removal observability note. +- [ ] 7.4 Final full `dotnet build RayTree.slnx -c Release` + run the non-Docker unit-test suite; confirm clean. From 71051c09f3d64169a9174d4e5b62edda38c7a568 Mon Sep 17 00:00:00 2001 From: bitc0der <59016822+bitc0der@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:29:14 +0700 Subject: [PATCH 2/8] Add cleanup metric spec --- .../changes/per-place-recovery-options/design.md | 13 ++++++++++++- .../per-place-recovery-options/proposal.md | 5 +++++ .../specs/opentelemetry-metrics/spec.md | 15 +++++++++++++++ .../changes/per-place-recovery-options/tasks.md | 10 ++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/openspec/changes/per-place-recovery-options/design.md b/openspec/changes/per-place-recovery-options/design.md index 787796a..aa19d1d 100644 --- a/openspec/changes/per-place-recovery-options/design.md +++ b/openspec/changes/per-place-recovery-options/design.md @@ -57,7 +57,18 @@ The connection-recovery metrics were the *second* piece of Core that plugins rea **Alternative considered:** keep the instruments but stop emitting (no-op). Rejected — dead instruments on the public meter mislead operators into thinking the series exist; a clean removal is honest and shrinks `RayTreeMeter`. **Alternative considered:** move the metrics per-plugin (each plugin owns its own meter). Rejected — the user's intent is removal, not relocation, and per-plugin meters would fragment the single `"RayTree"` meter that the OTel integration deliberately exposes. -### 5. Test relocation +### 5. Narrow RayTreeMeter's public emit surface to internal + +Removing the connection facade exposes a latent simplification. `RayTreeMeter` had three categories of public method: the connection facade (now removed), and the publisher-side emit/register methods (`RecordPublishSuccess`, `RecordPublishFailure`, `RecordPayloadSize`, `RecordBatchSize`, `RegisterPendingGauge`). The connection facade was public **out of necessity** — Kafka and RabbitMQ plugins have no `InternalsVisibleTo` and could only reach it through the public API. The other five were public only by association; their actual callers are: + +- Core itself (`OutboxPublisherService`, `ChangeSubscriber`, `EntityChangeTracker`, `ChangeTrackingBuilder`), and +- `NotificationBasedPublisher` in `RayTree.Plugins.PostgreSQL` (the NOTIFY fast-path emits the same publish metrics) — which **is** an `InternalsVisibleTo`-privileged assembly (`RayTree.Core.csproj` grants it, alongside `RayTree.EntityFrameworkCore` and the test projects). + +So all five can become `internal` with no loss of reachability: every caller already sees Core internals. Kafka/RabbitMQ never call them (their publish metrics are emitted by Core's `OutboxPublisherService` on their behalf). The net result is that **`RayTreeMeter` exposes no public way to emit a metric** — its public surface is `MeterName`, the two constructors, `DefaultPendingCacheTtl`, and `Dispose()`. It becomes a construct-it / let-Core-fill-it / observe-via-OTel type. This is the correct shape: metric *emission* is a Core-internal implementation concern; metric *observation* is the public contract, and that lives in `RayTree.OpenTelemetry` (`AddRayTreeMetrics`) and the `"RayTree"` meter name, both untouched. + +**Why fold it in now rather than later:** the visibility change is only *safe and obvious* once the connection facade is gone — while that facade existed, "all emit methods are internal" was false, so the narrowing had no clean line to draw. Doing both in one change leaves the type in a coherent end state instead of a half-public one. **Alternative considered:** leave them public for hypothetical caller "custom instrumentation." Rejected — these methods take RayTree-internal shapes (`ChangeType`, entity `Type`, lag seconds) that only Core can meaningfully populate; they were never a usable public extension point. A caller wanting custom metrics uses their own `Meter`, not RayTree's emit methods. + +### 6. Test relocation `ConnectionRecoveryOptionsTests` (validation/defaults) splits into the PostgreSQL and Kafka test projects, asserting against the plugin-local type. `ConnectionRecoveryConfigurationTests` (Hosting binding) is removed — the binding it tested is gone. `NotificationBasedPublisherRecoveryTests` and the Kafka recovery tests update the referenced type name only. diff --git a/openspec/changes/per-place-recovery-options/proposal.md b/openspec/changes/per-place-recovery-options/proposal.md index 6025764..e24cf77 100644 --- a/openspec/changes/per-place-recovery-options/proposal.md +++ b/openspec/changes/per-place-recovery-options/proposal.md @@ -22,6 +22,10 @@ - **Behavior and logs are otherwise unchanged.** All reconnect loops (Postgres LISTEN, Kafka rebuild), the `IOutbox` connection-fault classification (`IsConnectionFault` / `ConnectionComponent` / `ConnectionEndpoint`), the `Error→Warning` outbox log demotion, and every recovery log entry are retained. This change removes observability-via-metrics only. - RabbitMQ still exposes no recovery options (SDK-owned). +### Narrow the RayTreeMeter public surface + +- The connection facade was the **only** metric-emission API that had to be public (Kafka/RabbitMQ plugins have no `InternalsVisibleTo`). With it gone, the remaining emit/register methods — `RecordPublishSuccess`, `RecordPublishFailure`, `RecordPayloadSize`, `RecordBatchSize`, `RegisterPendingGauge` — are called only from Core and from the PostgreSQL plugin/tests, all of which already see Core internals via `InternalsVisibleTo`. Make these five methods `internal`. `RayTreeMeter`'s public surface collapses to `MeterName`, its constructors, `DefaultPendingCacheTtl`, and `Dispose()` — a construct-and-observe type with **all metric emission internal**. + ## Capabilities ### New Capabilities @@ -36,6 +40,7 @@ - **Public API (breaking):** - Removed: `RayTree.Core.Resilience.ConnectionRecoveryOptions`, `RayTree.Hosting.ChangeTrackingRecoveryKeys`, and the `ChangeTracking:*:ConnectionRecovery` configuration binding. - Removed: `RayTreeMeter.RecordConnectionDisconnect`, `RecordConnectionRecovery`, `RegisterConnectionStateGauge`, and the four `raytree.connection.*` instruments. + - Narrowed to `internal`: `RayTreeMeter.RecordPublishSuccess`, `RecordPublishFailure`, `RecordPayloadSize`, `RecordBatchSize`, `RegisterPendingGauge` (no longer part of the public API; callers all have `InternalsVisibleTo`). - Removed: the `RayTreeMeter?` constructor parameter on `RabbitMqConsumer` and `RabbitMqPublisher` (and the corresponding meter forwarding in the RabbitMQ builder/subscriber extensions). - Added: `PostgresConnectionRecoveryOptions` (PostgreSQL plugin), `KafkaConnectionRecoveryOptions` (Kafka plugin). - Changed property types: `ConnectionRecovery` on the three plugin options classes now returns the plugin-local type. diff --git a/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md b/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md index aeb5572..e1b3c54 100644 --- a/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md +++ b/openspec/changes/per-place-recovery-options/specs/opentelemetry-metrics/spec.md @@ -4,3 +4,18 @@ **Reason**: The connection-recovery metrics are removed entirely. Recovery is now observed through logs only (Postgres/Kafka retry, recovery, and exhaustion logs; RabbitMQ publisher `Warning`/`Information` logs). The four `raytree.connection.*` instruments and the three `RayTreeMeter` facade methods that emitted them (`RecordConnectionDisconnect`, `RecordConnectionRecovery`, `RegisterConnectionStateGauge`) no longer exist, and every plugin emission call site is removed. **Migration**: Dashboards, alerts, and OTel views referencing `raytree.connection.disconnects`, `raytree.connection.recoveries`, `raytree.connection.recovery.duration`, or `raytree.connection.state` must be removed — those series cease to exist. To observe disconnect/recovery activity, consume the recovery log entries instead (filter on `{Component}` / `{Endpoint}` structured properties; recovery duration is available as the `{Duration}` property on the "recovered" `Information` log). No replacement metric is provided. + +## ADDED Requirements + +### Requirement: Metric emission is not part of the public API +`RayTreeMeter` SHALL expose no public method for emitting or registering a metric. All emit/register members (`RecordPublishSuccess`, `RecordPublishFailure`, `RecordPayloadSize`, `RecordBatchSize`, `RegisterPendingGauge`) SHALL be `internal`, consumed only by `RayTree.Core` and by assemblies granted `InternalsVisibleTo` (`RayTree.Plugins.PostgreSQL`, `RayTree.EntityFrameworkCore`, and the test projects). `RayTreeMeter`'s public surface SHALL consist of `MeterName`, its constructors, `DefaultPendingCacheTtl`, and `Dispose()`. Metric *observation* SHALL remain public via the `"RayTree"` meter name and `RayTree.OpenTelemetry.AddRayTreeMetrics`. + +#### Scenario: No public emit method exists +- **WHEN** a consumer outside Core and without `InternalsVisibleTo` references `RayTreeMeter` +- **THEN** no metric-emitting or gauge-registering method SHALL be accessible +- **AND** only `MeterName`, the constructors, `DefaultPendingCacheTtl`, and `Dispose()` SHALL be available. + +#### Scenario: Privileged assemblies still emit internally +- **WHEN** `NotificationBasedPublisher` (in `RayTree.Plugins.PostgreSQL`, an `InternalsVisibleTo`-granted assembly) emits publish metrics on the NOTIFY fast-path +- **THEN** it SHALL call the `internal` `RecordPublishSuccess` / `RecordPublishFailure` / `RecordPayloadSize` / `RecordBatchSize` members +- **AND** the emitted instruments SHALL remain observable via `AddRayTreeMetrics`. diff --git a/openspec/changes/per-place-recovery-options/tasks.md b/openspec/changes/per-place-recovery-options/tasks.md index 8a02a00..3e8da30 100644 --- a/openspec/changes/per-place-recovery-options/tasks.md +++ b/openspec/changes/per-place-recovery-options/tasks.md @@ -21,8 +21,9 @@ ## 4. Remove connection-recovery metrics from RayTreeMeter - [ ] 4.1 In `src/RayTree.Core/Telemetry/RayTreeMeter.cs` remove the four `raytree.connection.*` instruments (`ConnectionDisconnects`, `ConnectionRecoveries`, `ConnectionRecoveryDuration`, the `raytree.connection.state` observable gauge), their `CreateCounter`/`CreateHistogram`/`CreateObservableGauge` registrations, the `_connectionStateSources` list, the `_connectionStateGate` lock, the `ObserveConnectionStates` callback, and the nested `ConnectionStateSubscription` type. -- [ ] 4.2 Remove the three public facade methods `RecordConnectionDisconnect`, `RecordConnectionRecovery`, and `RegisterConnectionStateGauge`. Leave all other instruments and the `ComponentTag`/`EndpointTag`/`OutcomeTag` helpers only if still used elsewhere; otherwise remove the now-orphaned tag helpers. -- [ ] 4.3 Build `dotnet build src/RayTree.Core -c Release` to surface every plugin/test call site of the removed members. +- [ ] 4.2 Remove the three public facade methods `RecordConnectionDisconnect`, `RecordConnectionRecovery`, and `RegisterConnectionStateGauge`. Remove the now-orphaned `ComponentTag`/`EndpointTag`/`OutcomeTag` helpers if nothing else uses them. +- [ ] 4.3 Narrow the remaining emit/register methods to `internal`: `RecordPublishSuccess`, `RecordPublishFailure`, `RecordPayloadSize`, `RecordBatchSize`, `RegisterPendingGauge`. Verify `RayTreeMeter`'s public surface is now only `MeterName`, the constructors, `DefaultPendingCacheTtl`, and `Dispose()`. +- [ ] 4.4 Build `dotnet build src/RayTree.Core -c Release`, then `dotnet build src/RayTree.Plugins.PostgreSQL -c Release` to confirm the PostgreSQL plugin (IVT-privileged) still compiles against the internalized members; surface any remaining call sites of the removed connection facade. ## 5. Remove metric emission at all call sites @@ -40,11 +41,12 @@ - [ ] 6.2 Delete `tests/RayTree.Core.Tests/Resilience/ConnectionRecoveryConfigurationTests.cs` (host binding removed) and `tests/RayTree.Core.Tests/Resilience/RecoveryMetricsTests.cs` (instruments removed). - [ ] 6.3 Delete `tests/RayTree.Plugins.RabbitMQ.Tests/RabbitMqRecoveryMetricsTests.cs` (RabbitMQ recovery metrics removed). - [ ] 6.4 Strip metric assertions from `tests/RayTree.Plugins.Kafka.Tests/KafkaRecoveryMetricsTests.cs`, `tests/RayTree.Core.Tests/Resilience/OutboxPublisherServiceConnectionFaultTests.cs`, and `tests/RayTree.Plugins.PostgreSQL.Tests/NotificationBasedPublisherRecoveryTests.cs`; retarget the plugin-local option types and keep the log/behavior assertions. -- [ ] 6.5 Run `dotnet test tests/RayTree.Core.Tests`, the PostgreSQL/Kafka/RabbitMQ test projects (unit-only filter where Docker is unavailable); all green. +- [ ] 6.5 Confirm tests still compile against the internalized emit methods (test projects have `InternalsVisibleTo`); any test that called a now-`internal` method as "public" needs no change beyond the build passing. If a test asserted on the removed public facade, delete that assertion. +- [ ] 6.6 Run `dotnet test tests/RayTree.Core.Tests`, the PostgreSQL/Kafka/RabbitMQ test projects (unit-only filter where Docker is unavailable); all green. ## 7. Docs and changelog -- [ ] 7.1 Update `CLAUDE.md` (Connection recovery section + the `RayTreeMeter` "18 instruments" count and the four shared connection instruments paragraph) and `AGENTS.md`: describe the per-plugin options types, the removed Hosting binding, and the removed connection metrics (recovery is log-only). +- [ ] 7.1 Update `CLAUDE.md` (Connection recovery section + the `RayTreeMeter` "18 instruments" count and the four shared connection instruments paragraph; note that emit methods are now `internal` and the public surface is construct-and-observe only) and `AGENTS.md`: describe the per-plugin options types, the removed Hosting binding, the removed connection metrics (recovery is log-only), and the narrowed `RayTreeMeter` public surface. - [ ] 7.2 Update `docs/opentelemetry-metrics.md` (remove the four `raytree.connection.*` rows and bucket guidance), and the plugin READMEs (`src/RayTree.Plugins.RabbitMQ/README.md`, `src/RayTree.Plugins.PostgreSQL/README.md`, `src/RayTree.Plugins.Kafka/README.md`) where they reference the Core type, the bound config sections, or the connection metrics. - [ ] 7.3 Add a `CHANGELOG.md` entry under a new version: BREAKING — `ConnectionRecoveryOptions` + `ChangeTrackingRecoveryKeys` removed (replaced by `PostgresConnectionRecoveryOptions` / `KafkaConnectionRecoveryOptions`); all `raytree.connection.*` metrics + `RayTreeMeter.RecordConnection*`/`RegisterConnectionStateGauge` removed; RabbitMQ publisher/consumer constructors no longer take `RayTreeMeter`. Include the before/after migration snippet and the metrics-removal observability note. - [ ] 7.4 Final full `dotnet build RayTree.slnx -c Release` + run the non-Docker unit-test suite; confirm clean. From 3ceb25dbb026c25a7a05cd95d50d0f7c9301215e Mon Sep 17 00:00:00 2001 From: bitc0der <59016822+bitc0der@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:31:42 +0700 Subject: [PATCH 3/8] Update OpenSpec env --- .agent/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .agent/skills/openspec-explore/SKILL.md | 11 +++++----- .agent/skills/openspec-propose/SKILL.md | 9 ++++---- .agent/workflows/opsx-apply.md | 3 +++ .agent/workflows/opsx-archive.md | 21 +++++++++++-------- .agent/workflows/opsx-explore.md | 9 ++++---- .agent/workflows/opsx-propose.md | 7 ++++--- .claude/commands/opsx/apply.md | 3 +++ .claude/commands/opsx/archive.md | 21 +++++++++++-------- .claude/commands/opsx/explore.md | 9 ++++---- .claude/commands/opsx/propose.md | 7 ++++--- .claude/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .claude/skills/openspec-explore/SKILL.md | 11 +++++----- .claude/skills/openspec-propose/SKILL.md | 9 ++++---- .cline/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .cline/skills/openspec-explore/SKILL.md | 11 +++++----- .cline/skills/openspec-propose/SKILL.md | 9 ++++---- .clinerules/workflows/opsx-apply.md | 3 +++ .clinerules/workflows/opsx-archive.md | 21 +++++++++++-------- .clinerules/workflows/opsx-explore.md | 9 ++++---- .clinerules/workflows/opsx-propose.md | 7 ++++--- .codex/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .codex/skills/openspec-explore/SKILL.md | 11 +++++----- .codex/skills/openspec-propose/SKILL.md | 9 ++++---- .continue/prompts/opsx-apply.prompt | 3 +++ .continue/prompts/opsx-archive.prompt | 21 +++++++++++-------- .continue/prompts/opsx-explore.prompt | 9 ++++---- .continue/prompts/opsx-propose.prompt | 7 ++++--- .../skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .continue/skills/openspec-explore/SKILL.md | 11 +++++----- .continue/skills/openspec-propose/SKILL.md | 9 ++++---- .cursor/commands/opsx-apply.md | 3 +++ .cursor/commands/opsx-archive.md | 21 +++++++++++-------- .cursor/commands/opsx-explore.md | 9 ++++---- .cursor/commands/opsx-propose.md | 7 ++++--- .cursor/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .cursor/skills/openspec-explore/SKILL.md | 11 +++++----- .cursor/skills/openspec-propose/SKILL.md | 9 ++++---- .gemini/commands/opsx/apply.toml | 3 +++ .gemini/commands/opsx/archive.toml | 21 +++++++++++-------- .gemini/commands/opsx/explore.toml | 9 ++++---- .gemini/commands/opsx/propose.toml | 7 ++++--- .gemini/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .gemini/skills/openspec-explore/SKILL.md | 11 +++++----- .gemini/skills/openspec-propose/SKILL.md | 9 ++++---- .github/prompts/opsx-apply.prompt.md | 3 +++ .github/prompts/opsx-archive.prompt.md | 21 +++++++++++-------- .github/prompts/opsx-explore.prompt.md | 9 ++++---- .github/prompts/opsx-propose.prompt.md | 7 ++++--- .github/skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .github/skills/openspec-explore/SKILL.md | 11 +++++----- .github/skills/openspec-propose/SKILL.md | 9 ++++---- .../skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .kilocode/skills/openspec-explore/SKILL.md | 11 +++++----- .kilocode/skills/openspec-propose/SKILL.md | 9 ++++---- .kilocode/workflows/opsx-apply.md | 3 +++ .kilocode/workflows/opsx-archive.md | 21 +++++++++++-------- .kilocode/workflows/opsx-explore.md | 9 ++++---- .kilocode/workflows/opsx-propose.md | 7 ++++--- .opencode/commands/opsx-apply.md | 3 +++ .opencode/commands/opsx-archive.md | 21 +++++++++++-------- .opencode/commands/opsx-explore.md | 9 ++++---- .opencode/commands/opsx-propose.md | 7 ++++--- .../skills/openspec-apply-change/SKILL.md | 5 ++++- .../skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .opencode/skills/openspec-explore/SKILL.md | 11 +++++----- .opencode/skills/openspec-propose/SKILL.md | 9 ++++---- .qwen/commands/opsx-apply.toml | 3 +++ .qwen/commands/opsx-archive.toml | 21 +++++++++++-------- .qwen/commands/opsx-explore.toml | 9 ++++---- .qwen/commands/opsx-propose.toml | 7 ++++--- .qwen/skills/openspec-apply-change/SKILL.md | 5 ++++- .qwen/skills/openspec-archive-change/SKILL.md | 17 ++++++++------- .qwen/skills/openspec-explore/SKILL.md | 11 +++++----- .qwen/skills/openspec-propose/SKILL.md | 9 ++++---- 84 files changed, 494 insertions(+), 368 deletions(-) diff --git a/.agent/skills/openspec-apply-change/SKILL.md b/.agent/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.agent/skills/openspec-apply-change/SKILL.md +++ b/.agent/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.agent/skills/openspec-archive-change/SKILL.md b/.agent/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.agent/skills/openspec-archive-change/SKILL.md +++ b/.agent/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.agent/skills/openspec-explore/SKILL.md b/.agent/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.agent/skills/openspec-explore/SKILL.md +++ b/.agent/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.agent/skills/openspec-propose/SKILL.md b/.agent/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.agent/skills/openspec-propose/SKILL.md +++ b/.agent/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.agent/workflows/opsx-apply.md b/.agent/workflows/opsx-apply.md index e23ec64..cb53869 100644 --- a/.agent/workflows/opsx-apply.md +++ b/.agent/workflows/opsx-apply.md @@ -23,6 +23,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -42,6 +43,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.agent/workflows/opsx-archive.md b/.agent/workflows/opsx-archive.md index 1163776..b30dcd0 100644 --- a/.agent/workflows/opsx-archive.md +++ b/.agent/workflows/opsx-archive.md @@ -23,8 +23,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -45,7 +48,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -60,19 +63,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -91,7 +94,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -104,7 +107,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -117,7 +120,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -134,7 +137,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.agent/workflows/opsx-explore.md b/.agent/workflows/opsx-explore.md index 3b674eb..32ec8d2 100644 --- a/.agent/workflows/opsx-explore.md +++ b/.agent/workflows/opsx-explore.md @@ -104,11 +104,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.agent/workflows/opsx-propose.md b/.agent/workflows/opsx-propose.md index cf30b2a..30bd6fd 100644 --- a/.agent/workflows/opsx-propose.md +++ b/.agent/workflows/opsx-propose.md @@ -30,7 +30,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -39,6 +39,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -56,10 +57,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md index ae14f0f..45003f8 100644 --- a/.claude/commands/opsx/apply.md +++ b/.claude/commands/opsx/apply.md @@ -26,6 +26,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -45,6 +46,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.claude/commands/opsx/archive.md b/.claude/commands/opsx/archive.md index 5e91608..4af9687 100644 --- a/.claude/commands/opsx/archive.md +++ b/.claude/commands/opsx/archive.md @@ -26,8 +26,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -48,7 +51,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -63,19 +66,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -94,7 +97,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -107,7 +110,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -120,7 +123,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -137,7 +140,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.claude/commands/opsx/explore.md b/.claude/commands/opsx/explore.md index 1757907..8655619 100644 --- a/.claude/commands/opsx/explore.md +++ b/.claude/commands/opsx/explore.md @@ -107,11 +107,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.claude/commands/opsx/propose.md b/.claude/commands/opsx/propose.md index 05276f4..90ba344 100644 --- a/.claude/commands/opsx/propose.md +++ b/.claude/commands/opsx/propose.md @@ -33,7 +33,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -42,6 +42,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -59,10 +60,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.claude/skills/openspec-apply-change/SKILL.md b/.claude/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.claude/skills/openspec-apply-change/SKILL.md +++ b/.claude/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.claude/skills/openspec-archive-change/SKILL.md b/.claude/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.claude/skills/openspec-archive-change/SKILL.md +++ b/.claude/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.claude/skills/openspec-explore/SKILL.md b/.claude/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.claude/skills/openspec-explore/SKILL.md +++ b/.claude/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.claude/skills/openspec-propose/SKILL.md b/.claude/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.claude/skills/openspec-propose/SKILL.md +++ b/.claude/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.cline/skills/openspec-apply-change/SKILL.md b/.cline/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.cline/skills/openspec-apply-change/SKILL.md +++ b/.cline/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.cline/skills/openspec-archive-change/SKILL.md b/.cline/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.cline/skills/openspec-archive-change/SKILL.md +++ b/.cline/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.cline/skills/openspec-explore/SKILL.md b/.cline/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.cline/skills/openspec-explore/SKILL.md +++ b/.cline/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.cline/skills/openspec-propose/SKILL.md b/.cline/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.cline/skills/openspec-propose/SKILL.md +++ b/.cline/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.clinerules/workflows/opsx-apply.md b/.clinerules/workflows/opsx-apply.md index 37a78e6..30a8841 100644 --- a/.clinerules/workflows/opsx-apply.md +++ b/.clinerules/workflows/opsx-apply.md @@ -23,6 +23,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -42,6 +43,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.clinerules/workflows/opsx-archive.md b/.clinerules/workflows/opsx-archive.md index c38d6f6..b0b43a3 100644 --- a/.clinerules/workflows/opsx-archive.md +++ b/.clinerules/workflows/opsx-archive.md @@ -23,8 +23,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -45,7 +48,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -60,19 +63,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -91,7 +94,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -104,7 +107,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -117,7 +120,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -134,7 +137,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.clinerules/workflows/opsx-explore.md b/.clinerules/workflows/opsx-explore.md index 0c5f034..84ba87a 100644 --- a/.clinerules/workflows/opsx-explore.md +++ b/.clinerules/workflows/opsx-explore.md @@ -104,11 +104,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.clinerules/workflows/opsx-propose.md b/.clinerules/workflows/opsx-propose.md index 6669d0d..a8a501e 100644 --- a/.clinerules/workflows/opsx-propose.md +++ b/.clinerules/workflows/opsx-propose.md @@ -30,7 +30,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -39,6 +39,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -56,10 +57,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.codex/skills/openspec-apply-change/SKILL.md b/.codex/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.codex/skills/openspec-apply-change/SKILL.md +++ b/.codex/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.codex/skills/openspec-archive-change/SKILL.md b/.codex/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.codex/skills/openspec-archive-change/SKILL.md +++ b/.codex/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.codex/skills/openspec-explore/SKILL.md b/.codex/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.codex/skills/openspec-explore/SKILL.md +++ b/.codex/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.codex/skills/openspec-propose/SKILL.md b/.codex/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.codex/skills/openspec-propose/SKILL.md +++ b/.codex/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.continue/prompts/opsx-apply.prompt b/.continue/prompts/opsx-apply.prompt index 1c2b132..ae0659f 100644 --- a/.continue/prompts/opsx-apply.prompt +++ b/.continue/prompts/opsx-apply.prompt @@ -25,6 +25,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -44,6 +45,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.continue/prompts/opsx-archive.prompt b/.continue/prompts/opsx-archive.prompt index 83e6c4f..47965f8 100644 --- a/.continue/prompts/opsx-archive.prompt +++ b/.continue/prompts/opsx-archive.prompt @@ -25,8 +25,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -47,7 +50,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -62,19 +65,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -93,7 +96,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -106,7 +109,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -119,7 +122,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -136,7 +139,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.continue/prompts/opsx-explore.prompt b/.continue/prompts/opsx-explore.prompt index f3c1679..37490c3 100644 --- a/.continue/prompts/opsx-explore.prompt +++ b/.continue/prompts/opsx-explore.prompt @@ -106,11 +106,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.continue/prompts/opsx-propose.prompt b/.continue/prompts/opsx-propose.prompt index 83d6d35..5e488d6 100644 --- a/.continue/prompts/opsx-propose.prompt +++ b/.continue/prompts/opsx-propose.prompt @@ -32,7 +32,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -41,6 +41,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -58,10 +59,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.continue/skills/openspec-apply-change/SKILL.md b/.continue/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.continue/skills/openspec-apply-change/SKILL.md +++ b/.continue/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.continue/skills/openspec-archive-change/SKILL.md b/.continue/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.continue/skills/openspec-archive-change/SKILL.md +++ b/.continue/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.continue/skills/openspec-explore/SKILL.md b/.continue/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.continue/skills/openspec-explore/SKILL.md +++ b/.continue/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.continue/skills/openspec-propose/SKILL.md b/.continue/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.continue/skills/openspec-propose/SKILL.md +++ b/.continue/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.cursor/commands/opsx-apply.md b/.cursor/commands/opsx-apply.md index fcfa6c9..9c17178 100644 --- a/.cursor/commands/opsx-apply.md +++ b/.cursor/commands/opsx-apply.md @@ -26,6 +26,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -45,6 +46,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.cursor/commands/opsx-archive.md b/.cursor/commands/opsx-archive.md index e5cdd0e..8e3553c 100644 --- a/.cursor/commands/opsx-archive.md +++ b/.cursor/commands/opsx-archive.md @@ -26,8 +26,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -48,7 +51,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -63,19 +66,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -94,7 +97,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -107,7 +110,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -120,7 +123,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -137,7 +140,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.cursor/commands/opsx-explore.md b/.cursor/commands/opsx-explore.md index f1aceec..ba1339b 100644 --- a/.cursor/commands/opsx-explore.md +++ b/.cursor/commands/opsx-explore.md @@ -107,11 +107,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.cursor/commands/opsx-propose.md b/.cursor/commands/opsx-propose.md index 4d75466..1744ba3 100644 --- a/.cursor/commands/opsx-propose.md +++ b/.cursor/commands/opsx-propose.md @@ -33,7 +33,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -42,6 +42,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -59,10 +60,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.cursor/skills/openspec-apply-change/SKILL.md b/.cursor/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.cursor/skills/openspec-apply-change/SKILL.md +++ b/.cursor/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.cursor/skills/openspec-archive-change/SKILL.md b/.cursor/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.cursor/skills/openspec-archive-change/SKILL.md +++ b/.cursor/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.cursor/skills/openspec-explore/SKILL.md b/.cursor/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.cursor/skills/openspec-explore/SKILL.md +++ b/.cursor/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.cursor/skills/openspec-propose/SKILL.md b/.cursor/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.cursor/skills/openspec-propose/SKILL.md +++ b/.cursor/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.gemini/commands/opsx/apply.toml b/.gemini/commands/opsx/apply.toml index 83a33d7..24ca8d6 100644 --- a/.gemini/commands/opsx/apply.toml +++ b/.gemini/commands/opsx/apply.toml @@ -22,6 +22,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -41,6 +42,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.gemini/commands/opsx/archive.toml b/.gemini/commands/opsx/archive.toml index cf76b75..6a11739 100644 --- a/.gemini/commands/opsx/archive.toml +++ b/.gemini/commands/opsx/archive.toml @@ -22,8 +22,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -44,7 +47,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -59,19 +62,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -90,7 +93,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -103,7 +106,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -116,7 +119,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -133,7 +136,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.gemini/commands/opsx/explore.toml b/.gemini/commands/opsx/explore.toml index 8ccd312..7a25618 100644 --- a/.gemini/commands/opsx/explore.toml +++ b/.gemini/commands/opsx/explore.toml @@ -103,11 +103,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.gemini/commands/opsx/propose.toml b/.gemini/commands/opsx/propose.toml index 8be4eaf..fbf3460 100644 --- a/.gemini/commands/opsx/propose.toml +++ b/.gemini/commands/opsx/propose.toml @@ -29,7 +29,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -38,6 +38,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -55,10 +56,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.gemini/skills/openspec-apply-change/SKILL.md b/.gemini/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.gemini/skills/openspec-apply-change/SKILL.md +++ b/.gemini/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.gemini/skills/openspec-archive-change/SKILL.md b/.gemini/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.gemini/skills/openspec-archive-change/SKILL.md +++ b/.gemini/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.gemini/skills/openspec-explore/SKILL.md b/.gemini/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.gemini/skills/openspec-explore/SKILL.md +++ b/.gemini/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.gemini/skills/openspec-propose/SKILL.md b/.gemini/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.gemini/skills/openspec-propose/SKILL.md +++ b/.gemini/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.github/prompts/opsx-apply.prompt.md b/.github/prompts/opsx-apply.prompt.md index e23ec64..cb53869 100644 --- a/.github/prompts/opsx-apply.prompt.md +++ b/.github/prompts/opsx-apply.prompt.md @@ -23,6 +23,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -42,6 +43,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.github/prompts/opsx-archive.prompt.md b/.github/prompts/opsx-archive.prompt.md index 1163776..b30dcd0 100644 --- a/.github/prompts/opsx-archive.prompt.md +++ b/.github/prompts/opsx-archive.prompt.md @@ -23,8 +23,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -45,7 +48,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -60,19 +63,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -91,7 +94,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -104,7 +107,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -117,7 +120,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -134,7 +137,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.github/prompts/opsx-explore.prompt.md b/.github/prompts/opsx-explore.prompt.md index 3b674eb..32ec8d2 100644 --- a/.github/prompts/opsx-explore.prompt.md +++ b/.github/prompts/opsx-explore.prompt.md @@ -104,11 +104,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.github/prompts/opsx-propose.prompt.md b/.github/prompts/opsx-propose.prompt.md index cf30b2a..30bd6fd 100644 --- a/.github/prompts/opsx-propose.prompt.md +++ b/.github/prompts/opsx-propose.prompt.md @@ -30,7 +30,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -39,6 +39,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -56,10 +57,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.github/skills/openspec-apply-change/SKILL.md b/.github/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.github/skills/openspec-apply-change/SKILL.md +++ b/.github/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.github/skills/openspec-archive-change/SKILL.md b/.github/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.github/skills/openspec-archive-change/SKILL.md +++ b/.github/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.github/skills/openspec-explore/SKILL.md b/.github/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.github/skills/openspec-explore/SKILL.md +++ b/.github/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.github/skills/openspec-propose/SKILL.md b/.github/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.github/skills/openspec-propose/SKILL.md +++ b/.github/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.kilocode/skills/openspec-apply-change/SKILL.md b/.kilocode/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.kilocode/skills/openspec-apply-change/SKILL.md +++ b/.kilocode/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.kilocode/skills/openspec-archive-change/SKILL.md b/.kilocode/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.kilocode/skills/openspec-archive-change/SKILL.md +++ b/.kilocode/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.kilocode/skills/openspec-explore/SKILL.md b/.kilocode/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.kilocode/skills/openspec-explore/SKILL.md +++ b/.kilocode/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.kilocode/skills/openspec-propose/SKILL.md b/.kilocode/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.kilocode/skills/openspec-propose/SKILL.md +++ b/.kilocode/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.kilocode/workflows/opsx-apply.md b/.kilocode/workflows/opsx-apply.md index a10693a..422724e 100644 --- a/.kilocode/workflows/opsx-apply.md +++ b/.kilocode/workflows/opsx-apply.md @@ -19,6 +19,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -38,6 +39,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.kilocode/workflows/opsx-archive.md b/.kilocode/workflows/opsx-archive.md index 46def16..d7a8850 100644 --- a/.kilocode/workflows/opsx-archive.md +++ b/.kilocode/workflows/opsx-archive.md @@ -19,8 +19,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -41,7 +44,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -56,19 +59,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -87,7 +90,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -100,7 +103,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -113,7 +116,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -130,7 +133,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.kilocode/workflows/opsx-explore.md b/.kilocode/workflows/opsx-explore.md index f4debae..6c31efa 100644 --- a/.kilocode/workflows/opsx-explore.md +++ b/.kilocode/workflows/opsx-explore.md @@ -100,11 +100,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.kilocode/workflows/opsx-propose.md b/.kilocode/workflows/opsx-propose.md index 5085afc..e9733e3 100644 --- a/.kilocode/workflows/opsx-propose.md +++ b/.kilocode/workflows/opsx-propose.md @@ -26,7 +26,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -35,6 +35,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -52,10 +53,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.opencode/commands/opsx-apply.md b/.opencode/commands/opsx-apply.md index 16d2ef3..9d0f3fc 100644 --- a/.opencode/commands/opsx-apply.md +++ b/.opencode/commands/opsx-apply.md @@ -23,6 +23,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -42,6 +43,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.opencode/commands/opsx-archive.md b/.opencode/commands/opsx-archive.md index 2bd807a..8f4e144 100644 --- a/.opencode/commands/opsx-archive.md +++ b/.opencode/commands/opsx-archive.md @@ -23,8 +23,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -45,7 +48,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -60,19 +63,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -91,7 +94,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -104,7 +107,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -117,7 +120,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -134,7 +137,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.opencode/commands/opsx-explore.md b/.opencode/commands/opsx-explore.md index 7db25f6..a4ed088 100644 --- a/.opencode/commands/opsx-explore.md +++ b/.opencode/commands/opsx-explore.md @@ -104,11 +104,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.opencode/commands/opsx-propose.md b/.opencode/commands/opsx-propose.md index b063a7e..f100115 100644 --- a/.opencode/commands/opsx-propose.md +++ b/.opencode/commands/opsx-propose.md @@ -30,7 +30,7 @@ When ready to implement, run /opsx-apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -39,6 +39,7 @@ When ready to implement, run /opsx-apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -56,10 +57,10 @@ When ready to implement, run /opsx-apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.opencode/skills/openspec-apply-change/SKILL.md b/.opencode/skills/openspec-apply-change/SKILL.md index 86c881d..87b5f9f 100644 --- a/.opencode/skills/openspec-apply-change/SKILL.md +++ b/.opencode/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.opencode/skills/openspec-archive-change/SKILL.md b/.opencode/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.opencode/skills/openspec-archive-change/SKILL.md +++ b/.opencode/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.opencode/skills/openspec-explore/SKILL.md b/.opencode/skills/openspec-explore/SKILL.md index 8c7225c..37ae6c1 100644 --- a/.opencode/skills/openspec-explore/SKILL.md +++ b/.opencode/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.opencode/skills/openspec-propose/SKILL.md b/.opencode/skills/openspec-propose/SKILL.md index 05fdc62..7caa2ef 100644 --- a/.opencode/skills/openspec-propose/SKILL.md +++ b/.opencode/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx-apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx-apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx-apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.qwen/commands/opsx-apply.toml b/.qwen/commands/opsx-apply.toml index 83a33d7..24ca8d6 100644 --- a/.qwen/commands/opsx-apply.toml +++ b/.qwen/commands/opsx-apply.toml @@ -22,6 +22,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -41,6 +42,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.qwen/commands/opsx-archive.toml b/.qwen/commands/opsx-archive.toml index cf76b75..6a11739 100644 --- a/.qwen/commands/opsx-archive.toml +++ b/.qwen/commands/opsx-archive.toml @@ -22,8 +22,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Prompt user for confirmation to continue @@ -44,7 +47,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -59,19 +62,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -90,7 +93,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs All artifacts complete. All tasks complete. @@ -103,7 +106,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** No delta specs All artifacts complete. All tasks complete. @@ -116,7 +119,7 @@ All artifacts complete. All tasks complete. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** Sync skipped (user chose to skip) **Warnings:** @@ -133,7 +136,7 @@ Review the archive if this was not intentional. ## Archive Failed **Change:** -**Target:** openspec/changes/archive/YYYY-MM-DD-/ +**Target:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ Target archive directory already exists. diff --git a/.qwen/commands/opsx-explore.toml b/.qwen/commands/opsx-explore.toml index 8ccd312..7a25618 100644 --- a/.qwen/commands/opsx-explore.toml +++ b/.qwen/commands/opsx-explore.toml @@ -103,11 +103,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.qwen/commands/opsx-propose.toml b/.qwen/commands/opsx-propose.toml index 8be4eaf..fbf3460 100644 --- a/.qwen/commands/opsx-propose.toml +++ b/.qwen/commands/opsx-propose.toml @@ -29,7 +29,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -38,6 +38,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -55,10 +56,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " diff --git a/.qwen/skills/openspec-apply-change/SKILL.md b/.qwen/skills/openspec-apply-change/SKILL.md index 70fbdb8..eddd3a6 100644 --- a/.qwen/skills/openspec-apply-change/SKILL.md +++ b/.qwen/skills/openspec-apply-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Implement tasks from an OpenSpec change. @@ -30,6 +30,7 @@ Implement tasks from an OpenSpec change. ``` Parse the JSON to understand: - `schemaName`: The workflow being used (e.g., "spec-driven") + - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) 3. **Get apply instructions** @@ -49,6 +50,8 @@ Implement tasks from an OpenSpec change. - If `state: "all_done"`: congratulate, suggest archive - Otherwise: proceed to implementation + **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. + 4. **Read context files** Read every file path listed under `contextFiles` from the apply instructions output. diff --git a/.qwen/skills/openspec-archive-change/SKILL.md b/.qwen/skills/openspec-archive-change/SKILL.md index 12e2f70..37b38eb 100644 --- a/.qwen/skills/openspec-archive-change/SKILL.md +++ b/.qwen/skills/openspec-archive-change/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Archive a completed change in the experimental workflow. @@ -30,8 +30,11 @@ Archive a completed change in the experimental workflow. Parse the JSON to understand: - `schemaName`: The workflow being used + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context - `artifacts`: List of artifacts with their status (`done` or other) + If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos. + **If any artifacts are not `done`:** - Display warning listing incomplete artifacts - Use **AskUserQuestion tool** to confirm user wants to proceed @@ -52,7 +55,7 @@ Archive a completed change in the experimental workflow. 4. **Assess delta spec sync state** - Check for delta specs at `openspec/changes//specs/`. If none exist, proceed without sync prompt. + Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt. **If delta specs exist:** - Compare each delta spec with its corresponding main spec at `openspec/specs//spec.md` @@ -67,19 +70,19 @@ Archive a completed change in the experimental workflow. 5. **Perform the archive** - Create the archive directory if it doesn't exist: + Create an `archive` directory under `planningHome.changesDir` if it doesn't exist: ```bash - mkdir -p openspec/changes/archive + mkdir -p "/archive" ``` Generate target name using current date: `YYYY-MM-DD-` **Check if target already exists:** - If yes: Fail with error, suggest renaming existing archive or using different date - - If no: Move the change directory to archive + - If no: Move `changeRoot` to the archive directory ```bash - mv openspec/changes/ openspec/changes/archive/YYYY-MM-DD- + mv "" "/archive/YYYY-MM-DD-" ``` 6. **Display summary** @@ -98,7 +101,7 @@ Archive a completed change in the experimental workflow. **Change:** **Schema:** -**Archived to:** openspec/changes/archive/YYYY-MM-DD-/ +**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-/ **Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") All artifacts complete. All tasks complete. diff --git a/.qwen/skills/openspec-explore/SKILL.md b/.qwen/skills/openspec-explore/SKILL.md index 6858d3f..ca50074 100644 --- a/.qwen/skills/openspec-explore/SKILL.md +++ b/.qwen/skills/openspec-explore/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. @@ -102,11 +102,10 @@ Think freely. When insights crystallize, you might offer: If the user mentions a change or you detect one is relevant: -1. **Read existing artifacts for context** - - `openspec/changes//proposal.md` - - `openspec/changes//design.md` - - `openspec/changes//tasks.md` - - etc. +1. **Resolve and read existing artifacts for context** + - Run `openspec status --change "" --json`. + - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON. + - Read existing files from `artifactPaths..existingOutputPaths`. 2. **Reference them naturally in conversation** - "Your design mentions using Redis, but we just realized SQLite fits better..." diff --git a/.qwen/skills/openspec-propose/SKILL.md b/.qwen/skills/openspec-propose/SKILL.md index 4b7e204..51a52e1 100644 --- a/.qwen/skills/openspec-propose/SKILL.md +++ b/.qwen/skills/openspec-propose/SKILL.md @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" - generatedBy: "1.3.1" + generatedBy: "1.4.0" --- Propose a new change - create the change and generate all artifacts in one step. @@ -37,7 +37,7 @@ When ready to implement, run /opsx:apply ```bash openspec new change "" ``` - This creates a scaffolded change at `openspec/changes//` with `.openspec.yaml`. + This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`. 3. **Get the artifact build order** ```bash @@ -46,6 +46,7 @@ When ready to implement, run /opsx:apply Parse the JSON to get: - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) - `artifacts`: list of all artifacts with their status and dependencies + - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths. 4. **Create artifacts in sequence until apply-ready** @@ -63,10 +64,10 @@ When ready to implement, run /opsx:apply - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) - `template`: The structure to use for your output file - `instruction`: Schema-specific guidance for this artifact type - - `outputPath`: Where to write the artifact + - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - - Create the artifact file using `template` as the structure + - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath` - Apply `context` and `rules` as constraints - but do NOT copy them into the file - Show brief progress: "Created " From 8dad082eebb4d40faeeb79ed441b18b6815d74a8 Mon Sep 17 00:00:00 2001 From: bitc0der <59016822+bitc0der@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:32:46 +0700 Subject: [PATCH 4/8] Cleanup --- .cline/skills/openspec-apply-change/SKILL.md | 159 ---------- .../skills/openspec-archive-change/SKILL.md | 117 ------- .cline/skills/openspec-explore/SKILL.md | 287 ------------------ .cline/skills/openspec-propose/SKILL.md | 111 ------- .clinerules/workflows/opsx-apply.md | 152 ---------- .clinerules/workflows/opsx-archive.md | 157 ---------- .clinerules/workflows/opsx-explore.md | 169 ----------- .clinerules/workflows/opsx-propose.md | 104 ------- .continue/prompts/opsx-apply.prompt | 154 ---------- .continue/prompts/opsx-archive.prompt | 159 ---------- .continue/prompts/opsx-explore.prompt | 171 ----------- .continue/prompts/opsx-propose.prompt | 106 ------- .../skills/openspec-apply-change/SKILL.md | 159 ---------- .../skills/openspec-archive-change/SKILL.md | 117 ------- .continue/skills/openspec-explore/SKILL.md | 287 ------------------ .continue/skills/openspec-propose/SKILL.md | 111 ------- .../skills/openspec-apply-change/SKILL.md | 159 ---------- .../skills/openspec-archive-change/SKILL.md | 117 ------- .kilocode/skills/openspec-explore/SKILL.md | 287 ------------------ .kilocode/skills/openspec-propose/SKILL.md | 111 ------- .kilocode/workflows/opsx-apply.md | 148 --------- .kilocode/workflows/opsx-archive.md | 153 ---------- .kilocode/workflows/opsx-explore.md | 165 ---------- .kilocode/workflows/opsx-propose.md | 100 ------ .qwen/commands/opsx-apply.toml | 152 ---------- .qwen/commands/opsx-archive.toml | 157 ---------- .qwen/commands/opsx-explore.toml | 169 ----------- .qwen/commands/opsx-propose.toml | 104 ------- .qwen/skills/openspec-apply-change/SKILL.md | 159 ---------- .qwen/skills/openspec-archive-change/SKILL.md | 117 ------- .qwen/skills/openspec-explore/SKILL.md | 287 ------------------ .qwen/skills/openspec-propose/SKILL.md | 111 ------- 32 files changed, 5016 deletions(-) delete mode 100644 .cline/skills/openspec-apply-change/SKILL.md delete mode 100644 .cline/skills/openspec-archive-change/SKILL.md delete mode 100644 .cline/skills/openspec-explore/SKILL.md delete mode 100644 .cline/skills/openspec-propose/SKILL.md delete mode 100644 .clinerules/workflows/opsx-apply.md delete mode 100644 .clinerules/workflows/opsx-archive.md delete mode 100644 .clinerules/workflows/opsx-explore.md delete mode 100644 .clinerules/workflows/opsx-propose.md delete mode 100644 .continue/prompts/opsx-apply.prompt delete mode 100644 .continue/prompts/opsx-archive.prompt delete mode 100644 .continue/prompts/opsx-explore.prompt delete mode 100644 .continue/prompts/opsx-propose.prompt delete mode 100644 .continue/skills/openspec-apply-change/SKILL.md delete mode 100644 .continue/skills/openspec-archive-change/SKILL.md delete mode 100644 .continue/skills/openspec-explore/SKILL.md delete mode 100644 .continue/skills/openspec-propose/SKILL.md delete mode 100644 .kilocode/skills/openspec-apply-change/SKILL.md delete mode 100644 .kilocode/skills/openspec-archive-change/SKILL.md delete mode 100644 .kilocode/skills/openspec-explore/SKILL.md delete mode 100644 .kilocode/skills/openspec-propose/SKILL.md delete mode 100644 .kilocode/workflows/opsx-apply.md delete mode 100644 .kilocode/workflows/opsx-archive.md delete mode 100644 .kilocode/workflows/opsx-explore.md delete mode 100644 .kilocode/workflows/opsx-propose.md delete mode 100644 .qwen/commands/opsx-apply.toml delete mode 100644 .qwen/commands/opsx-archive.toml delete mode 100644 .qwen/commands/opsx-explore.toml delete mode 100644 .qwen/commands/opsx-propose.toml delete mode 100644 .qwen/skills/openspec-apply-change/SKILL.md delete mode 100644 .qwen/skills/openspec-archive-change/SKILL.md delete mode 100644 .qwen/skills/openspec-explore/SKILL.md delete mode 100644 .qwen/skills/openspec-propose/SKILL.md diff --git a/.cline/skills/openspec-apply-change/SKILL.md b/.cline/skills/openspec-apply-change/SKILL.md deleted file mode 100644 index eddd3a6..0000000 --- a/.cline/skills/openspec-apply-change/SKILL.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -name: openspec-apply-change -description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. -license: MIT -compatibility: Requires openspec CLI. -metadata: - author: openspec - version: "1.0" - generatedBy: "1.4.0" ---- - -Implement tasks from an OpenSpec change. - -**Input**: Optionally specify a change name. 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") - - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - - 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 - could be proposal/specs/design/tasks or spec/tests/implementation/docs) - - 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 openspec-continue-change - - If `state: "all_done"`: congratulate, suggest archive - - Otherwise: proceed to implementation - - **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files. - -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! Ready to archive this change. -``` - -**Output On Pause (Issue Encountered)** - -``` -## Implementation Paused - -**Change:** -**Schema:** -**Progress:** 4/7 tasks complete - -### Issue Encountered - - -**Options:** -1.