From baab508a6803175f1d505fdca72ee6d73a9da182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Lozano=20Garci=CC=81a?= Date: Thu, 12 Feb 2026 18:03:49 +0100 Subject: [PATCH] feat: inject project context into all skills and enforce constraints Add `openspec instructions --context` CLI command to expose project context from config.yaml independently of any change. Update all skill templates (explore, continue, apply, ff, archive, bulk-archive, sync, verify) to load project context at session start and follow it as mandatory constraints. Strengthen enforcement language from "IMPORTANT" to "MANDATORY" for context, rules, and instruction fields across all skill and opsx command templates. Inject project context into apply instructions output via a new `context` field. --- docs/cli.md | 33 +++- .../project-context-all-skills/.openspec.yaml | 2 + .../project-context-all-skills/design.md | 120 +++++++++++++ .../project-context-all-skills/proposal.md | 30 ++++ .../specs/cli-instructions-context/spec.md | 42 +++++ .../specs/instruction-loader/spec.md | 32 ++++ .../specs/skill-context-enforcement/spec.md | 57 +++++++ .../project-context-all-skills/tasks.md | 63 +++++++ src/cli/index.ts | 22 ++- src/commands/workflow/index.ts | 2 +- src/commands/workflow/instructions.ts | 54 +++++- src/commands/workflow/shared.ts | 1 + src/core/templates/skill-templates.ts | 116 ++++++++++--- test/commands/artifact-workflow.test.ts | 161 ++++++++++++++++++ 14 files changed, 698 insertions(+), 37 deletions(-) create mode 100644 openspec/changes/project-context-all-skills/.openspec.yaml create mode 100644 openspec/changes/project-context-all-skills/design.md create mode 100644 openspec/changes/project-context-all-skills/proposal.md create mode 100644 openspec/changes/project-context-all-skills/specs/cli-instructions-context/spec.md create mode 100644 openspec/changes/project-context-all-skills/specs/instruction-loader/spec.md create mode 100644 openspec/changes/project-context-all-skills/specs/skill-context-enforcement/spec.md create mode 100644 openspec/changes/project-context-all-skills/tasks.md diff --git a/docs/cli.md b/docs/cli.md index e064e9dac..472ca4d3d 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -458,7 +458,7 @@ Next: Create design using /opsx:continue ### `openspec instructions` -Get enriched instructions for creating an artifact or applying tasks. Used by AI agents to understand what to create next. +Get enriched instructions for creating an artifact, applying tasks, or retrieving project context. Used by AI agents to understand what to create next. ``` openspec instructions [artifact] [options] @@ -476,10 +476,21 @@ openspec instructions [artifact] [options] |--------|-------------| | `--change ` | Change name (required in non-interactive mode) | | `--schema ` | Schema override | +| `--context` | Output project context from `config.yaml` (incompatible with `--change`, `--schema`, artifact) | | `--json` | Output as JSON | **Special case:** Use `apply` as the artifact to get task implementation instructions. +**Modes:** + +This command operates in three modes: + +1. **Artifact instructions** (default): Get instructions for creating a specific artifact +2. **Apply instructions** (`apply` argument): Get task implementation instructions with progress tracking +3. **Context-only** (`--context` flag): Return just the project context from `config.yaml` + +The `--context` flag is exclusive — it cannot be combined with `--change`, `--schema`, or an artifact argument. + **Examples:** ```bash @@ -494,14 +505,30 @@ openspec instructions apply --change add-dark-mode # JSON for agent consumption openspec instructions design --change add-dark-mode --json + +# Get project context (text) +openspec instructions --context + +# Get project context (JSON) +openspec instructions --context --json ``` **Output includes:** - Template content for the artifact -- Project context from config +- Project context from `config.yaml` - Content from dependency artifacts -- Per-artifact rules from config +- Per-artifact rules from `config.yaml` + +**Context-only output (JSON):** + +```json +{ + "context": "This is a TypeScript CLI tool that runs on Node.js 18+..." +} +``` + +If no `config.yaml` exists or it has no `context` field, returns `{"context": null}` in JSON mode or no output in text mode. --- diff --git a/openspec/changes/project-context-all-skills/.openspec.yaml b/openspec/changes/project-context-all-skills/.openspec.yaml new file mode 100644 index 000000000..95d284aff --- /dev/null +++ b/openspec/changes/project-context-all-skills/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-02-12 diff --git a/openspec/changes/project-context-all-skills/design.md b/openspec/changes/project-context-all-skills/design.md new file mode 100644 index 000000000..e44e7dc01 --- /dev/null +++ b/openspec/changes/project-context-all-skills/design.md @@ -0,0 +1,120 @@ +## Context + +Project context (`config.yaml` → `context` field) reaches only skills that call `openspec instructions ` — Continue, FF, New, Onboard. Skills that call `openspec instructions apply` (Apply, Verify) or no instructions at all (Explore, Archive, Bulk-archive, Sync) operate blind to project constraints. + +Additionally, skills that do receive `context`, `rules`, and `instruction` use weak enforcement language ("guidance", "apply as constraints") rather than mandatory directives. + +Current architecture: + +``` +readProjectConfig() + │ + └──► generateInstructions() ──► ArtifactInstructions { context, rules } + └──► `instructions ` command + └──► Continue, FF, New, Onboard ✅ + + ╳ generateApplyInstructions() ──► ApplyInstructions { NO context } + └──► `instructions apply` command + └──► Apply, Verify ❌ + + ╳ (no pathway) ──► Explore, Archive, Bulk-archive, Sync ❌ +``` + +## Goals / Non-Goals + +**Goals:** +- Every skill that generates, validates, or reasons about code/artifacts has access to project context +- `context`, `rules`, and `instruction` are communicated as mandatory constraints in all skill prompts +- Minimal CLI API surface change — reuse existing `instructions` command + +**Non-Goals:** +- Changing how `rules` work (they remain per-artifact, only relevant to artifact-creating skills) +- Adding context to the Feedback skill (not project-work related) +- Caching or performance optimization of `readProjectConfig()` (already benchmarked as fast enough) + +## Decisions + +### 1. Add `--context` flag to existing `instructions` command + +**Choice:** New `--context` flag on `openspec instructions` rather than a separate `openspec context` command. + +**Rationale:** The `instructions` command is already the single entry point for AI agents to get guidance. Adding `--context` keeps the API surface small. The flag works without `--change` or artifact arguments since project context is change-independent. + +**Usage:** +```bash +openspec instructions --context # text output +openspec instructions --context --json # { "context": "..." } +``` + +**Behavior:** +- `--context` can be used alone (text output) or with `--json` (structured output) +- `--context` is incompatible with `--change`, `--schema`, and artifact arguments. Error if any are combined. +- Reads `config.yaml` via `readProjectConfig()` +- Returns only the `context` field (not `rules` — those are per-artifact) +- Returns empty/null gracefully if no config or no context defined + +**Alternative considered:** Separate `openspec context` command. Rejected because it fragments the instruction pathway and adds a command that only returns one field. + +### 2. Add `context` to `ApplyInstructions` + +**Choice:** Enrich `generateApplyInstructions()` to call `readProjectConfig()` and include `context` in the response, same pattern as `generateInstructions()`. + +**Rationale:** Apply and Verify already call `instructions apply --json`. Adding `context` to the response means these skills get project context without changing their flow. + +**Changes:** +- `ApplyInstructions` interface: add `context?: string` +- `generateApplyInstructions()`: call `readProjectConfig()`, extract `context` +- `printApplyInstructionsText()`: print `` block (same format as artifact instructions) + +### 3. Standardize enforcement language across all skill prompts + +**Choice:** Define a consistent block of text that every skill uses when describing how to handle `context`, `rules`, and `instruction`. + +**Pattern for artifact-creating skills** (Continue, FF, New, Onboard): +``` +**You MUST follow these fields from the instructions output:** +- `context`: Project constraints. You MUST follow these when creating artifacts. Do NOT include in output. +- `rules`: Artifact-specific rules. You MUST follow these. Do NOT include in output. +- `instruction`: Directives for how to create this artifact. You MUST follow these. +``` + +**Pattern for code-operating skills** (Apply, Verify): +``` +**You MUST follow the `context` field** from the instructions output. +This contains project constraints (tech stack, conventions, cross-platform rules) +that you MUST respect when implementing/verifying code. Do NOT include in output. +``` + +**Pattern for change-independent skills** (Explore, Archive, Bulk-archive, Sync): +``` +At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a `context` field, you MUST follow these project constraints +throughout the session. +``` + +### 4. Per-skill context consumption + +| Skill | Context source | What changes | +|-------|---------------|--------------| +| **Continue** | `instructions --json` | Strengthen enforcement language | +| **FF** | `instructions --json` | Strengthen enforcement language | +| **New** | `instructions --json` | Strengthen enforcement language | +| **Onboard** | `instructions --json` | Strengthen enforcement language | +| **Apply** | `instructions apply --json` | Add context consumption + enforcement | +| **Verify** | `instructions apply --json` | Add context consumption + enforcement | +| **Explore** | `instructions --context --json` | Add context loading at session start | +| **Archive** | `instructions --context --json` | Add context loading at session start | +| **Bulk-archive** | `instructions --context --json` | Add context loading at session start | +| **Sync** | `instructions --context --json` | Add context loading at session start | +| **Feedback** | N/A | No changes (not project-work related) | + +## Risks / Trade-offs + +**[Prompt length increase]** → Every skill prompt grows by a few lines. Acceptable — clarity is worth the tokens. + +**[`--context` flag overlap with other options]** → `--context` errors if combined with `--change`, `--schema`, or an artifact argument. Strict validation, no ambiguity. + +**[No config file]** → Skills calling `--context` when no `config.yaml` exists. → Return gracefully (empty context), skill continues without constraints. Same behavior as `generateInstructions()` today. diff --git a/openspec/changes/project-context-all-skills/proposal.md b/openspec/changes/project-context-all-skills/proposal.md new file mode 100644 index 000000000..92bfd3924 --- /dev/null +++ b/openspec/changes/project-context-all-skills/proposal.md @@ -0,0 +1,30 @@ +## Why + +The project context defined in `openspec/config.yaml` (tech stack, conventions, cross-platform rules) doesn't reach most skills. Only skills that call `openspec instructions ` (Continue, FF, New, Onboard) receive it. Skills that call `openspec instructions apply` (Apply, Verify) or don't call instructions at all (Explore, Archive, Bulk-archive, Sync) operate without knowing basic project constraints. Additionally, even skills that do receive `context`, `rules`, and `instruction` describe them with weak language ("guidance", "apply as constraints") rather than treating them as mandatory directives. + +Ref: [GitHub Issue #696](https://github.com/Fission-AI/OpenSpec/issues/696) + +## What Changes + +- Add `context` field to `ApplyInstructions` interface and `generateApplyInstructions()` function, reading it from `readProjectConfig()` — same pattern as `generateInstructions()` already does for artifact instructions +- Add `openspec instructions --context` standalone flag that returns the project context from `config.yaml` without requiring a change name or artifact ID. Supports `--json` for structured output. Incompatible with `--change`, `--schema`, and artifact arguments; error if combined +- Update **all** skill prompts to consume project context and to use mandatory language ("you MUST follow", not "apply as constraints") for `context`, `rules`, and `instruction` fields +- Document the new `--context` flag in CLI documentation + +## Capabilities + +### New Capabilities +- `cli-instructions-context`: Standalone `--context` flag on the `instructions` command that returns project context without requiring a change or artifact +- `skill-context-enforcement`: Consistent, mandatory language across all skill prompts for how `context`, `rules`, and `instruction` fields must be followed + +### Modified Capabilities +- `instruction-loader`: Add `context` field to `ApplyInstructions` so `instructions apply` returns project context alongside tasks and progress + +## Impact + +- `src/commands/workflow/instructions.ts` — `generateApplyInstructions()` must call `readProjectConfig()` and include `context` in output; `instructionsCommand()` must handle `--context` flag +- `src/commands/workflow/shared.ts` — `ApplyInstructions` interface gets `context` field +- `src/core/templates/skill-templates.ts` — All skill template prompts updated for context consumption and enforcement language +- `.claude/commands/opsx/*.md` — All generated slash command files updated accordingly +- CLI documentation for the `--context` flag +- Tests for instructions command and apply instructions diff --git a/openspec/changes/project-context-all-skills/specs/cli-instructions-context/spec.md b/openspec/changes/project-context-all-skills/specs/cli-instructions-context/spec.md new file mode 100644 index 000000000..9df016dea --- /dev/null +++ b/openspec/changes/project-context-all-skills/specs/cli-instructions-context/spec.md @@ -0,0 +1,42 @@ +## ADDED Requirements + +### Requirement: Standalone context flag +The `openspec instructions` command SHALL support a `--context` flag that returns the project context from `config.yaml` without requiring a change name or artifact ID. + +#### Scenario: Return project context as text +- **WHEN** `openspec instructions --context` is called +- **THEN** the command reads `openspec/config.yaml` via `readProjectConfig()` and outputs the `context` field as plain text + +#### Scenario: Return project context as JSON +- **WHEN** `openspec instructions --context --json` is called +- **THEN** the command outputs `{ "context": "" }` + +#### Scenario: No config file exists +- **WHEN** `openspec instructions --context` is called and no `openspec/config.yaml` exists +- **THEN** the command outputs nothing (text mode) or `{ "context": null }` (JSON mode) and exits with code 0 + +#### Scenario: Config exists but no context field +- **WHEN** `openspec instructions --context` is called and `config.yaml` exists but has no `context` field +- **THEN** the command outputs nothing (text mode) or `{ "context": null }` (JSON mode) and exits with code 0 + +### Requirement: Context flag exclusivity +The `--context` flag SHALL be incompatible with change-specific and artifact-specific options. + +#### Scenario: Combined with --change +- **WHEN** `openspec instructions --context --change "some-change"` is called +- **THEN** the command outputs an error message and exits with non-zero code + +#### Scenario: Combined with --schema +- **WHEN** `openspec instructions --context --schema "some-schema"` is called +- **THEN** the command outputs an error message and exits with non-zero code + +#### Scenario: Combined with artifact argument +- **WHEN** `openspec instructions proposal --context` is called +- **THEN** the command outputs an error message and exits with non-zero code + +### Requirement: CLI documentation +The `--context` flag SHALL be documented in the CLI reference documentation. + +#### Scenario: Documentation includes context flag +- **WHEN** a user reads `docs/cli.md` +- **THEN** the `openspec instructions` section documents the `--context` flag with usage examples and behavior diff --git a/openspec/changes/project-context-all-skills/specs/instruction-loader/spec.md b/openspec/changes/project-context-all-skills/specs/instruction-loader/spec.md new file mode 100644 index 000000000..20413b82b --- /dev/null +++ b/openspec/changes/project-context-all-skills/specs/instruction-loader/spec.md @@ -0,0 +1,32 @@ +## MODIFIED Requirements + +### Requirement: Template Enrichment +The system SHALL enrich templates with change-specific context. + +#### Scenario: Include artifact metadata +- **WHEN** instructions are generated for an artifact +- **THEN** the output includes change name, artifact ID, schema name, and output path + +#### Scenario: Include dependency status +- **WHEN** an artifact has dependencies +- **THEN** the output shows each dependency with completion status (done/missing) + +#### Scenario: Include unlocked artifacts +- **WHEN** instructions are generated +- **THEN** the output includes which artifacts become available after this one + +#### Scenario: Root artifact indicator +- **WHEN** an artifact has no dependencies +- **THEN** the dependency section indicates this is a root artifact + +#### Scenario: Include project context in apply instructions +- **WHEN** `generateApplyInstructions()` is called for a change +- **THEN** the output includes a `context` field read from `config.yaml` via `readProjectConfig()` + +#### Scenario: Apply instructions with no config +- **WHEN** `generateApplyInstructions()` is called and no `config.yaml` exists +- **THEN** the `context` field is `undefined` and the rest of the apply instructions are unaffected + +#### Scenario: Apply instructions text output includes project context +- **WHEN** `printApplyInstructionsText()` is called with a non-empty `context` field +- **THEN** the text output includes a `` block with the context content, matching the format used by artifact instructions diff --git a/openspec/changes/project-context-all-skills/specs/skill-context-enforcement/spec.md b/openspec/changes/project-context-all-skills/specs/skill-context-enforcement/spec.md new file mode 100644 index 000000000..a71f4f058 --- /dev/null +++ b/openspec/changes/project-context-all-skills/specs/skill-context-enforcement/spec.md @@ -0,0 +1,57 @@ +## ADDED Requirements + +### Requirement: Mandatory enforcement language for artifact-creating skills +Skills that create artifacts (Continue, FF, New, Onboard) SHALL use mandatory language ("you MUST follow") when describing how to handle `context`, `rules`, and `instruction` fields from the instructions output. + +#### Scenario: Continue skill enforcement +- **WHEN** the Continue skill prompt describes the `context`, `rules`, and `instruction` fields +- **THEN** it uses "you MUST follow" language and explicitly states these are mandatory constraints, not suggestions + +#### Scenario: FF skill enforcement +- **WHEN** the FF skill prompt describes the `context`, `rules`, and `instruction` fields +- **THEN** it uses "you MUST follow" language and explicitly states these are mandatory constraints, not suggestions + +#### Scenario: New skill enforcement +- **WHEN** the New skill prompt describes the `context`, `rules`, and `instruction` fields +- **THEN** it uses "you MUST follow" language and explicitly states these are mandatory constraints, not suggestions + +#### Scenario: Onboard skill enforcement +- **WHEN** the Onboard skill prompt describes the `context`, `rules`, and `instruction` fields +- **THEN** it uses "you MUST follow" language and explicitly states these are mandatory constraints, not suggestions + +### Requirement: Context consumption for code-operating skills +Skills that operate on code (Apply, Verify) SHALL consume the `context` field from `instructions apply --json` and use mandatory enforcement language. + +#### Scenario: Apply skill consumes context +- **WHEN** the Apply skill reads the output of `openspec instructions apply --json` +- **THEN** it treats the `context` field as mandatory project constraints that MUST be followed when implementing code + +#### Scenario: Verify skill consumes context +- **WHEN** the Verify skill reads the output of `openspec instructions apply --json` +- **THEN** it treats the `context` field as mandatory project constraints that MUST be followed when verifying code + +### Requirement: Context loading for change-independent skills +Skills that operate without a change context (Explore, Archive, Bulk-archive, Sync) SHALL load project context at session start via `openspec instructions --context --json`. + +#### Scenario: Explore skill loads context +- **WHEN** the Explore skill starts a session +- **THEN** it calls `openspec instructions --context --json` and treats the `context` field as mandatory project constraints + +#### Scenario: Archive skill loads context +- **WHEN** the Archive skill starts a session +- **THEN** it calls `openspec instructions --context --json` and treats the `context` field as mandatory project constraints + +#### Scenario: Bulk-archive skill loads context +- **WHEN** the Bulk-archive skill starts a session +- **THEN** it calls `openspec instructions --context --json` and treats the `context` field as mandatory project constraints + +#### Scenario: Sync skill loads context +- **WHEN** the Sync skill starts a session +- **THEN** it calls `openspec instructions --context --json` and treats the `context` field as mandatory project constraints + +### Requirement: Generated slash commands match skill templates +The generated `.claude/commands/opsx/*.md` files SHALL reflect the same enforcement language as their corresponding skill templates in `skill-templates.ts`. + +#### Scenario: Slash commands are regenerated +- **WHEN** skills are generated via `openspec skills generate` +- **THEN** all `.claude/commands/opsx/*.md` files contain the updated mandatory enforcement language matching their skill template diff --git a/openspec/changes/project-context-all-skills/tasks.md b/openspec/changes/project-context-all-skills/tasks.md new file mode 100644 index 000000000..17bf61871 --- /dev/null +++ b/openspec/changes/project-context-all-skills/tasks.md @@ -0,0 +1,63 @@ +## 1. CLI: Add `context` to apply instructions + +- [x] 1.1 Add `context?: string` field to `ApplyInstructions` interface in `src/commands/workflow/shared.ts` +- [x] 1.2 Update `generateApplyInstructions()` in `src/commands/workflow/instructions.ts` to call `readProjectConfig()` and populate `context` +- [x] 1.3 Update `printApplyInstructionsText()` to print `` block when context is present (matching artifact instructions format) +- [x] 1.4 Add tests for apply instructions with context (config present, config absent, context field missing) + +## 2. CLI: Add `--context` flag to instructions command + +- [x] 2.1 Add `--context` option to the instructions command in `src/cli/index.ts` +- [x] 2.2 Implement context-only handler in `src/commands/workflow/instructions.ts`: read `readProjectConfig()`, output context as text or JSON +- [x] 2.3 Add validation: error if `--context` is combined with `--change`, `--schema`, or an artifact argument +- [x] 2.4 Handle graceful cases: no config file, config without context field +- [x] 2.5 Add tests for `--context` flag (text output, JSON output, exclusivity errors, graceful fallbacks) + +## 3. CLI: Update shell completions + +- [x] 3.1 Add `--context` to the instructions command completions in `src/core/completions/command-registry.ts` (N/A - instructions command not in completions registry) + +## 4. Skill prompts: Strengthen enforcement in artifact-creating skills + +- [x] 4.1 Update Continue skill template (`getContinueChangeSkillTemplate`) — replace weak language with "you MUST follow" for `context`, `rules`, `instruction` +- [x] 4.2 Update FF skill template (`getFfChangeSkillTemplate`) — same enforcement pattern +- [x] 4.3 Update New skill template (`getNewChangeSkillTemplate`) — N/A, does not create artifacts (only shows template) +- [x] 4.4 Update Onboard skill template (`getOnboardSkillTemplate`) — N/A, uses own inline flow without standard instructions pathway + +## 5. Skill prompts: Add context consumption to code-operating skills + +- [x] 5.1 Update Apply skill template (`getApplyChangeSkillTemplate`) — add `context` field consumption from `instructions apply --json` with mandatory enforcement +- [x] 5.2 Update Verify skill template (`getVerifyChangeSkillTemplate`) — add `context` field consumption from `instructions apply --json` with mandatory enforcement + +## 6. Skill prompts: Add context loading to change-independent skills + +- [x] 6.1 Update Explore skill template (`getExploreSkillTemplate`) — add `openspec instructions --context --json` call at session start with mandatory enforcement +- [x] 6.2 Update Archive skill template (`getArchiveChangeSkillTemplate`) — add `openspec instructions --context --json` call at session start with mandatory enforcement +- [x] 6.3 Update Bulk-archive skill template (`getBulkArchiveChangeSkillTemplate`) — add `openspec instructions --context --json` call at session start with mandatory enforcement +- [x] 6.4 Update Sync skill template (`getSyncSpecsSkillTemplate`) — add `openspec instructions --context --json` call at session start with mandatory enforcement + +## 7. Slash commands (opsx): Update generated command files + +- [x] 7.1 Update opsx Continue command template (`getOpsxContinueCommandTemplate`) — same enforcement as skill template +- [x] 7.2 Update opsx FF command template (`getOpsxFfCommandTemplate`) — same enforcement as skill template +- [x] 7.3 Update opsx New command template (`getOpsxNewCommandTemplate`) — N/A, does not create artifacts +- [x] 7.4 Update opsx Onboard command template (`getOpsxOnboardCommandTemplate`) — N/A, delegates to shared function +- [x] 7.5 Update opsx Apply command template (`getOpsxApplyCommandTemplate`) — add context consumption + enforcement +- [x] 7.6 Update opsx Verify command template (`getOpsxVerifyCommandTemplate`) — add context consumption + enforcement +- [x] 7.7 Update opsx Explore command template (`getOpsxExploreCommandTemplate`) — add context loading + enforcement +- [x] 7.8 Update opsx Archive command template (`getOpsxArchiveCommandTemplate`) — add context loading + enforcement +- [x] 7.9 Update opsx Bulk-archive command template (`getOpsxBulkArchiveCommandTemplate`) — add context loading + enforcement +- [x] 7.10 Update opsx Sync command template (`getOpsxSyncCommandTemplate`) — add context loading + enforcement + +## 8. Regenerate skill files + +- [x] 8.1 Run `openspec skills generate` to regenerate all `.claude/commands/opsx/*.md` files with updated templates + +## 9. Documentation + +- [x] 9.1 Update `docs/cli.md` — add `--context` flag to the `openspec instructions` section with usage examples and behavior + +## 10. Verification + +- [x] 10.1 Run full test suite and verify no regressions +- [x] 10.2 Verify on Windows CI that path handling in new code uses `path.join()` diff --git a/src/cli/index.ts b/src/cli/index.ts index 006f21c36..0d1a58893 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -20,6 +20,7 @@ import { statusCommand, instructionsCommand, applyInstructionsCommand, + contextInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, @@ -437,14 +438,27 @@ program // Instructions command program .command('instructions [artifact]') - .description('Output enriched instructions for creating an artifact or applying tasks') + .description('Output enriched instructions for creating an artifact, applying tasks, or getting project context') .option('--change ', 'Change name') .option('--schema ', 'Schema override (auto-detected from config.yaml)') + .option('--context', 'Output project context from config.yaml (incompatible with --change, --schema, artifact)') .option('--json', 'Output as JSON') - .action(async (artifactId: string | undefined, options: InstructionsOptions) => { + .action(async (artifactId: string | undefined, options: InstructionsOptions & { context?: boolean }) => { try { - // Special case: "apply" is not an artifact, but a command to get apply instructions - if (artifactId === 'apply') { + if (options.context) { + // Validate exclusivity: --context is incompatible with other options + if (artifactId) { + throw new Error('--context cannot be combined with an artifact argument'); + } + if (options.change) { + throw new Error('--context cannot be combined with --change'); + } + if (options.schema) { + throw new Error('--context cannot be combined with --schema'); + } + await contextInstructionsCommand({ json: options.json }); + } else if (artifactId === 'apply') { + // Special case: "apply" is not an artifact, but a command to get apply instructions await applyInstructionsCommand(options); } else { await instructionsCommand(artifactId, options); diff --git a/src/commands/workflow/index.ts b/src/commands/workflow/index.ts index 232b2dbe3..8d6a26852 100644 --- a/src/commands/workflow/index.ts +++ b/src/commands/workflow/index.ts @@ -7,7 +7,7 @@ export { statusCommand } from './status.js'; export type { StatusOptions } from './status.js'; -export { instructionsCommand, applyInstructionsCommand } from './instructions.js'; +export { instructionsCommand, applyInstructionsCommand, contextInstructionsCommand } from './instructions.js'; export type { InstructionsOptions } from './instructions.js'; export { templatesCommand } from './templates.js'; diff --git a/src/commands/workflow/instructions.ts b/src/commands/workflow/instructions.ts index 0d501afec..491ebe764 100644 --- a/src/commands/workflow/instructions.ts +++ b/src/commands/workflow/instructions.ts @@ -14,6 +14,7 @@ import { resolveSchema, type ArtifactInstructions, } from '../../core/artifact-graph/index.js'; +import { readProjectConfig } from '../../core/project-config.js'; import { validateChangeExists, validateSchemaExists, @@ -37,6 +38,10 @@ export interface ApplyInstructionsOptions { json?: boolean; } +export interface ContextInstructionsOptions { + json?: boolean; +} + // ----------------------------------------------------------------------------- // Artifact Instructions Command // ----------------------------------------------------------------------------- @@ -386,6 +391,15 @@ export async function generateApplyInstructions( instruction = schemaInstruction?.trim() ?? 'Read context files, work through pending tasks, mark complete as you go.\nPause if you hit blockers or need clarification.'; } + // Read project config for context + let projectContext: string | undefined; + try { + const projectConfig = readProjectConfig(projectRoot); + projectContext = projectConfig?.context?.trim() || undefined; + } catch { + // If config read fails, continue without context + } + return { changeName, changeDir, @@ -396,6 +410,7 @@ export async function generateApplyInstructions( state, missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined, instruction, + context: projectContext, }; } @@ -429,12 +444,21 @@ export async function applyInstructionsCommand(options: ApplyInstructionsOptions } export function printApplyInstructionsText(instructions: ApplyInstructions): void { - const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions; + const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction, context } = instructions; console.log(`## Apply: ${changeName}`); console.log(`Schema: ${schemaName}`); console.log(); + // Project context (AI constraint - do not include in output) + if (context) { + console.log(''); + console.log(''); + console.log(context); + console.log(''); + console.log(); + } + // Warning for blocked state if (state === 'blocked' && missingArtifacts) { console.log('### ⚠️ Blocked'); @@ -479,3 +503,31 @@ export function printApplyInstructionsText(instructions: ApplyInstructions): voi console.log('### Instruction'); console.log(instruction); } + +// ----------------------------------------------------------------------------- +// Context Instructions Command +// ----------------------------------------------------------------------------- + +/** + * Returns project context from config.yaml without requiring a change or artifact. + * Used by skills that operate outside of a specific change context. + */ +export async function contextInstructionsCommand(options: ContextInstructionsOptions): Promise { + const projectRoot = process.cwd(); + let contextValue: string | null = null; + + try { + const projectConfig = readProjectConfig(projectRoot); + contextValue = projectConfig?.context?.trim() || null; + } catch { + // If config read fails, return null context gracefully + } + + if (options.json) { + console.log(JSON.stringify({ context: contextValue }, null, 2)); + } else { + if (contextValue) { + console.log(contextValue); + } + } +} diff --git a/src/commands/workflow/shared.ts b/src/commands/workflow/shared.ts index a2c8bdcc7..341523687 100644 --- a/src/commands/workflow/shared.ts +++ b/src/commands/workflow/shared.ts @@ -35,6 +35,7 @@ export interface ApplyInstructions { state: 'blocked' | 'all_done' | 'ready'; missingArtifacts?: string[]; instruction: string; + context?: string; } // ----------------------------------------------------------------------------- diff --git a/src/core/templates/skill-templates.ts b/src/core/templates/skill-templates.ts index 481611930..a6bff52ab 100644 --- a/src/core/templates/skill-templates.ts +++ b/src/core/templates/skill-templates.ts @@ -101,12 +101,17 @@ At the start, quickly check what exists: \`\`\`bash openspec list --json \`\`\` +\`\`\`bash +openspec instructions --context --json +\`\`\` -This tells you: +The first tells you: - If there are active changes - Their names, schemas, and status - What the user might be working on +The second returns the project's \`context\` from \`config.yaml\`. **If it returns a \`context\` field, you MUST follow these project constraints throughout the session** (tech stack, conventions, cross-platform rules, etc.). + ### When no change exists Think freely. When insights crystallize, you might offer: @@ -443,16 +448,16 @@ export function getContinueChangeSkillTemplate(): SkillTemplate { openspec instructions --change "" --json \`\`\` - Parse the JSON. The key fields are: - - \`context\`: Project background (constraints for you - do NOT include in output) - - \`rules\`: Artifact-specific rules (constraints for you - do NOT include in output) + - \`context\`: Project constraints — you MUST follow these when creating artifacts. Do NOT include in output. + - \`rules\`: Artifact-specific rules — you MUST follow these. Do NOT include in output. + - \`instruction\`: Directives for how to create this artifact — you MUST follow these. - \`template\`: The structure to use for your output file - - \`instruction\`: Schema-specific guidance - \`outputPath\`: Where to write the artifact - \`dependencies\`: Completed artifacts to read for context - **Create the artifact file**: - Read any completed dependency files for context - Use \`template\` as the structure - fill in its sections - - Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file + - Follow \`context\`, \`rules\`, and \`instruction\` as mandatory constraints — but do NOT copy them into the file - Write to the output path specified in instructions - Show what was created and what's now unlocked - STOP after creating ONE artifact @@ -499,9 +504,11 @@ For other schemas, follow the \`instruction\` field from the CLI output. - If context is unclear, ask the user before creating - Verify the artifact file exists after writing before marking progress - Use the schema's artifact sequence, don't assume specific artifact names -- **IMPORTANT**: \`context\` and \`rules\` are constraints for YOU, not content for the file - - Do NOT copy \`\`, \`\`, \`\` blocks into the artifact - - These guide what you write, but should never appear in the output`, +- **MANDATORY**: \`context\`, \`rules\`, and \`instruction\` from the instructions output are constraints you MUST follow + - \`context\`: Project-level constraints (tech stack, conventions) — follow them, do NOT copy into the artifact + - \`rules\`: Artifact-specific rules — follow them, do NOT copy into the artifact + - \`instruction\`: Directives for how to create this artifact — follow them + - Do NOT copy \`\`, \`\`, \`\` blocks into the artifact`, license: 'MIT', compatibility: 'Requires openspec CLI.', metadata: { author: 'openspec', version: '1.0' }, @@ -546,6 +553,7 @@ export function getApplyChangeSkillTemplate(): SkillTemplate { \`\`\` This returns: + - \`context\`: Project constraints — you MUST follow these when implementing code. Do NOT include in output. - Context file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs) - Progress (total, complete, remaining) - Task list with status @@ -646,6 +654,7 @@ What would you like to do? \`\`\` **Guardrails** +- **MANDATORY**: If the apply instructions include a \`context\` field, you MUST follow these project constraints when implementing code - Keep going through tasks until done or blocked - Always read context files before starting (from the apply instructions output) - If task is ambiguous, pause and ask before implementing @@ -716,15 +725,15 @@ export function getFfChangeSkillTemplate(): SkillTemplate { openspec instructions --change "" --json \`\`\` - The instructions JSON includes: - - \`context\`: Project background (constraints for you - do NOT include in output) - - \`rules\`: Artifact-specific rules (constraints for you - do NOT include in output) + - \`context\`: Project constraints — you MUST follow these when creating artifacts. Do NOT include in output. + - \`rules\`: Artifact-specific rules — you MUST follow these. Do NOT include in output. + - \`instruction\`: Directives for how to create this artifact — you MUST follow these. - \`template\`: The structure to use for your output file - - \`instruction\`: Schema-specific guidance for this artifact type - \`outputPath\`: Where 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 - - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file + - Follow \`context\`, \`rules\`, and \`instruction\` as mandatory constraints — but do NOT copy them into the file - Show brief progress: "✓ Created " b. **Continue until all \`applyRequires\` artifacts are complete** @@ -755,9 +764,11 @@ After completing all artifacts, summarize: - The schema defines what each artifact should contain - follow it - Read dependency artifacts for context before creating new ones - Use \`template\` as the structure for your output file - fill in its sections -- **IMPORTANT**: \`context\` and \`rules\` are constraints for YOU, not content for the file +- **MANDATORY**: \`context\`, \`rules\`, and \`instruction\` from the instructions output are constraints you MUST follow + - \`context\`: Project-level constraints (tech stack, conventions) — follow them, do NOT copy into the artifact + - \`rules\`: Artifact-specific rules — follow them, do NOT copy into the artifact + - \`instruction\`: Directives for how to create this artifact — follow them - Do NOT copy \`\`, \`\`, \`\` blocks into the artifact - - These guide what you write, but should never appear in the output **Guardrails** - Create ALL artifacts needed for implementation (as defined by schema's \`apply.requires\`) @@ -785,6 +796,12 @@ This is an **agent-driven** operation - you will read delta specs and directly e **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. +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **If no change name provided, prompt for selection** @@ -1565,12 +1582,17 @@ At the start, quickly check what exists: \`\`\`bash openspec list --json \`\`\` +\`\`\`bash +openspec instructions --context --json +\`\`\` -This tells you: +The first tells you: - If there are active changes - Their names, schemas, and status - What the user might be working on +The second returns the project's \`context\` from \`config.yaml\`. **If it returns a \`context\` field, you MUST follow these project constraints throughout the session** (tech stack, conventions, cross-platform rules, etc.). + If the user mentioned a specific change name, read its artifacts for context. ### When no change exists @@ -1783,16 +1805,16 @@ export function getOpsxContinueCommandTemplate(): CommandTemplate { openspec instructions --change "" --json \`\`\` - Parse the JSON. The key fields are: - - \`context\`: Project background (constraints for you - do NOT include in output) - - \`rules\`: Artifact-specific rules (constraints for you - do NOT include in output) + - \`context\`: Project constraints — you MUST follow these when creating artifacts. Do NOT include in output. + - \`rules\`: Artifact-specific rules — you MUST follow these. Do NOT include in output. + - \`instruction\`: Directives for how to create this artifact — you MUST follow these. - \`template\`: The structure to use for your output file - - \`instruction\`: Schema-specific guidance - \`outputPath\`: Where to write the artifact - \`dependencies\`: Completed artifacts to read for context - **Create the artifact file**: - Read any completed dependency files for context - Use \`template\` as the structure - fill in its sections - - Apply \`context\` and \`rules\` as constraints when writing - but do NOT copy them into the file + - Follow \`context\`, \`rules\`, and \`instruction\` as mandatory constraints — but do NOT copy them into the file - Write to the output path specified in instructions - Show what was created and what's now unlocked - STOP after creating ONE artifact @@ -1839,9 +1861,11 @@ For other schemas, follow the \`instruction\` field from the CLI output. - If context is unclear, ask the user before creating - Verify the artifact file exists after writing before marking progress - Use the schema's artifact sequence, don't assume specific artifact names -- **IMPORTANT**: \`context\` and \`rules\` are constraints for YOU, not content for the file - - Do NOT copy \`\`, \`\`, \`\` blocks into the artifact - - These guide what you write, but should never appear in the output` +- **MANDATORY**: \`context\`, \`rules\`, and \`instruction\` from the instructions output are constraints you MUST follow + - \`context\`: Project-level constraints (tech stack, conventions) — follow them, do NOT copy into the artifact + - \`rules\`: Artifact-specific rules — follow them, do NOT copy into the artifact + - \`instruction\`: Directives for how to create this artifact — follow them + - Do NOT copy \`\`, \`\`, \`\` blocks into the artifact` }; } @@ -1884,6 +1908,7 @@ export function getOpsxApplyCommandTemplate(): CommandTemplate { \`\`\` This returns: + - \`context\`: Project constraints — you MUST follow these when implementing code. Do NOT include in output. - Context file paths (varies by schema) - Progress (total, complete, remaining) - Task list with status @@ -1984,6 +2009,7 @@ What would you like to do? \`\`\` **Guardrails** +- **MANDATORY**: If the apply instructions include a \`context\` field, you MUST follow these project constraints when implementing code - Keep going through tasks until done or blocked - Always read context files before starting (from the apply instructions output) - If task is ambiguous, pause and ask before implementing @@ -2053,15 +2079,15 @@ export function getOpsxFfCommandTemplate(): CommandTemplate { openspec instructions --change "" --json \`\`\` - The instructions JSON includes: - - \`context\`: Project background (constraints for you - do NOT include in output) - - \`rules\`: Artifact-specific rules (constraints for you - do NOT include in output) + - \`context\`: Project constraints — you MUST follow these when creating artifacts. Do NOT include in output. + - \`rules\`: Artifact-specific rules — you MUST follow these. Do NOT include in output. + - \`instruction\`: Directives for how to create this artifact — you MUST follow these. - \`template\`: The structure to use for your output file - - \`instruction\`: Schema-specific guidance for this artifact type - \`outputPath\`: Where 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 - - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file + - Follow \`context\`, \`rules\`, and \`instruction\` as mandatory constraints — but do NOT copy them into the file - Show brief progress: "✓ Created " b. **Continue until all \`applyRequires\` artifacts are complete** @@ -2114,6 +2140,12 @@ export function getArchiveChangeSkillTemplate(): SkillTemplate { **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. +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **If no change name provided, prompt for selection** @@ -2233,6 +2265,12 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig **Input**: None required (prompts for selection) +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **Get active changes** @@ -2483,6 +2521,12 @@ This is an **agent-driven** operation - you will read delta specs and directly e **Input**: Optionally specify a change name after \`/opsx:sync\` (e.g., \`/opsx:sync add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **If no change name provided, prompt for selection** @@ -2645,7 +2689,9 @@ export function getVerifyChangeSkillTemplate(): SkillTemplate { openspec instructions apply --change "" --json \`\`\` - This returns the change directory and context files. Read all available artifacts from \`contextFiles\`. + This returns the change directory, context files, and project context. Read all available artifacts from \`contextFiles\`. + + **MANDATORY**: If the response includes a \`context\` field, you MUST follow these project constraints throughout verification. Do NOT include in output. 4. **Initialize verification report structure** @@ -2791,6 +2837,12 @@ export function getOpsxArchiveCommandTemplate(): CommandTemplate { **Input**: Optionally specify a change name after \`/opsx:archive\` (e.g., \`/opsx:archive add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **If no change name provided, prompt for selection** @@ -2969,6 +3021,12 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig **Input**: None required (prompts for selection) +**Project Context**: At the start, load project context: +\`\`\`bash +openspec instructions --context --json +\`\`\` +If it returns a \`context\` field, you MUST follow these project constraints throughout the session. + **Steps** 1. **Get active changes** @@ -3240,7 +3298,9 @@ export function getOpsxVerifyCommandTemplate(): CommandTemplate { openspec instructions apply --change "" --json \`\`\` - This returns the change directory and context files. Read all available artifacts from \`contextFiles\`. + This returns the change directory, context files, and project context. Read all available artifacts from \`contextFiles\`. + + **MANDATORY**: If the response includes a \`context\` field, you MUST follow these project constraints throughout verification. Do NOT include in output. 4. **Initialize verification report structure** diff --git a/test/commands/artifact-workflow.test.ts b/test/commands/artifact-workflow.test.ts index 181629940..8a7053259 100644 --- a/test/commands/artifact-workflow.test.ts +++ b/test/commands/artifact-workflow.test.ts @@ -549,6 +549,167 @@ artifacts: expect(json.state).toBe('ready'); expect(json.instruction).toContain('All required artifacts complete'); }); + + it('includes project context when config.yaml has context field', async () => { + await createTestChange('context-apply', ['proposal', 'design', 'specs', 'tasks']); + + // Create config.yaml with context + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\ncontext: |\n Tech stack: TypeScript\n Cross-platform: yes\n' + ); + + const result = await runCLI( + ['instructions', 'apply', '--change', 'context-apply', '--json'], + { cwd: tempDir } + ); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toContain('Tech stack: TypeScript'); + expect(json.context).toContain('Cross-platform: yes'); + }); + + it('includes project context in text output', async () => { + await createTestChange('context-text-apply', ['proposal', 'design', 'specs', 'tasks']); + + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\ncontext: |\n Tech stack: TypeScript\n' + ); + + const result = await runCLI( + ['instructions', 'apply', '--change', 'context-text-apply'], + { cwd: tempDir } + ); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain(''); + expect(result.stdout).toContain('Tech stack: TypeScript'); + expect(result.stdout).toContain(''); + }); + + it('omits context when config.yaml has no context field', async () => { + await createTestChange('no-context-apply', ['proposal', 'design', 'specs', 'tasks']); + + // Create config.yaml without context + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\n' + ); + + const result = await runCLI( + ['instructions', 'apply', '--change', 'no-context-apply', '--json'], + { cwd: tempDir } + ); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toBeUndefined(); + }); + + it('omits context when no config.yaml exists', async () => { + await createTestChange('no-config-apply', ['proposal', 'design', 'specs', 'tasks']); + + const result = await runCLI( + ['instructions', 'apply', '--change', 'no-config-apply', '--json'], + { cwd: tempDir } + ); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toBeUndefined(); + }); + }); + + describe('instructions --context command', () => { + it('returns project context as JSON', async () => { + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\ncontext: |\n Tech stack: TypeScript\n Cross-platform: yes\n' + ); + + const result = await runCLI(['instructions', '--context', '--json'], { + cwd: tempDir, + }); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toContain('Tech stack: TypeScript'); + expect(json.context).toContain('Cross-platform: yes'); + }); + + it('returns project context as text', async () => { + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\ncontext: |\n Tech stack: TypeScript\n' + ); + + const result = await runCLI(['instructions', '--context'], { + cwd: tempDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('Tech stack: TypeScript'); + }); + + it('returns null context when no config exists', async () => { + const result = await runCLI(['instructions', '--context', '--json'], { + cwd: tempDir, + }); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toBeNull(); + }); + + it('returns null context when config has no context field', async () => { + await fs.writeFile( + path.join(tempDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\n' + ); + + const result = await runCLI(['instructions', '--context', '--json'], { + cwd: tempDir, + }); + expect(result.exitCode).toBe(0); + + const json = JSON.parse(result.stdout); + expect(json.context).toBeNull(); + }); + + it('returns empty output in text mode when no context', async () => { + const result = await runCLI(['instructions', '--context'], { + cwd: tempDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.trim()).toBe(''); + }); + + it('errors when combined with --change', async () => { + const result = await runCLI( + ['instructions', '--context', '--change', 'some-change'], + { cwd: tempDir } + ); + expect(result.exitCode).not.toBe(0); + expect(getOutput(result)).toContain('--context cannot be combined with --change'); + }); + + it('errors when combined with --schema', async () => { + const result = await runCLI( + ['instructions', '--context', '--schema', 'some-schema'], + { cwd: tempDir } + ); + expect(result.exitCode).not.toBe(0); + expect(getOutput(result)).toContain('--context cannot be combined with --schema'); + }); + + it('errors when combined with artifact argument', async () => { + const result = await runCLI( + ['instructions', 'proposal', '--context'], + { cwd: tempDir } + ); + expect(result.exitCode).not.toBe(0); + expect(getOutput(result)).toContain('--context cannot be combined with an artifact argument'); + }); }); describe('help text', () => {