diff --git a/.changeset/ground-proposals-in-project-code.md b/.changeset/ground-proposals-in-project-code.md new file mode 100644 index 0000000000..0df5bf26c0 --- /dev/null +++ b/.changeset/ground-proposals-in-project-code.md @@ -0,0 +1,5 @@ +--- +"@fission-ai/openspec": patch +--- + +Guide propose and fast-forward workflows to inspect relevant project code, tests, and documentation before drafting artifacts, so plans reflect the existing implementation instead of deferring basic discovery to implementation tasks. diff --git a/skills/openspec-ff-change/SKILL.md b/skills/openspec-ff-change/SKILL.md index c63aeee13b..72a95620bb 100644 --- a/skills/openspec-ff-change/SKILL.md +++ b/skills/openspec-ff-change/SKILL.md @@ -61,6 +61,10 @@ Fast-forward through artifact creation - generate everything needed to start imp - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read `context` and `rules` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside `openspec/`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the `instruction` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at `resolvedOutputPath` - Otherwise create the artifact file using `template` as the structure and write it to `resolvedOutputPath`. If `resolvedOutputPath` is a glob, follow `instruction` to choose the concrete file path - Apply `context` and `rules` as constraints - but do NOT copy them into the file diff --git a/skills/openspec-propose/SKILL.md b/skills/openspec-propose/SKILL.md index ccf7b22334..2d5709dfe1 100644 --- a/skills/openspec-propose/SKILL.md +++ b/skills/openspec-propose/SKILL.md @@ -96,6 +96,10 @@ When the user is ready to implement, they must start the apply workflow explicit - `resolvedOutputPath`: Resolved path or pattern to write the artifact - `dependencies`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read `context` and `rules` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside `openspec/`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the `instruction` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at `resolvedOutputPath` - Otherwise create the artifact file using `template` as the structure and write it to `resolvedOutputPath`. If `resolvedOutputPath` is a glob, follow `instruction` to choose the concrete file path - Apply `context` and `rules` as constraints - but do NOT copy them into the file diff --git a/src/core/templates/workflows/ff-change.ts b/src/core/templates/workflows/ff-change.ts index 1a3f036ba2..843fb79c87 100644 --- a/src/core/templates/workflows/ff-change.ts +++ b/src/core/templates/workflows/ff-change.ts @@ -63,6 +63,10 @@ ${STORE_SELECTION_GUIDANCE} - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact - \`dependencies\`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\` - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file @@ -176,6 +180,10 @@ ${STORE_SELECTION_GUIDANCE} - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact - \`dependencies\`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\` - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file diff --git a/src/core/templates/workflows/propose.ts b/src/core/templates/workflows/propose.ts index 8e700c703d..54ceda16c7 100644 --- a/src/core/templates/workflows/propose.ts +++ b/src/core/templates/workflows/propose.ts @@ -98,6 +98,10 @@ ${STORE_SELECTION_GUIDANCE} - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact - \`dependencies\`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\` - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file @@ -247,6 +251,10 @@ ${STORE_SELECTION_GUIDANCE} - \`resolvedOutputPath\`: Resolved path or pattern to write the artifact - \`dependencies\`: Completed artifacts to read for context - Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them) + - **Inspect the relevant project before drafting**: Read \`context\` and \`rules\` first, then inspect relevant implementation, nearby tests, configuration, and documentation outside \`openspec/\`. Keep inspection read-only and proportional to the change; reuse findings for later artifacts and inspect more only as needed. + - Identify the target project from the request and project context; the planning home may be separate from the code. If the target is unclear, ask. For greenfield or non-code changes, inspect the available structure and relevant documents. If source is unavailable, state the limitation and ask when it materially affects the plan. + - Ground scope, approach, and tasks in what you find. Distinguish observed behavior from assumptions and proposed additions; surface conflicts with existing specs instead of silently deciding which is correct. + - Do this discovery now, rather than leaving generic "explore the codebase" or "make a plan" tasks for implementation. Keep any necessary follow-up investigation specific to an unresolved question. - If the \`instruction\` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at \`resolvedOutputPath\` - Otherwise create the artifact file using \`template\` as the structure and write it to \`resolvedOutputPath\`. If \`resolvedOutputPath\` is a glob, follow \`instruction\` to choose the concrete file path - Apply \`context\` and \`rules\` as constraints - but do NOT copy them into the file diff --git a/test/core/templates/propose.test.ts b/test/core/templates/propose.test.ts index 0ac2e6201f..8f8e77a49a 100644 --- a/test/core/templates/propose.test.ts +++ b/test/core/templates/propose.test.ts @@ -89,6 +89,53 @@ describe('default task guidance', () => { }); }); +describe('planning code inspection (#339)', () => { + it('inspects the project after loading instructions and dependencies, before creating or delegating artifacts', () => { + for (const [label, body] of loopBodies) { + const instructions = body.indexOf('openspec instructions '); + const dependencies = body.indexOf('Read any completed dependency files'); + const inspection = body.indexOf('**Inspect the relevant project before drafting**'); + const delegation = body.indexOf('If the `instruction` field delegates creation'); + expect(instructions, label).toBeGreaterThanOrEqual(0); + expect(dependencies, label).toBeGreaterThan(instructions); + expect(inspection, label).toBeGreaterThan(dependencies); + expect(delegation, label).toBeGreaterThan(inspection); + + const guidance = body.slice(inspection, delegation); + expect(guidance, label).toContain('Read `context` and `rules` first'); + expect(guidance, label).toContain('relevant implementation, nearby tests, configuration, and documentation outside `openspec/`'); + expect(guidance, label).toContain('Keep inspection read-only and proportional to the change'); + expect(guidance, label).toContain('reuse findings for later artifacts'); + expect(guidance, label).toContain('Do this discovery now'); + } + }); + + it('handles separate stores, missing code, and uncertain findings without inventing facts', () => { + for (const [label, body] of loopBodies) { + expect(body, label).toContain('the planning home may be separate from the code'); + expect(body, label).toContain('If the target is unclear, ask'); + expect(body, label).toContain('For greenfield or non-code changes, inspect the available structure and relevant documents'); + expect(body, label).toContain('If source is unavailable, state the limitation'); + expect(body, label).toContain('Distinguish observed behavior from assumptions and proposed additions'); + expect(body, label).toContain('surface conflicts with existing specs instead of silently deciding which is correct'); + } + }); + + it('preserves inspection guidance through every command adapter', () => { + for (const command of getCommandContents(['propose', 'ff'])) { + for (const adapter of CommandAdapterRegistry.getAll()) { + const generated = generateCommand(command, adapter).fileContent; + const inspection = generated.indexOf('**Inspect the relevant project before drafting**'); + const delegation = generated.indexOf('If the `instruction` field delegates creation'); + const label = `${adapter.toolId} ${command.id}`; + expect(inspection, label).toBeGreaterThanOrEqual(0); + expect(delegation, label).toBeGreaterThan(inspection); + expect(generated, label).toContain('Keep inspection read-only and proportional to the change'); + } + } + }); +}); + describe('propose implementation boundary', () => { it('makes the planning-only boundary prominent (#232, #258, #262)', () => { for (const [label, body] of proposeBodies) { diff --git a/test/core/templates/skill-templates-parity.test.ts b/test/core/templates/skill-templates-parity.test.ts index c7a8b44b5c..3be53f5557 100644 --- a/test/core/templates/skill-templates-parity.test.ts +++ b/test/core/templates/skill-templates-parity.test.ts @@ -42,14 +42,14 @@ const EXPECTED_FUNCTION_HASHES: Record = { getNewChangeSkillTemplate: 'eabd1e895c5881dcb17dcbaa3fb26098dd59e8eacb318e400820b4dc811ef781', getContinueChangeSkillTemplate: '012136f6411a99c8fa228e2f9444cb64b0a89e0f56fdeac2fe03b2f5bee0c5d7', getApplyChangeSkillTemplate: 'd1e7d5ceb85193c0964057dbb88e9651526754bd33f84020e2440ff0621d5dbb', - getFfChangeSkillTemplate: '5501740e7ec36ab23ab8c3a0d6dd0655a5e2f35433c7b90e82904fef5e7a326a', + getFfChangeSkillTemplate: 'efa6a70c111b18b61a7720250b9622afa9a212fb64edf609cf80e2182a9bdf8c', getSyncSpecsSkillTemplate: 'b099e2ff31859c9b10d928066e662524f9aad9ecf2be12fceacb732d718c4146', getOnboardSkillTemplate: '3a836faae463d88c289a1c129cb7ee556a563b7e53e1a52a4711ff152a3b51f7', getOpsxExploreCommandTemplate: '1460fcb4fbdf22244e9e76608102e611db598cd4cca8c5dbd001292854bcba6e', getOpsxNewCommandTemplate: 'f2d30e569798a4c92ba932859d6ba4e0ad10e18feccbade1cfee0957597b3463', getOpsxContinueCommandTemplate: 'e50e50266efa1b8e64ff9b6274ee8254f0a240d6adc1b862d126e2f1c9d3a559', getOpsxApplyCommandTemplate: 'e3579ac78f2e2c75fa3d3a7ac7dc3e49c395e96f7323398f0f041d94f8de9bb0', - getOpsxFfCommandTemplate: 'e603bc0996604e6c17a3140943ea642a32d0fc65565e25424bf956e124c55772', + getOpsxFfCommandTemplate: '21132fc9c6d3b3ab2d2295d6bbd72d1e0052eb35ea1be0258c8b1ab3e200c4db', getArchiveChangeSkillTemplate: '56bfada1a5f35a127791b70de9d428a75b5aedd1584d6c9803a1ecb1fd1b4a23', getBulkArchiveChangeSkillTemplate: '93875998cade5322d95b43299fba794bc1da754e917dd63a770406386a6d295d', getOpsxSyncCommandTemplate: '0d2427efb79986e8fff3f96bd075a739c80d45eb29159fae717e950030da8202', @@ -58,8 +58,8 @@ const EXPECTED_FUNCTION_HASHES: Record = { getOpsxOnboardCommandTemplate: 'ee99aa99252c602720fbb8c63fb3ac438a5bd4e952fd961ddf1ae956cbfc2c8f', getOpsxBulkArchiveCommandTemplate: '9fa8cdebe2f5667ebfc37bdc023396762c59d5b038c771dac2d8fd2c19e2627b', getOpsxVerifyCommandTemplate: '1efcf7eff0671f48e9d9420f50865c563dd3079ee60f8c380bb7a90dd0102696', - getOpsxProposeSkillTemplate: '24623c066f97e34b957d448d1f9a9e8b8a13da3dfce45d45671f6226a2534848', - getOpsxProposeCommandTemplate: 'e67ba591efb0fecacb2229d06dfa84af18b825fab8a7b01377279e4f09a06ce4', + getOpsxProposeSkillTemplate: '9c0fbf0137151bd03ec30c45180f83daec96e8976ceaf517c63147f84b803446', + getOpsxProposeCommandTemplate: 'b3c145f541dcc13d9859eae8f7bedbe4553371477ed2c5ac07a4a80f82c46f52', getFeedbackSkillTemplate: 'dabeb5e825b9349abc8156c3e7b8608f27987912a6d9bf47ef29addde6138133', getUpdateChangeSkillTemplate: '7dc8abc6f64c58bf34d7581ed4ab095a3b7a53cb372349bee2d840db58622819', getOpsxUpdateCommandTemplate: 'e2388521b22f92f74561df9a0c2f98e1fa4d265af93b5ba26f42fb47a6c5bfed', @@ -70,13 +70,13 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record = { 'openspec-new-change': 'ec4529beef978e34634a6f7286fab55d68fad8fb374dceb45691d52caab33fbb', 'openspec-continue-change': 'bb6194a16c54891cdb253678e8f70ce53b2af86735243980f366ce551d37e42e', 'openspec-apply-change': '81ea96d9fa6ec8536cd23c1fe561ed28e1cc1cad0a8ceb700588e08974cc0e49', - 'openspec-ff-change': '217c78da2b6e8358f609ac57dcd02266aaec3354ce26dc6ec2fc9c2174673ab4', + 'openspec-ff-change': '31355250514bce51b16ff37ee2b833bc9d475cd0dbd4b1f68fe2041694575623', 'openspec-sync-specs': 'd933d8856584d6c1253de91e652e7aee9e85c77ad4d3531f6476f79d84e6e5e8', 'openspec-archive-change': '7c65053d674ba4e1e20e2bf73ba7e5a7f94baef2eaa9b33cee48d4cadea51b7a', 'openspec-bulk-archive-change': '2039b9ecf6e64339dffe0e16272507a386d9fe326f419ff758315aa736fdd96c', 'openspec-verify-change': 'af9be013dcbe8c6d8f6d9ab10c893fbd03f4c62933c384d82f63894dd0ceb84f', 'openspec-onboard': 'f6f59476acaf5e4d65dbb180da4cef62432612f3cecf207d471a951295e2003a', - 'openspec-propose': '25d08ed4f031770cea219604167d76bca9f3e89fe0c2f545263674482c6f13f0', + 'openspec-propose': 'e358b45102a88082cf20f5c4441cba02533724ad6eef8ed15ba174e3496cb6ed', 'openspec-update-change': '586547406aca94422dfeb3ffedce6c01049429b743f57ce829baa79ebc714d51', };