Skip to content

fix(templates): give explore the project's context and rules - #1408

Merged
clay-good merged 2 commits into
mainfrom
fix/explore-project-context
Jul 22, 2026
Merged

fix(templates): give explore the project's context and rules#1408
clay-good merged 2 commits into
mainfrom
fix/explore-project-context

Conversation

@clay-good

@clay-good clay-good commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #696.

Status: LGTM — guidance-only change to the explore skill and command templates. No CLI behavior, schema, parser, or architecture changes. Full suite green (2,032 passed; the only failures are the 17 known environment-only zsh-installer ones addressed by #1400).

What was wrong

Explore was the only workflow that never saw the project's own context.

Every artifact-creating workflow (propose, continue, ff, …) gets the context and rules from the OpenSpec config injected for free, because they all call openspec instructions <artifact-id> --change "<name>" --json, which reads the project config internally.

Explore has no artifact and often no change name, so it never travels that path. At session start it ran only:

openspec list --json

That returns active changes and nothing else. So explore — the step where you do your thinking, before anything is written down — was blind to the project's declared tech stack, conventions, and constraints. It would happily reason toward a suggestion the project's own config rules out.

How it was fixed

After the existing openspec list --json call, both the skill and the command surface now read the project's context:

Then read the project's own context from the resolved root - <root.path>/openspec/config.yaml (or config.yml). Use the root.path returned above, and skip this if neither file exists:

  • context: project background - tech stack, conventions, constraints
  • rules: keyed by artifact id - the entries for an artifact apply only when you write that artifact

Ground your thinking in these. They are constraints for you to follow, not content to reproduce: do NOT copy them into the conversation or into any artifact you create.

Four deliberate details:

  • Resolved through root.path, not a hardcoded path. openspec list --json already reports the resolved root, so this works for stores and workspace planning homes. <root.path>/openspec/config.* is exactly what instructions.ts does via readProjectConfig(root.path) for every other workflow.
  • Both filenames. resolveConfigFilePath probes config.yaml then config.yml, and init returns 'exists' for either — so a .yml project stays .yml forever. Naming only .yaml would have silently skipped context for those projects.
  • Rules stay scoped. rules is Record<artifactId, string[]>; explore holds no artifact at startup, so the guidance is explicit that entries apply when writing that artifact.
  • Leakage wording matches house style. Sibling templates and the instructions renderer forbid copying context/rules into the artifact, not just the chat. Since explore may create artifacts, the wording covers both.

Proof it works

New focused test, test/core/templates/explore.test.ts — every assertion runs against both delivery surfaces (skill and command), since the two bodies must stay in contract:

✓ test/core/templates/explore.test.ts (5 tests)
  ✓ loads project context from the OpenSpec config at startup (#696)
  ✓ resolves the config through the reported root rather than assuming a repo-local path (#696)
  ✓ accepts config.yml as well as config.yaml (#696)
  ✓ scopes rules to the artifact they are keyed to (#696)
  ✓ treats project context as constraints that must not leak into output (#696)

Reverting the template change fails all five. Regenerated skills/openspec-explore/SKILL.md via pnpm generate:skills (verified byte-identical, generator is idempotent) and refreshed the three golden hashes in skill-templates-parity.test.ts, per the repo's template-change convention.

Notes / nits

  • Guidance-only: no runtime code path is touched. Worst case for any user is an extra file read that is explicitly skippable.
  • The generated skill grows by ~390 bytes (11.4 KB → 11.8 KB, about 100 tokens).
  • The paragraph appears twice in explore.ts because that file carries the skill body and the command body separately — matching how every other workflow template in this directory is structured.
  • Possible follow-up, deliberately out of scope: openspec context --json already loads the parsed project config and discards it (context.tsgatherRelationshipData), but currently emits only root/members/status. Emitting context and rules there would let this template say openspec context --json like every other template uses a CLI call, instead of naming a config path. That is a public JSON-output contract change and a maintainer call, so it is not in this PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Explore sessions now load project context and rules from openspec/config.yaml (or config.yml) at startup.
    • This context/rules grounding is applied consistently across the Explore skill and the /opsx:explore command.
  • Bug Fixes

    • Template rendering now resolves config via the discovered project root path, avoiding repo-layout assumptions.
  • Documentation

    • Updated Explore guidance to clarify how context and rules should be used (as constraints only).
  • Tests

    • Added/updated coverage to confirm template contract parity, config.yml support, and correct root-path resolution.

@clay-good
clay-good requested a review from TabishB as a code owner July 21, 2026 16:14
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d47c6b0f-1c86-408d-8e31-3c87335a2b66

📥 Commits

Reviewing files that changed from the base of the PR and between 9633988 and bb6e1f0.

📒 Files selected for processing (5)
  • .changeset/explore-project-context.md
  • skills/openspec-explore/SKILL.md
  • src/core/templates/workflows/explore.ts
  • test/core/templates/explore.test.ts
  • test/core/templates/skill-templates-parity.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • skills/openspec-explore/SKILL.md
  • src/core/templates/workflows/explore.ts
  • .changeset/explore-project-context.md
  • test/core/templates/explore.test.ts
  • test/core/templates/skill-templates-parity.test.ts

📝 Walkthrough

Walkthrough

Explore now loads context and rules from openspec/config.yaml or config.yml using the project root returned by openspec list --json. Both template surfaces include this instruction, with tests covering the contract and updated parity hashes.

Changes

Explore context loading

Layer / File(s) Summary
Add config context instructions
skills/openspec-explore/SKILL.md, src/core/templates/workflows/explore.ts
Explore instructions read project configuration through <root.path> when present and use its context and artifact-scoped rules as constraints.
Validate and release the template change
test/core/templates/explore.test.ts, test/core/templates/skill-templates-parity.test.ts, .changeset/explore-project-context.md
Tests cover path resolution, supported config filenames, rule scoping, and non-copying constraints; parity hashes and the package patch changeset are updated.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: tabishb, alfred-openspec

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: explore now loads project context and rules.
Linked Issues check ✅ Passed The PR satisfies #696 by teaching explore to read project context from openspec/config.yaml at session start.
Out of Scope Changes check ✅ Passed The additional yml support, test updates, and hash refreshes are all directly tied to the stated explore-context change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/explore-project-context

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@clay-good

Copy link
Copy Markdown
Collaborator Author

Ran three independent adversarial review passes over this branch (runtime correctness, regression risk, design fit) before asking for review. Recording what they found and what I did, so a reviewer doesn't have to re-derive it.

Fixed in 33d9a1e

1. config.yml projects silently got nothing — flagged by all three passes, and the most serious finding: it broke the fix in exactly the way the fix was meant to prevent.

resolveConfigFilePath (src/core/project-config.ts:420-427) probes config.yaml, then falls back to config.yml. And createConfig (src/core/init.ts:758-760) returns 'exists' when either is present, so openspec init never migrates a .yml project to .yaml — it stays .yml permanently. The original wording named only config.yaml and paired it with "skip if the file does not exist", so those projects took the skip branch and lost their context silently. Now names both, and the test pins it.

2. rules was described without its keying. rules is Record<artifactId, string[]> and only rules[artifactId] is ever applied (src/core/artifact-graph/instruction-loader.ts:321-322). Explore holds no artifact at startup, so "per-artifact constraints the project expects" invited applying design rules to a proposal. Now explicit that entries apply when writing that artifact.

3. Leakage wording was aimed at the wrong target. The original said "not material to recite back to the user" — but house style everywhere else (instructions.ts:212,227, and continue-change.ts / propose.ts / ff-change.ts) forbids copying context/rules into the artifact. Explore may create artifacts, so the wording now covers conversation and artifact.

Checked and found clean

  • root is always present when --json is set. ListCommand has exactly one caller (src/cli/index.ts:239), which passes root whenever json is true. The root-absent branch in list.ts is unreachable dead defensiveness.
  • <root.path>/openspec/config.* is the right join. root.path is the project root, not the openspec/ dir (root-selection.ts:122-127), and this matches readProjectConfig(root.path) in instructions.ts — including the --store, nearest-root, and workspace-planning-home cases.
  • No stale generated artifacts. Grepped the repo for the superseded hash prefixes: zero hits. Re-ran pnpm generate:skills — the tree is clean, so the committed SKILL.md is byte-identical to generator output.
  • vocabulary-sweep passes. Its only banned pattern is context[-_ ]?store, which the added text doesn't produce.
  • Template escaping is sound. All backticks are escaped and the added lines contain no $, so no accidental interpolation. Verified in the rendered SKILL.md.
  • Lint: 0 errors (the single warning is pre-existing in src/core/references.ts, untouched here). Full suite: 2,032 passed, only the 17 known environment-only zsh-installer failures.

Raised, deliberately not done here

The design-fit pass made a fair point: no other workflow template reads an OpenSpec-owned config file directly — they all go through a CLI call. The clean answer is to have openspec context --json emit context/rules (it already parses the config and throws the fields away) and let this template call it. That changes a public JSON output contract, so it's a maintainer call rather than something to slip into a bug fix. Noted in the PR description as a follow-up. Happy to do it here instead if you'd prefer that shape.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/core/templates/explore.test.ts`:
- Around line 22-36: Strengthen the assertions in the tests covering startup
config loading so they verify ordering, not just independent substrings: use
indexOf(...) to assert that “openspec list --json” and root.path resolution
occur before the openspec/config.yaml instructions. Update the relevant test
cases in the explore template suite while preserving the existing checks and run
the specified Vitest test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e21ee40f-43e0-4cf7-89a2-21169ffa53f5

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc2cc3 and 9633988.

📒 Files selected for processing (5)
  • .changeset/explore-project-context.md
  • skills/openspec-explore/SKILL.md
  • src/core/templates/workflows/explore.ts
  • test/core/templates/explore.test.ts
  • test/core/templates/skill-templates-parity.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/core/templates/workflows/explore.ts
  • .changeset/explore-project-context.md
  • skills/openspec-explore/SKILL.md
  • test/core/templates/skill-templates-parity.test.ts

Comment on lines +22 to +36
it('loads project context from the OpenSpec config at startup (#696)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec/config.yaml');
expect(body, label).toContain('`context`: project background');
expect(body, label).toContain('`rules`: keyed by artifact id');
}
});

it('resolves the config through the reported root rather than assuming a repo-local path (#696)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec list --json');
expect(body, label).toContain('<root.path>/openspec/config.yaml');
expect(body, label).toContain('root.path');
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that config loading follows openspec list --json.

These assertions only verify independent substrings; they would pass if the config instructions appeared before root resolution or were unrelated to the openspec list --json step. Assert the relative ordering using indexOf(...) so the startup contract is protected.

+      const listIndex = body.indexOf('openspec list --json');
+      const configIndex = body.indexOf('<root.path>/openspec/config.yaml');
+      expect(configIndex).toBeGreaterThan(listIndex);

Run with pnpm exec vitest run test/core/templates/explore.test.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('loads project context from the OpenSpec config at startup (#696)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec/config.yaml');
expect(body, label).toContain('`context`: project background');
expect(body, label).toContain('`rules`: keyed by artifact id');
}
});
it('resolves the config through the reported root rather than assuming a repo-local path (#696)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec list --json');
expect(body, label).toContain('<root.path>/openspec/config.yaml');
expect(body, label).toContain('root.path');
}
});
it('loads project context from the OpenSpec config at startup (`#696`)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec/config.yaml');
expect(body, label).toContain('`context`: project background');
expect(body, label).toContain('`rules`: keyed by artifact id');
}
});
it('resolves the config through the reported root rather than assuming a repo-local path (`#696`)', () => {
for (const [label, body] of bodies) {
expect(body, label).toContain('openspec list --json');
expect(body, label).toContain('<root.path>/openspec/config.yaml');
expect(body, label).toContain('root.path');
const listIndex = body.indexOf('openspec list --json');
const configIndex = body.indexOf('<root.path>/openspec/config.yaml');
expect(configIndex).toBeGreaterThan(listIndex);
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/templates/explore.test.ts` around lines 22 - 36, Strengthen the
assertions in the tests covering startup config loading so they verify ordering,
not just independent substrings: use indexOf(...) to assert that “openspec list
--json” and root.path resolution occur before the openspec/config.yaml
instructions. Update the relevant test cases in the explore template suite while
preserving the existing checks and run the specified Vitest test.

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at 9633988: the resolved-root config guidance is scoped correctly across both delivery surfaces, and the focused template/parity tests plus a clean regeneration pass locally. The ordering-test nit is non-blocking because the generated instructions themselves have the required order.

clay-good and others added 2 commits July 22, 2026 09:41
Explore was the only workflow that never loaded openspec/config.yaml.
Every artifact-creating workflow receives the project's `context` and
`rules` through `openspec instructions --json`, but explore has no
artifact or change name, so it never travels that path — it started a
session knowing only what `openspec list --json` returns.

The result was a thinking partner blind to the project's own tech stack,
conventions, and constraints.

Both the skill and command surfaces now read the config through the
`root.path` reported by `openspec list --json`, so stores and workspace
planning homes resolve correctly instead of assuming a repo-local path.
Guidance-only: no CLI behavior, schema, or architecture changes.

Fixes #696

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-ups on the explore context guidance:

- `config.yml` is a first-class alternative to `config.yaml`
  (`resolveConfigFilePath` probes both, and `init` leaves a `.yml`
  project on `.yml` permanently). Naming only `.yaml` meant those
  projects hit the skip-if-missing branch and silently lost their
  context - the exact failure this change set out to fix.

- `rules` is keyed by artifact id, and explore holds no artifact at
  startup. The guidance now says the entries apply when writing that
  artifact, so rules for one artifact are not applied to another.

- Match house style on leakage: every sibling template and the
  instructions renderer forbid copying context/rules into the artifact,
  not just into the conversation. The wording now covers both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rebase preserves the reviewed resolved-root config guidance, config.yml handling, artifact-scoped rules, and non-leakage boundary. Exact-head build, clean skill regeneration, 15 focused tests, and the full CI/CodeQL matrix pass.

@clay-good
clay-good added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit 378d468 Jul 22, 2026
14 checks passed
@clay-good
clay-good deleted the fix/explore-project-context branch July 22, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Explore skill doesn't read project context from config.yaml

2 participants