Repository navigation
Sync to upstream OpenSpec v1.2.0 → v1.4.1 (release v0.2.0) - #1
Merged
Merged
Conversation
Authored via the OpenSpec workflow (explore→propose). Covers Bucket B (tools, parser, validation, completion, telemetry, profile) and Bucket A (workspace, initiatives, context-store) in one change. 4/4 artifacts valid. Reference diff artifacts in docs/sync-v1.2-to-v1.4.1/. Baseline marked at vendor tag rs-sync-baseline (afdca0d); vendor now at v1.4.1.
Reviewed implementation of the stable, low-risk portion of the sync-upstream-v1-4-1 change. - ai_tools: add Bob Shell, ForgeCode, Junie, Kimi CLI, Lingma, Mistral Vibe; add detection_paths + GitHub Copilot detection (fixes bare .github/ false positive); consolidate duplicated detect_available_tools into generator. - spec_parser: case-insensitive requirement headers across ADDED/MODIFIED/ REMOVED/RENAMED; regression test locks in detection of requirements nested in fenced code blocks. - validate: hint when SHALL/MUST appears only in a requirement header (check the body, not raw, so header-only requirements are correctly flagged). - telemetry: 1s timeout on PostHog sends, errors swallowed (firewall-silent). - config: core profile now matches upstream CORE_WORKFLOWS [propose, explore, apply, sync, archive]. Also adds docs/sync-v1.2-to-v1.4.1/bucket-a-plan.md (context-store interop contract + Bucket A roadmap). cargo build/test (96)/clippy clean; openspec validate --specs --all 8/8.
New src/core/context_store/ module — the interop-critical data layer for the context-store subsystem. - foundation: constants, path helpers (with global_data_dir override mirroring upstream ContextStorePathOptions), kebab-case id validation, serde structs for registry.yaml/store.yaml (type:git backend, snake_case fields), parse/serialize, atomic write. - registry: load/save (testable via global_data_dir override), list entries, conflict detection (same-id and same-path), path normalization. Review fixes: added the global_data_dir override that was dropped (upstream parity + testability) and replaced two hollow tests with real disk round-trips. No CLI yet (increment 2 = operations + command wiring). cargo test 112, clippy clean.
Rust's xdg_config_dir/xdg_data_dir used the `dirs` crate, which on macOS resolves to ~/Library/Application Support — diverging from upstream OpenSpec, which uses ~/.config and ~/.local/share on macOS/Linux. This meant the Rust binary and npm CLI did not share global config or context stores on macOS. - Reimplement both via a testable resolve_global_dir mirroring upstream getGlobalConfigDir/getGlobalDataDir (XDG_*_HOME wins; Windows env dirs; macOS/Linux dotfile paths). - Add best-effort one-time migration of legacy macOS global config at startup. - Add dir-resolution unit tests; document the requirement in the change's config-management spec delta. cargo test 115, clippy clean; openspec validate change valid.
Operations layer for the context-store subsystem (setup, register, unregister, remove, list, doctor) + helpers (path_kind, git init/detect, metadata read/write, id inference). Result structs use camelCase serde for upstream-compatible --json. Review fixes: - infer_store_id_from_path now mirrors upstream exactly (validate basename, no sanitization) — the prior version added underscore/space->hyphen sanitization that silently accepted invalid basenames upstream rejects. Added a regression test. - dropped an unused test binding. Known gap (deferred): git-nesting guard (allowInsideGitRepository) for `setup --path <inside-a-repo>` not yet implemented; default store root is unaffected. No CLI wiring yet (increment 2b). cargo test 130, clippy clean.
Wire the context-store operations into the CLI as
`openspec context-store <setup|register|unregister|remove|list|doctor>`
with --json output and human-readable summaries.
- args.rs: ContextStore(ContextStoreCommands) clap group + dispatch +
telemetry command paths.
- cli/context_store.rs: handlers calling the operations layer; --json via
serde (camelCase, upstream-compatible); `remove` requires --yes.
Review fixes:
- remove-without--yes returned a single guidance error (was double-printed
via both eprintln and main's handler).
- replaced a reinvented PadEnd trait with idiomatic `{:<16}` formatting.
Verified end to end against an isolated XDG_DATA_HOME: registry.yaml on disk
matches the upstream schema; doctor --json emits camelCase. cargo test 130,
clippy clean.
Known gap (deferred): git-nesting guard for `setup --path <inside-a-repo>`.
Initiatives data layer under src/core/collections/initiatives/ (schema, markdown templates, operations) working against a context-store root. - schema: InitiativeState (version/id/title/summary/status/created/owners/ metadata) with serde deny_unknown_fields and upstream field order; status enum (lowercase); validation mirroring upstream (kebab-case id, non-blank title/summary, YYYY-MM-DD created, non-blank owners); parse/serialize. - templates: the 5 markdown builders (requirements/design/decisions/questions/ tasks), byte-for-byte matching upstream. - operations: create (mkdir + exclusive writes + cleanup-on-failure), read, list (sorted, id-must-match-folder); create takes a CreateInitiativeInput struct. Review fix: create_initiative refactored from 8 positional args to a CreateInitiativeInput struct (clippy too_many_arguments); all call sites updated. No CLI or context-store resolution yet (increment 3b). cargo test 163, clippy clean.
Expose initiatives as `openspec initiative <create|show|list>` with --store/--store-path/--json. - initiatives/resolution.rs: SelectedStore; registered_stores; resolve_selected_store (--store registry lookup / --store-path with metadata-id fallback / required-selector + conflict errors mirroring upstream); find_initiative_across_stores. - cli/initiative.rs: create (requires a selector + title/summary), show (selector → that store; else scan all stores: not-found / ambiguous / single), list (selector → one store; else across all registered stores); --json (camelCase) + human output. - args.rs: Initiative(InitiativeCommands) clap group + dispatch + command paths. Review fix: find_initiative_across_stores now propagates a parse error instead of silently skipping a corrupted initiative (was `if let Ok(Some(..))`). Verified end to end against an isolated XDG_DATA_HOME: initiative.yaml matches the upstream schema; ambiguous/not-found show paths behave correctly. cargo test 172, clippy clean.
New src/core/workspace/ data layer for the beta workspace subsystem.
- foundation: constants, workspace-name (kebab) + link-name (folder-style)
validation, view.yaml serde types (context store binding/selector, context,
preferred_opener, tools, workspace_skills) with deny_unknown_fields and
required nullable `context` (always emits context: null), parse/serialize.
- registry: workspaces/registry.yaml {version, workspaces: name->root} with
global_data_dir override, load/save/list.
- legacy_state: legacy workspace.yaml (shared) + .openspec-workspace/local.yaml
parsing and merge into a view state.
- state_io: is_workspace_root (view.yaml OR a workspace.yaml that PARSES as
OpenSpec shared state — foreign workspace.yaml is ignored, upstream #1165),
find_workspace_root (walk up), read/write view state, changes-dir check.
Review fix: removed three dead/redundant path-helper wrappers in state_io.
No CLI / openers / skills yet (later increments). cargo test 201, clippy clean.
Workspace opener support. - foundation: WORKSPACE_SUPPORTED_OPENER_VALUES / AGENT / EDITOR opener id constants; parse_workspace_preferred_opener_value (editor->vscode, agent ids -> agent opener, else error) and validate_workspace_preferred_opener. - openers: WorkspaceOpenerChoice; is_workspace_executable_available (PATH + Windows PATHEXT lookup, Unix exec-bit check) with a testable internal variant; get_workspace_opener_executable / _label; list_workspace_opener_choices (availability-sorted) and get_default_workspace_opener_choice_value. cargo test 242, clippy clean.
Workspace "open surface" generation. - foundation: get_workspace_context_initiative_id; code-workspace file name/path helpers (validated). - open_surface: guidance markers + body (byte-for-byte with upstream), build/apply guidance block in AGENTS.md (insert or replace between markers, error on malformed marker state), build_workspace_code_workspace_content (JSON folders: links + optional initiative-context + workspace root, 2-space indent), resolve_workspace_open_links (sorted; skip missing-local-path / path-missing), sync_workspace_open_surface (write AGENTS.md + <name>.code- workspace, clean legacy .gitignore). Idempotent re-sync. Review fix: strengthened the sync test to assert the returned generation paths. cargo test 259, clippy clean.
Faithful port of the workspace skills installation layer. - templates/skills: CORE_WORKFLOWS const; get_profile_workflows (core set vs custom workflows); transform_to_hyphen_commands (/opsx: -> /opsx- for opencode/pi); generate_skill_content_transformed; extract_generated_by_version (reads the SKILL.md `generatedBy: "OpenSpec v.."` frontmatter to detect OpenSpec-managed skill dirs). - workspace/skills: installation report (generated/added/refreshed/removed/ skipped/failed, snake_case fields); profile/delivery/workflow context from global config; profile-drift detection; --tools value parsing (all/none/csv with validation); generate_workspace_agent_skills and update_workspace_agent_skills (write per-tool SKILL.md, refresh vs generate vs add, remove unselected agents/workflows by managed-dir detection). cargo test 287, clippy clean.
Flag-driven `openspec workspace <list|ls|link|relink|doctor>` over the workspace data layer. - workspace/commands_support: SelectedWorkspace; resolve_selected_workspace (by --workspace name from the registry, or find the workspace containing cwd); infer_link_name; resolve_existing_directory (canonicalize + must-be-dir); add_workspace_link / update_workspace_link (validate, mutate view.yaml, sync open surface), with status reporting. - cli/workspace: list/ls (registry rows + per-workspace status incl. skill drift), link, relink, doctor (per-link existence + state/planning paths); --json (structured) + human output. - args.rs: Workspace(WorkspaceCommands) clap group + dispatch + command paths. setup/update/open deferred to W3b. Verified end to end against an isolated XDG_DATA_HOME + workspace fixture. cargo test 297, clippy clean.
…t A, inc W3b) Completes the workspace subsystem CLI (flag-driven). - commands_support: get_managed_workspace_root; create_managed_workspace (create under managed dir, write view.yaml, sync open surface, register in the workspace registry, cleanup on failure); parse_setup_links (<path> or <name>=<path>, dedup, validate). - cli/workspace: setup (requires --name + >=1 --link; optional --opener/--tools; installs skills or records skipped report), update (refresh open surface + update agent skills for the selected/current workspace), open (resolve opener from --editor/--agent/stored/default, reject conflicting selectors, require the executable on PATH, sync surface, launch detached). Single run() dispatch. - cli/update: top-level `openspec update` prints a workspace-detected hint when run in a workspace root, then proceeds with the normal project update (does not route into workspace update); foreign workspace.yaml is ignored. Review fixes: implemented open faithfully (it was a stub that neither validated the --editor/--agent conflict nor launched); unified dispatch through run() (removed dead "handled in args.rs" stub arms and the split dispatch). Verified end to end against an isolated XDG_DATA_HOME. cargo test 305, clippy clean.
- bump version 0.1.4 -> 0.2.0 - CHANGELOG: v0.2.0 entry (new tools, parser/validation/completion/telemetry fixes, macOS dir interop, context-store / initiatives / workspace subsystems, known gaps) - docs/STATE.md: sync marker -> v1.4.1 (baseline tag rs-sync-baseline), last synced 2026-06-20, compatibility table + releases + current-version section cargo test 305, clippy clean; openspec validate --all 8/8.
`context-store setup --path <p>` now refuses a path inside an existing Git repository (a containing/ancestor repo; a repo created at the store root by --init-git does not count), mirroring upstream's context_store_setup_inside_git_repo guard. Adds a --allow-inside-git-repository escape hatch (the flag-driven equivalent of upstream's interactive confirm). The default managed store root is unaffected. The check runs before any filesystem mutation. Also: collapse the context-store CLI dispatch to a single run(cmd) call (run_setup is private again) instead of a split match in args.rs; correct the CHANGELOG/STATE.md note (the guard is context-store, not workspace) and record it as resolved. cargo test 310, clippy clean; openspec validate --all 8/8.
CI runs `cargo fmt --all -- --check` and `cargo clippy --all-targets -- -D warnings`, which are stricter than a plain `cargo clippy`. - apply rustfmt across the new modules - clippy (test code): drop needless array-literal borrows in git-init calls, use !is_empty() instead of len() > 0, and entries.flatten() instead of `if let Ok(..)` loops cargo fmt --check, clippy --all-targets -D warnings, check --all-targets, and test (310) all clean.
- note the port tracks upstream v1.4.1 - features: 30 AI tools; coordination (context stores / initiatives / workspaces, beta) - CLI table: add context-store / initiative / workspace - new "Coordination" section with example commands - project structure: add core/context_store, core/collections, core/workspace - AI tool integration: reflect 30 tools + auto-detection - shell completions: mention opt-in `completion install`
Audit found the global config.json diverged from the npm CLI: Rust wrote
snake_case `feature_flags` and `telemetry.{anonymous_id,notice_seen}` while
upstream uses `featureFlags` / `anonymousId` / `noticeSeen` in the same
~/.config/openspec/config.json. A flag or telemetry state written by one tool
was invisible to the other.
- add #[serde(rename_all = "camelCase")] to GlobalConfig and TelemetryConfig
- keep snake_case `#[serde(alias = ...)]` so configs written by earlier Rust
versions still load (no lost anonymousId / re-shown first-run notice)
- skip serializing `telemetry` when None (no more `"telemetry": null`)
- append a trailing newline on write, matching upstream
(Note: telemetry IS stored in config.json upstream too — the fix is camelCase,
not removing it.)
cargo test 313, fmt + clippy --all-targets -D warnings clean; validate --all 8/8.
The spec parser was purely line-based and treated a `### Requirement:`, `#### Scenario:`, or `## Section` line inside a ``` / ~~~ code fence as a real header — diverging from upstream (MarkdownParser.buildCodeFenceMask) and inflating requirement/scenario counts for any spec containing a fenced example. - add build_code_fence_mask (mirrors upstream: 3+ backtick/tilde fences, marker + length matched on close, unclosed fence masks to EOF) - apply it at every line-based header scan: parse_sections / get_content_until_next_header, split_top_level_sections, the requirement-block parsers, extract_requirements_section, and count_scenarios (validate.rs) - replace the earlier test that asserted the wrong (fenced-headers-parsed) behavior; correct the spec-parser change spec wording Note: this corrects an over-claim in an earlier commit of this branch — upstream #995 was about flagging hidden requirements during validation (handled separately), not parsing fenced headers as real. cargo test 314, fmt + clippy --all-targets -D warnings clean; validate --all 8/8.
`openspec validate` was false-passing artifacts upstream rejects. validate_spec (mirrors base.schema.ts/spec.schema.ts; messages from constants.ts): ERROR (was WARNING/absent) when a main spec has no requirements, a requirement has empty text, a requirement body lacks SHALL/MUST, or a requirement has no scenario. validate_change (mirrors validator.ts): detect duplicate requirement names within ADDED/MODIFIED/REMOVED, duplicate FROM/TO in RENAMED, and cross-section conflicts (MODIFIED+REMOVED, MODIFIED+ADDED, ADDED+REMOVED, MODIFIED referencing a renamed FROM, RENAMED TO colliding with ADDED) — the archive merge already enforced these, so validate and archive now agree. --strict is now honored: warnings fail validation (valid = no errors && (!strict || no warnings)), matching upstream createReport. Also fixes a latent defect the stricter rule surfaced: openspec/specs/telemetry "Anonymous Usage Tracking" used MAY with no normative keyword (upstream would reject it too) — reworded to include SHALL NOT. cargo test 320, fmt + clippy --all-targets -D warnings clean; validate --all 8/8 (and --strict 8/8).
…date archive applied delta merges and wrote the rebuilt main spec without re-validating it, and ignored --no-validate. A merge that produced a structurally invalid spec would be silently persisted. - factor spec validation into validate::collect_spec_issues (content-based), reused by the validate command and archive - after merge, re-validate the rebuilt spec; if it has ERRORs, abort the archive with the issues listed (mirrors upstream applySpecs re-validation) instead of writing a broken spec - --no-validate now actually skips this gate (was ignored) (The merge already enforced duplicate/cross-section/rename checks; this adds the post-merge structural guard.) cargo test 320, fmt + clippy --all-targets -D warnings clean; validate --all 8/8.
`openspec show --json` emitted snake_case keys, no spec metadata block, and a
broken change-delta shape (RENAMED encoded as a "RENAMED from X" string, flat
{operation, requirement} deltas) — incompatible with the npm CLI's JSON.
- spec: emit { id, title, overview, requirementCount, requirements:[{text,
scenarios:[{rawText}]}], metadata:{version, format:"openspec"} } (camelCase,
metadata block added).
- change: emit { id, title, deltaCount, deltas:[{spec, operation, description,
requirement?, rename?}] }; operation is the ADDED/MODIFIED/REMOVED/RENAMED
enum; RENAMED uses rename:{from,to} (not a string); deltas are sourced from
the change's specs/<capability>/spec.md delta files (like merge/validate),
replacing the old proposal-scraping path.
- add SpecParser::parse_delta_section_requirements helper reused for delta
requirement objects.
cargo test 323, fmt + clippy --all-targets -D warnings clean; validate --all 8/8;
verified show --json shapes against our own change + a spec.
These upstream commands were missing from the Rust port.
- `openspec templates [--schema <name>] [--json]`: show resolved template paths
for each artifact in a schema (default spec-driven); --json emits a sorted
{artifactId: templatePath} map.
- `openspec schema which [name] [--all] [--json]`: show where a schema resolves
from (project > user > package, with shadow detection); --all lists all
schemas grouped by source.
- `openspec schema validate [name] [--json]`: validate schema structure via the
existing ResolvedSchema::validate(); nonzero exit on invalid.
Reuses the existing schema resolution/listing infra. `schema fork`/`init` and
`set change` are intentionally not included here (follow-up).
Note: package-schema resolution still uses the pre-existing get_package_schemas_dir
(`vendor/OpenSpec/schemas`), which only exists in this dev checkout; installed
binaries fall back to the embedded schema. Tracked as a separate follow-up.
cargo test 329, fmt + clippy --all-targets -D warnings clean; validate --all 8/8.
Completes the schema command group with the two write subcommands. - `schema fork <source> [name] [--force] [--json]`: copy an existing schema into openspec/schemas/<name>/ for customization. On-disk sources are copied; the embedded spec-driven source is materialized (schema.yaml + embedded templates), so it works for installed binaries with no vendor/ dir. Default name <source>-custom; --force overwrites. - `schema init <name> [--force] [--json]`: scaffold a new project-local schema (schema.yaml with the standard proposal→specs→design→tasks artifacts + apply phase, plus template files). Forked/init'd schemas pass `schema validate`. - kebab-case name validation; --json result objects; nonzero exit on invalid-name / not-found / already-exists. Review fix: fork_error/init_error returned Err AND eprintln'd, double-printing "Error:" (main prints it too) — now return a single composed error. Simplified vs upstream: init omits --description/--artifacts/--default and the interactive prompts (flag-driven port). cargo test 335, fmt + clippy --all-targets -D warnings clean; validate --all 8/8.
`openspec set change <name> --initiative <id> [--store <id>] [--store-path <p>]
[--json]` writes an initiative link into the change's .openspec.yaml.
- ChangeMetadata gains optional `initiative: { store, id }` (InitiativeLink),
serialized only when present; legacy {schema, created} files still parse.
- resolves the initiative via the existing initiatives resolution (a selected
store, else scan registered stores: 0 → not-found, >1 → ambiguous).
- idempotent when already linked to the same initiative (linked:false, exit 0);
errors when already linked to a different one; preserves schema + created.
- single-Err pattern for human errors; --json error prints {change:null,error}.
cargo test 344, fmt + clippy --all-targets -D warnings clean; validate --all 8/8;
verified link/idempotent/conflict + .openspec.yaml round-trip in a temp project.
Expand the [0.2.0] entry with the parity-audit work (config camelCase, fenced-code-block parsing now ignored, validation strictness, show --json shape, archive re-validation) and the new commands (templates, schema, set change); fix the now-inaccurate "fenced code blocks are detected" line; note the known gaps (feedback/view not ported; dev-only package-schema path). Add templates/schema/set to the README command table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sync to upstream OpenSpec v1.2.0 → v1.4.1 (release v0.2.0)
Ports the upstream OpenSpec delta from
v1.2.0-5-gafdca0d(baseline tagrs-sync-baseline) to v1.4.1 into the Rust binary. Authored via the OpenSpec workflow (thesync-upstream-v1-4-1change underopenspec/changes/, which validates).Bucket B — stable fixes & additions
detectionPaths-based auto-detection (fixes GitHub Copilot false positive from a bare.github/).--jsonno longer leaks spinner text to stderr.syncworkflow included in the defaultcoreprofile.macOS interop fix
~/.config,~/.local/shareinstead of~/Library/Application Support), with a best-effort migration — so the Rust binary and the npm CLI share locations.New subsystems (on-disk formats byte-compatible with the npm CLI)
openspec context-store <setup|register|unregister|remove|list|doctor>; globalcontext-stores/registry.yaml+ per-store.openspec-store/store.yaml.setup --pathrefuses a path inside an existing git repo (--allow-inside-git-repositoryto override).openspec initiative <create|show|list>(--store/--store-path/--json);<store>/initiatives/<id>/withinitiative.yaml+ 5 markdown files.openspec workspace <setup|list|link|relink|doctor|update|open>;.openspec-workspace/view.yamlview state, managedworkspaces/registry.yaml, AGENTS.mdguidance +
<name>.code-workspacegeneration, per-tool workspace skills. Top-levelopenspec updateprints a hint in a workspace root but stays project-scoped; foreign rootworkspace.yamlfiles are ignored.Release
0.1.4→0.2.0; CHANGELOG[0.2.0];docs/STATE.mdsync marker →v1.4.1.Verification
cargo build --release,cargo test(310 passing),cargo clippy(clean).openspec validate --all→ 8/8.