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', () => {