diff --git a/.changeset/requirements-tracking-option.md b/.changeset/requirements-tracking-option.md new file mode 100644 index 000000000..4cd1debd3 --- /dev/null +++ b/.changeset/requirements-tracking-option.md @@ -0,0 +1,5 @@ +--- +"@link-assistant/hive-mind": minor +--- + +Add experimental `--requirements-tracking` to maintain repository requirements docs and auto-restart PRs that omit requirements updates. diff --git a/docs/CONFIGURATION.hi.md b/docs/CONFIGURATION.hi.md index da2f31b39..4e0b080e5 100644 --- a/docs/CONFIGURATION.hi.md +++ b/docs/CONFIGURATION.hi.md @@ -388,6 +388,7 @@ solve [options] | `--prompt-issue-reporting` | | boolean | false | देखे गए bugs के लिए auto-create issues | | `--prompt-case-studies` | | boolean | false | case study documentation बनाएं | | `--prompt-architecture-care` | | boolean | false | [EXPERIMENTAL] REQUIREMENTS.md और ARCHITECTURE.md प्रबंधित करें | +| `--requirements-tracking` | | boolean | false | [EXPERIMENTAL] docs/requirements/\*.md में repository requirements maintain करें और PR update न होने पर एक बार auto-restart करें | | `--prompt-playwright-mcp` | | boolean | true | Playwright MCP hints (केवल तभी जब MCP इंस्टॉल हो, अक्षम करने के लिए `--no-prompt-playwright-mcp` उपयोग करें) | | `--prompt-check-sibling-pull-requests` | | boolean | true | संबंधित कार्य का अध्ययन करते समय sibling PRs जांचें (अक्षम करने के लिए `--no-prompt-check-sibling-pull-requests` उपयोग करें) | | `--prompt-experiments-folder` | | string | ./experiments | experiments folder का पथ (अक्षम करने के लिए खाली करें) | diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index f89f961fd..79dc1b622 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -414,6 +414,7 @@ solve [options] | `--prompt-issue-reporting` | | boolean | false | Auto-create issues for spotted bugs | | `--prompt-case-studies` | | boolean | false | Create case study documentation | | `--prompt-architecture-care` | | boolean | false | [EXPERIMENTAL] Manage REQUIREMENTS.md and ARCHITECTURE.md | +| `--requirements-tracking` | | boolean | false | [EXPERIMENTAL] Maintain repository requirements in docs/requirements/\*.md and auto-restart once if a PR does not update them | | `--prompt-playwright-mcp` | | boolean | true | Playwright MCP hints (only if MCP installed, use `--no-prompt-playwright-mcp` to disable) | | `--prompt-check-sibling-pull-requests` | | boolean | true | Check sibling PRs when studying related work (use `--no-prompt-check-sibling-pull-requests` to disable) | | `--prompt-experiments-folder` | | string | ./experiments | Path to experiments folder (empty to disable) | diff --git a/docs/CONFIGURATION.ru.md b/docs/CONFIGURATION.ru.md index 22e453683..69439341b 100644 --- a/docs/CONFIGURATION.ru.md +++ b/docs/CONFIGURATION.ru.md @@ -388,6 +388,7 @@ solve [options] | `--prompt-issue-reporting` | | boolean | false | Автоматически создавать задачи для обнаруженных ошибок | | `--prompt-case-studies` | | boolean | false | Создавать документацию с разборами случаев | | `--prompt-architecture-care` | | boolean | false | [ЭКСПЕРИМ.] Управлять REQUIREMENTS.md и ARCHITECTURE.md | +| `--requirements-tracking` | | boolean | false | [ЭКСПЕРИМ.] Вести требования репозитория в docs/requirements/\*.md и один раз автоперезапускать, если PR их не обновил | | `--prompt-playwright-mcp` | | boolean | true | Подсказки Playwright MCP (только если MCP установлен, используйте `--no-prompt-playwright-mcp` для отключения) | | `--prompt-check-sibling-pull-requests` | | boolean | true | Проверять смежные PR при изучении связанных работ (используйте `--no-prompt-check-sibling-pull-requests` для отключения) | | `--prompt-experiments-folder` | | string | ./experiments | Путь к папке экспериментов (пусто для отключения) | diff --git a/docs/CONFIGURATION.zh.md b/docs/CONFIGURATION.zh.md index f995c3b1f..3829ead7d 100644 --- a/docs/CONFIGURATION.zh.md +++ b/docs/CONFIGURATION.zh.md @@ -388,6 +388,7 @@ solve [options] | `--prompt-issue-reporting` | | boolean | false | 自动为发现的 bug 创建 issue | | `--prompt-case-studies` | | boolean | false | 创建案例研究文档 | | `--prompt-architecture-care` | | boolean | false | [实验性] 管理 REQUIREMENTS.md 和 ARCHITECTURE.md | +| `--requirements-tracking` | | boolean | false | [实验性] 在 docs/requirements/\*.md 中维护仓库需求;若 PR 未更新则自动重启一次 | | `--prompt-playwright-mcp` | | boolean | true | Playwright MCP 提示(仅当 MCP 已安装时,使用 `--no-prompt-playwright-mcp` 禁用) | | `--prompt-check-sibling-pull-requests` | | boolean | true | 研究相关工作时检查同级 PR(使用 `--no-prompt-check-sibling-pull-requests` 禁用) | | `--prompt-experiments-folder` | | string | ./experiments | 实验文件夹路径(留空则禁用) | diff --git a/docs/case-studies/issue-1743/README.md b/docs/case-studies/issue-1743/README.md new file mode 100644 index 000000000..147ffeaa4 --- /dev/null +++ b/docs/case-studies/issue-1743/README.md @@ -0,0 +1,88 @@ +# Issue 1743: Requirements Tracking + +## Scope + +Issue: https://github.com/link-assistant/hive-mind/issues/1743 + +Pull request: https://github.com/link-assistant/hive-mind/pull/1744 + +The request is to add an experimental `--requirements-tracking` option. When enabled, the solver should ask AI tools to maintain repository requirements in `docs/requirements/*.md`, use `docs/requirements/README.md` as the main index, collect issue case-study data under `docs/case-studies/issue-{id}/`, and auto-restart when a pull request omits requirements-document updates. + +## Evidence + +Raw evidence collected for this case study is in `data/`: + +- `issue-1743.json`: issue metadata and body. +- `issue-1743-comments.json`: issue comments, currently empty. +- `pr-1744.json`: prepared pull request metadata before implementation. +- `pr-1744-issue-comments.json`, `pr-1744-review-comments.json`, `pr-1744-reviews.json`: pull request feedback, currently empty. +- `pr-1744-initial-files.txt`: initial changed files in the prepared pull request. +- `online-sources.md`: external references checked during analysis. + +Online research used three stable sources: + +- GitHub Docs describes linked pull requests and issue-closing keywords, which supports keeping PR descriptions tied to issue requirements and fixes. +- ISO/IEC/IEEE 29148:2018 is the current ISO requirements-engineering standard page; its abstract covers requirements processes, information items, contents, and format guidance. +- NIST SP 500-204 notes software requirements quality attributes including completeness, correctness, consistency, verifiability, modifiability, traceability, understandability, and robustness. + +## Requirements + +- Add `--requirements-tracking`. +- Keep the option disabled by default because it changes repository files. +- When enabled, prompt AI tools to read existing `docs/requirements/README.md` and referenced requirement documents before work. +- When enabled, prompt AI tools to create or update `docs/requirements/*.md` when repository-level requirements are added, modified, or removed in issues, issue comments, PR comments, or reviews. +- Treat `docs/requirements/README.md` as the main requirements index. +- Enforce the prompt guidance for all supported solve tools: `claude`, `codex`, `gemini`, `qwen`, `opencode`, and `agent`. +- If requirements tracking is enabled and the pull request does not change `docs/requirements/*.md`, auto-restart once or auto-resume with feedback. +- Collect issue-related data under `docs/case-studies/issue-1743/`. +- Include requirements, solution options, selected plan, and verification notes in the case study. + +## Existing Behavior + +`src/architecture-care.prompts.lib.mjs` already has optional guidance for root-level `REQUIREMENTS.md` and `ARCHITECTURE.md`, behind `--prompt-architecture-care`. That option is broader and uses root files, so it does not satisfy this issue's dedicated `docs/requirements/*.md` ledger requirement. + +`--prompt-case-studies` already asks agents to collect case-study data under `docs/case-studies/issue-{id}/`, but it is a prompt-only feature and does not create a persistent requirements ledger or enforce requirements-document updates. + +`verifyResults()` already detects placeholder PR titles and descriptions, then allows `solve.mjs` to restart once when `--auto-restart-on-non-updated-pull-request-description` is set. This is the closest existing enforcement pattern. + +## Solution Options + +### Option 1: Extend `--prompt-architecture-care` + +This would reuse the existing prompt module, but it would mix root `REQUIREMENTS.md` and `ARCHITECTURE.md` guidance with the new `docs/requirements/*.md` ledger. It also would not provide the requested `--requirements-tracking` flag. + +Decision: rejected. + +### Option 2: Add a prompt-only `--requirements-tracking` + +This would be simple and would satisfy part of the issue, but it would not auto-restart when a pull request forgets to update requirements documents. + +Decision: rejected. + +### Option 3: Add a dedicated option, prompt module, and PR-file enforcement + +This keeps the behavior explicit and off by default, reuses the existing prompt wiring style for all supported tools, and extends the existing verification/restart pattern with a focused `docs/requirements/*.md` changed-file check. + +Decision: selected. + +## Implementation Plan + +1. Add `requirements-tracking` to `SOLVE_OPTION_DEFINITIONS` with `type: "boolean"` and `default: false`. +2. Add `src/requirements-tracking.prompts.lib.mjs` with gated prompt guidance. +3. Include the gated sub-prompt in every supported tool prompt module. +4. Add helpers in `src/solve.results.lib.mjs` to parse PR changed files and detect `docs/requirements/*.md`. +5. Make `verifyResults()` avoid exiting when requirements tracking is enabled and the PR lacks requirements-doc changes. +6. Add a one-time restart block in `src/solve.mjs` before `--finalize`, using feedback that points the agent to `docs/requirements/*.md`. +7. Add regression coverage for option defaults, prompt gating, supported tool prompt wiring, file detection, and restart-hook ordering. +8. Create `docs/requirements/README.md` and this case study as the first tracked requirements update. + +## Verification Plan + +- Run the issue-specific test: `node tests/test-requirements-tracking-1743.mjs`. +- Run documentation option sync: `node tests/test-docs-options-sync.mjs`. +- Run lint: `npm run lint`. +- Run the default suite: `npm test`. + +## Notes + +The enforcement check intentionally keys off pull request changed files rather than local working-tree state. The requirement in the issue is about the final pull request, and checking the PR diff avoids false confidence from uncommitted local files. diff --git a/docs/case-studies/issue-1743/data/issue-1743-comments.json b/docs/case-studies/issue-1743/data/issue-1743-comments.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/docs/case-studies/issue-1743/data/issue-1743-comments.json @@ -0,0 +1 @@ +[] diff --git a/docs/case-studies/issue-1743/data/issue-1743.json b/docs/case-studies/issue-1743/data/issue-1743.json new file mode 100644 index 000000000..d4e7c7d88 --- /dev/null +++ b/docs/case-studies/issue-1743/data/issue-1743.json @@ -0,0 +1,14 @@ +{ + "author": { "id": "MDQ6VXNlcjE0MzE5MDQ=", "is_bot": false, "login": "konard", "name": "Konstantin Diachenko" }, + "body": "That options should ask AI to keep track of all requirements for the repository in `./docs/requirements/*.md` files. With main file to be `./docs/requirements/README.md`.\n\nAs it does affect files in the repository, that option should be turned off by default. So for AI it should be recommended to read these requirements to understand the context of the system and that is required from it before working with any issue. And also if any new requirements are added or modified or removed as the result of issue or comments to pull request `./docs/requirements/*.md` files should be updated.\n\nTo enforce it we should have addition to system message about tracking of requirements for all supported tools.\n\nAlso if `./docs/requirements/*.md` were not updated at pull request at all, we should do auto-restart or auto-resume.\n\nAs the result it will allow user not to repeat himself too much, and yet there is no need to fully read the code to understand the requirements fully. So requirements should be kept short, and yet contain all requirements. If user overrides some of them at any stage changes should be reflected in the requirements folder.\n\nWe need to collect data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), list of each and all requirements from the issue, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nPlease plan and execute everything in a single pull request, you have unlimited time and context, as context autocompacts and you can continue indefinetely, until it is each and every requirement fully addressed, and everything is totally done.", + "createdAt": "2026-05-02T06:51:00Z", + "labels": [ + { "id": "LA_kwDOPUU0qc8AAAACGYm6jw", "name": "documentation", "description": "Improvements or additions to documentation", "color": "0075ca" }, + { "id": "LA_kwDOPUU0qc8AAAACGYm6mA", "name": "enhancement", "description": "New feature or request", "color": "a2eeef" } + ], + "number": 1743, + "state": "OPEN", + "title": "`--requirements-tracking`", + "updatedAt": "2026-05-02T06:51:00Z", + "url": "https://github.com/link-assistant/hive-mind/issues/1743" +} diff --git a/docs/case-studies/issue-1743/data/online-sources.md b/docs/case-studies/issue-1743/data/online-sources.md new file mode 100644 index 000000000..e4d6a395a --- /dev/null +++ b/docs/case-studies/issue-1743/data/online-sources.md @@ -0,0 +1,5 @@ +# Online Sources Checked + +- GitHub Docs, "Linking a pull request to an issue": https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue +- ISO, "ISO/IEC/IEEE 29148:2018 Systems and software engineering - Life cycle processes - Requirements engineering": https://www.iso.org/cms/%20render/live/en/sites/isoorg/contents/data/standard/07/20/72089.html +- NIST SP 500-204, "High integrity software standards and guidelines": https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication500-204.pdf diff --git a/docs/case-studies/issue-1743/data/pr-1744-initial-files.txt b/docs/case-studies/issue-1743/data/pr-1744-initial-files.txt new file mode 100644 index 000000000..e1664421e --- /dev/null +++ b/docs/case-studies/issue-1743/data/pr-1744-initial-files.txt @@ -0,0 +1 @@ +.gitkeep diff --git a/docs/case-studies/issue-1743/data/pr-1744-issue-comments.json b/docs/case-studies/issue-1743/data/pr-1744-issue-comments.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/docs/case-studies/issue-1743/data/pr-1744-issue-comments.json @@ -0,0 +1 @@ +[] diff --git a/docs/case-studies/issue-1743/data/pr-1744-review-comments.json b/docs/case-studies/issue-1743/data/pr-1744-review-comments.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/docs/case-studies/issue-1743/data/pr-1744-review-comments.json @@ -0,0 +1 @@ +[] diff --git a/docs/case-studies/issue-1743/data/pr-1744-reviews.json b/docs/case-studies/issue-1743/data/pr-1744-reviews.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/docs/case-studies/issue-1743/data/pr-1744-reviews.json @@ -0,0 +1 @@ +[] diff --git a/docs/case-studies/issue-1743/data/pr-1744.json b/docs/case-studies/issue-1743/data/pr-1744.json new file mode 100644 index 000000000..86f17e6e1 --- /dev/null +++ b/docs/case-studies/issue-1743/data/pr-1744.json @@ -0,0 +1 @@ +{ "baseRefName": "main", "body": "## 🤖 AI-Powered Solution Draft\n\nThis pull request is being automatically generated to solve issue #1743.\n\n### 📋 Issue Reference\nFixes #1743\n\n### 🚧 Status\n**Work in Progress** - The AI assistant is currently analyzing and implementing the solution draft.\n\n### 📝 Implementation Details\n_Details will be added as the solution draft is developed..._\n\n---\n*This PR was created automatically by the AI issue solver*", "createdAt": "2026-05-02T06:51:56Z", "headRefName": "issue-1743-df64c32eba77", "isDraft": true, "number": 1744, "state": "OPEN", "title": "[WIP] `--requirements-tracking`", "updatedAt": "2026-05-02T06:51:58Z", "url": "https://github.com/link-assistant/hive-mind/pull/1744" } diff --git a/docs/requirements/README.md b/docs/requirements/README.md new file mode 100644 index 000000000..107060fe1 --- /dev/null +++ b/docs/requirements/README.md @@ -0,0 +1,17 @@ +# Repository Requirements + +This directory is the persistent requirements ledger for hive-mind. `README.md` is the main index; additional `*.md` files can be added when a requirement area grows too large for this file. + +## Requirements Tracking + +- `--requirements-tracking` is experimental and disabled by default because it changes repository files. +- When `--requirements-tracking` is enabled, every supported AI tool prompt must tell the agent to read and maintain `docs/requirements/*.md`. +- `docs/requirements/README.md` is the required main index for repository requirements. +- When an issue, issue comment, pull request comment, or review adds, modifies, or removes a repository-level requirement, the same pull request must update `docs/requirements/*.md`. +- If requirements tracking is enabled and a pull request does not modify `docs/requirements/*.md`, solve post-processing should auto-restart the tool once with feedback asking it to update the requirements documentation or justify why no repository requirement changed. +- Requirements entries should be short, factual, repository-level statements. They should not duplicate full issue or pull request transcripts. + +## Case Study Documentation + +- When a task requires a case study, issue-related data should be collected under `docs/case-studies/issue-{id}/`. +- Case studies should preserve raw issue and pull request data when practical, list extracted requirements, describe solution options, and record the selected implementation and verification plan. diff --git a/src/agent.prompts.lib.mjs b/src/agent.prompts.lib.mjs index 0b06bcb00..54be12f4f 100644 --- a/src/agent.prompts.lib.mjs +++ b/src/agent.prompts.lib.mjs @@ -5,6 +5,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -257,7 +258,7 @@ Visual UI work and screenshots. - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description. - When possible, create automated visual regression tests to prevent the UI bug from recurring.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; // Export all functions as default object too diff --git a/src/claude.prompts.lib.mjs b/src/claude.prompts.lib.mjs index 13a9759e7..642ae1e3e 100644 --- a/src/claude.prompts.lib.mjs +++ b/src/claude.prompts.lib.mjs @@ -6,6 +6,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; import { primaryModelNames } from './models/index.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -328,7 +329,7 @@ Visual UI work and screenshots. - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description. - When possible, create automated visual regression tests to prevent the UI bug from recurring.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; // Export all functions as default object too diff --git a/src/codex.prompts.lib.mjs b/src/codex.prompts.lib.mjs index 82acfd729..3578911b0 100644 --- a/src/codex.prompts.lib.mjs +++ b/src/codex.prompts.lib.mjs @@ -5,6 +5,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -305,7 +306,7 @@ Visual UI work and screenshots. - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description. - When possible, create automated visual regression tests to prevent the UI bug from recurring.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; // Export all functions as default object too diff --git a/src/gemini.prompts.lib.mjs b/src/gemini.prompts.lib.mjs index 9ac144be2..a27565971 100644 --- a/src/gemini.prompts.lib.mjs +++ b/src/gemini.prompts.lib.mjs @@ -5,6 +5,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -227,7 +228,7 @@ Visual UI work and screenshots. - When you work on visual UI changes, include a render or screenshot of the final result in the pull request description. - When you save screenshots to the repository, use permanent links in the PR description such as https://github.com/${screenshotRepoPath}/blob/${branchName}/docs/screenshots/result.png?raw=true.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; export default { diff --git a/src/opencode.prompts.lib.mjs b/src/opencode.prompts.lib.mjs index 2814ea183..59f4497e7 100644 --- a/src/opencode.prompts.lib.mjs +++ b/src/opencode.prompts.lib.mjs @@ -5,6 +5,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -258,7 +259,7 @@ Visual UI work and screenshots. - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description. - When possible, create automated visual regression tests to prevent the UI bug from recurring.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; // Export all functions as default object too diff --git a/src/option-suggestions.lib.mjs b/src/option-suggestions.lib.mjs index 9ecdf98aa..43a573aa4 100644 --- a/src/option-suggestions.lib.mjs +++ b/src/option-suggestions.lib.mjs @@ -188,6 +188,7 @@ const KNOWN_OPTION_NAMES = [ 'prompt-general-purpose-sub-agent', 'prompt-issue-reporting', 'prompt-architecture-care', + 'requirements-tracking', 'prompt-case-studies', 'prompt-playwright-mcp', 'prompt-check-sibling-pull-requests', diff --git a/src/qwen.prompts.lib.mjs b/src/qwen.prompts.lib.mjs index 72e55b8b4..0728c5979 100644 --- a/src/qwen.prompts.lib.mjs +++ b/src/qwen.prompts.lib.mjs @@ -5,6 +5,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs'; import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs'; +import { getRequirementsTrackingSubPrompt } from './requirements-tracking.prompts.lib.mjs'; import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs'; /** @@ -244,7 +245,7 @@ Visual UI work and screenshots. - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description. - When possible, create automated visual regression tests to prevent the UI bug from recurring.` : '' - }${ciExamples}${getArchitectureCareSubPrompt(argv)}`; + }${ciExamples}${getRequirementsTrackingSubPrompt(argv)}${getArchitectureCareSubPrompt(argv)}`; }; export default { diff --git a/src/requirements-tracking.prompts.lib.mjs b/src/requirements-tracking.prompts.lib.mjs new file mode 100644 index 000000000..2a1b22608 --- /dev/null +++ b/src/requirements-tracking.prompts.lib.mjs @@ -0,0 +1,41 @@ +/** + * Requirements tracking sub-prompt module. + * + * This experimental feature is enabled via --requirements-tracking. + */ + +/** + * Build the requirements tracking sub-prompt content. + * + * @returns {string} The formatted sub-prompt for requirements tracking. + */ +export const buildRequirementsTrackingSubPrompt = () => { + return ` +Requirements Tracking. + - This repository uses docs/requirements/*.md as the persistent requirements ledger. + - docs/requirements/README.md is the main index for repository requirements. + - At the start of work, if docs/requirements/README.md exists, read it and the requirement documents it references before changing code. + - Keep requirements short, factual, repository-level, and useful for future issue work. Do not duplicate issue transcripts. + - When an issue, issue comment, pull request comment, or review adds, modifies, or removes a repository requirement, update docs/requirements/*.md in the same pull request. + - If requirements tracking is enabled and no docs/requirements/ directory exists, create docs/requirements/README.md with the current known requirements. + - Before finalizing, check the pull request files and confirm docs/requirements/*.md changed when repository requirements changed. + - If no repository requirement changed, state that explicitly in the pull request description.`; +}; + +/** + * Get the requirements tracking sub-prompt if enabled. + * + * @param {Object} argv - Command line arguments. + * @returns {string} The sub-prompt content or empty string if disabled. + */ +export const getRequirementsTrackingSubPrompt = argv => { + if (argv && argv.requirementsTracking) { + return buildRequirementsTrackingSubPrompt(); + } + return ''; +}; + +export default { + buildRequirementsTrackingSubPrompt, + getRequirementsTrackingSubPrompt, +}; diff --git a/src/solve.config.lib.mjs b/src/solve.config.lib.mjs index 4084db59f..0ecb6b209 100644 --- a/src/solve.config.lib.mjs +++ b/src/solve.config.lib.mjs @@ -421,6 +421,11 @@ export const SOLVE_OPTION_DEFINITIONS = { description: '[EXPERIMENTAL] Include guidance for managing REQUIREMENTS.md and ARCHITECTURE.md files. When enabled, agents will update these documentation files when changes affect requirements or architecture.', default: false, }, + 'requirements-tracking': { + type: 'boolean', + description: '[EXPERIMENTAL] Ask agents to maintain repository requirements in docs/requirements/*.md and auto-restart once if a pull request does not update them.', + default: false, + }, 'prompt-case-studies': { type: 'boolean', description: 'Create comprehensive case study documentation for the issue including logs, analysis, timeline, root cause investigation, and proposed solutions. Organizes findings into ./docs/case-studies/issue-{id}/ directory. Supported for --tool claude and --tool codex.', diff --git a/src/solve.mjs b/src/solve.mjs index 21a0c87bd..89ca706f2 100755 --- a/src/solve.mjs +++ b/src/solve.mjs @@ -1122,7 +1122,7 @@ try { }); // Search for newly created pull requests and comments - const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls); + let verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls); const logsAlreadyUploaded = verifyResult?.logUploadSuccess || false; // Issue #1162: Auto-restart when PR title/description still has placeholder content @@ -1171,12 +1171,59 @@ try { // Re-verify results after restart (without auto-restart flag to prevent recursion) const reVerifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, { ...argv, autoRestartOnNonUpdatedPullRequestDescription: false }, shouldAttachLogs, false, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls); + verifyResult = reVerifyResult; if (reVerifyResult?.prTitleHasPlaceholder || reVerifyResult?.prBodyHasPlaceholder) { await log('⚠️ PR title/description still not updated after restart'); } } + // Issue #1743: Auto-restart once when --requirements-tracking is enabled but docs/requirements/*.md was not updated + if (argv.requirementsTracking && verifyResult?.requirementsDocsChecked && !verifyResult?.requirementsDocsUpdated) { + const { buildRequirementsDocsNotUpdatedHint } = results; + const hintLines = buildRequirementsDocsNotUpdatedHint(); + + await log(''); + await log('🔄 AUTO-RESTART: Requirements tracking docs not updated'); + for (const line of hintLines) { + await log(` ${line}`); + } + await log(' Restarting tool to give agent another chance to update requirements documentation...'); + await log(''); + + const { executeToolIteration } = await import('./solve.restart-shared.lib.mjs'); + + const restartResult = await executeToolIteration({ + issueUrl, + owner, + repo, + issueNumber, + prNumber, + branchName, + tempDir, + workspaceTmpDir, + mergeStateStatus, + feedbackLines: hintLines, + argv, + }); + + if (restartResult) { + if (restartResult.sessionId) sessionId = restartResult.sessionId; + if (restartResult.anthropicTotalCostUSD) anthropicTotalCostUSD = restartResult.anthropicTotalCostUSD; + if (restartResult.publicPricingEstimate) publicPricingEstimate = restartResult.publicPricingEstimate; + if (restartResult.pricingInfo) pricingInfo = restartResult.pricingInfo; + } + + await cleanupClaudeFile(tempDir, branchName, null, argv); + + const reVerifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, { ...argv, autoRestartOnNonUpdatedPullRequestDescription: false }, shouldAttachLogs, false, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls); + verifyResult = reVerifyResult; + + if (reVerifyResult?.requirementsDocsChecked && !reVerifyResult?.requirementsDocsUpdated) { + await log('⚠️ Requirements tracking docs still not updated after restart'); + } + } + // Issue #1383: --finalize const autoEnsureResult = await runAutoEnsureRequirements({ issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, argv, cleanupClaudeFile }); if (autoEnsureResult) { diff --git a/src/solve.results.lib.mjs b/src/solve.results.lib.mjs index 586e610b1..79a4192fb 100644 --- a/src/solve.results.lib.mjs +++ b/src/solve.results.lib.mjs @@ -133,6 +133,56 @@ export const buildPRNotUpdatedHint = (titleNotUpdated, descriptionNotUpdated) => return lines; }; +export const REQUIREMENTS_TRACKING_DOCS_DIRECTORY = 'docs/requirements/'; + +export const normalizeChangedFilePath = filePath => { + return String(filePath || '') + .replaceAll('\\', '/') + .replace(/^\.\//, ''); +}; + +export const isRequirementsTrackingDocumentPath = filePath => { + const normalized = normalizeChangedFilePath(filePath); + return normalized.startsWith(REQUIREMENTS_TRACKING_DOCS_DIRECTORY) && normalized.endsWith('.md'); +}; + +export const hasRequirementsTrackingDocumentChange = filePaths => { + return Array.isArray(filePaths) && filePaths.some(isRequirementsTrackingDocumentPath); +}; + +export const parseChangedFilesOutput = output => { + return String(output || '') + .split(/\r?\n/) + .map(line => line.trim()) + .filter(Boolean); +}; + +export const getPullRequestChangedFiles = async ({ prNumber, owner, repo, command = $, logger = log } = {}) => { + if (!prNumber || !owner || !repo) { + return { checked: false, files: [], error: 'missing_pull_request_context' }; + } + + try { + const diffFilesResult = await command`gh pr diff ${prNumber} --repo ${owner}/${repo} --name-only`; + if (diffFilesResult.code !== 0) { + const stderr = diffFilesResult.stderr ? diffFilesResult.stderr.toString().trim() : ''; + if (logger) await logger(` ⚠️ Could not list pull request files for requirements tracking${stderr ? `: ${stderr}` : ''}`); + return { checked: false, files: [], error: stderr || 'gh_pr_diff_failed' }; + } + + return { + checked: true, + files: parseChangedFilesOutput(diffFilesResult.stdout?.toString()), + error: null, + }; + } catch (error) { + if (logger) await logger(` ⚠️ Could not list pull request files for requirements tracking: ${error.message}`); + return { checked: false, files: [], error: error.message }; + } +}; + +export const buildRequirementsDocsNotUpdatedHint = () => ['Requirements tracking is enabled, but this pull request does not modify docs/requirements/*.md.', 'Read docs/requirements/README.md if it exists, then create or update docs/requirements/*.md to reflect repository requirements from the issue and pull request discussion.', 'If no repository requirement changed, update the pull request description with that justification.']; + /** * Ensure an existing pull request body contains a GitHub closing keyword for the issue. * @@ -703,6 +753,9 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb // Declare placeholder detection variables outside block scopes for use in return value let prTitleHasPlaceholder = false; let prBodyHasPlaceholder = false; + let requirementsDocsChecked = false; + let requirementsDocsUpdated = false; + let requirementsChangedFiles = []; // Skip PR body update and ready conversion for merged PRs (they can't be edited) if (!isPrMerged) { @@ -791,6 +844,26 @@ Fixes ${issueRef} } } + if (argv.requirementsTracking) { + await log(' 🔍 Checking requirements tracking documentation updates...'); + const changedFilesResult = await getPullRequestChangedFiles({ + prNumber: pr.number, + owner, + repo, + command: $, + logger: log, + }); + requirementsDocsChecked = changedFilesResult.checked; + requirementsChangedFiles = changedFilesResult.files; + requirementsDocsUpdated = hasRequirementsTrackingDocumentChange(requirementsChangedFiles); + + if (requirementsDocsChecked && requirementsDocsUpdated) { + await log(' ✅ Requirements tracking documentation changed in this pull request'); + } else if (requirementsDocsChecked) { + await log(' ⚠️ Requirements tracking is enabled, but docs/requirements/*.md was not changed'); + } + } + // Check if PR is ready for review (convert from draft if necessary) if (pr.isDraft) { await log(' 🔄 Converting PR from draft to ready for review...'); @@ -854,16 +927,20 @@ Fixes ${issueRef} if (shouldAutoRestartForPlaceholder) { await log('\n🔄 Placeholder detected in PR title/description - auto-restart will be triggered'); } + const shouldAutoRestartForRequirementsTracking = argv.requirementsTracking && requirementsDocsChecked && !requirementsDocsUpdated; + if (shouldAutoRestartForRequirementsTracking) { + await log('\n🔄 Requirements tracking docs missing from PR - auto-restart will be triggered'); + } const shouldWaitForAutoMerge = argv.autoMerge || argv.autoRestartUntilMergeable; if (shouldWaitForAutoMerge) { await log('\n🔄 Auto-merge mode enabled - will attempt to merge after verification'); } - if (!argv.watch && !shouldRestart && !shouldAutoRestartForPlaceholder && !shouldWaitForAutoMerge) { + if (!argv.watch && !shouldRestart && !shouldAutoRestartForPlaceholder && !shouldAutoRestartForRequirementsTracking && !shouldWaitForAutoMerge) { await safeExit(0, 'Process completed successfully'); } // Issue #1154: Return logUploadSuccess to prevent duplicate log uploads // Issue #1162: Return placeholder detection status for auto-restart - return { logUploadSuccess, prTitleHasPlaceholder, prBodyHasPlaceholder }; // Return for watch mode or auto-restart + return { logUploadSuccess, prTitleHasPlaceholder, prBodyHasPlaceholder, requirementsDocsChecked, requirementsDocsUpdated, requirementsChangedFiles }; // Return for watch mode or auto-restart } else { await log(` ℹ️ Found pull request #${pr.number} but it appears to be from a different session`); } diff --git a/tests/test-requirements-tracking-1743.mjs b/tests/test-requirements-tracking-1743.mjs new file mode 100644 index 000000000..3d5f0b81d --- /dev/null +++ b/tests/test-requirements-tracking-1743.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +/** + * Regression coverage for issue #1743. + * + * @hive-mind-test-suite default + */ + +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import fsModule from 'node:fs'; +import osModule from 'node:os'; +import pathModule from 'node:path'; +import { fileURLToPath } from 'node:url'; + +globalThis.use = async name => { + if (name === 'command-stream') { + return { + $: () => ({ + code: 0, + stdout: '', + stderr: '', + stream: async function* stream() {}, + }), + }; + } + if (name === 'fs') return { ...fsModule, default: fsModule }; + if (name === 'path') return { ...pathModule, default: pathModule }; + if (name === 'os') return { ...osModule, default: osModule }; + return await import(name); +}; + +const __dirname = pathModule.dirname(fileURLToPath(import.meta.url)); +const repoRoot = pathModule.join(__dirname, '..'); + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const buildPromptParams = argv => ({ + owner: 'link-assistant', + repo: 'hive-mind', + issueNumber: 1743, + prNumber: 1744, + branchName: 'issue-1743-df64c32eba77', + workspaceTmpDir: '/tmp/hive-mind', + argv, + modelSupportsVision: false, +}); + +test('requirements-tracking option is defined and disabled by default', async () => { + const { SOLVE_OPTION_DEFINITIONS } = await import('../src/solve.config.lib.mjs'); + const option = SOLVE_OPTION_DEFINITIONS['requirements-tracking']; + + assert.ok(option, 'requirements-tracking option should exist'); + assert.equal(option.type, 'boolean'); + assert.equal(option.default, false); + assert.match(option.description, /docs\/requirements\/\*\.md/); +}); + +test('requirements-tracking prompt is gated and mentions the requirements ledger', async () => { + const { getRequirementsTrackingSubPrompt } = await import('../src/requirements-tracking.prompts.lib.mjs'); + + assert.equal(getRequirementsTrackingSubPrompt({ requirementsTracking: false }), ''); + + const prompt = getRequirementsTrackingSubPrompt({ requirementsTracking: true }); + assert.match(prompt, /Requirements Tracking/); + assert.match(prompt, /docs\/requirements\/README\.md/); + assert.match(prompt, /docs\/requirements\/\*\.md/); +}); + +test('all supported tool prompts include requirements tracking only when enabled', async () => { + const promptModules = ['claude', 'codex', 'gemini', 'qwen', 'opencode', 'agent']; + + for (const tool of promptModules) { + const { buildSystemPrompt } = await import(`../src/${tool}.prompts.lib.mjs`); + + const disabledPrompt = buildSystemPrompt(buildPromptParams({ requirementsTracking: false })); + assert.equal(disabledPrompt.includes('Requirements Tracking.'), false, `${tool} prompt should omit requirements tracking by default`); + + const enabledPrompt = buildSystemPrompt(buildPromptParams({ requirementsTracking: true })); + assert.equal(enabledPrompt.includes('Requirements Tracking.'), true, `${tool} prompt should include requirements tracking when enabled`); + assert.equal(enabledPrompt.includes('docs/requirements/README.md'), true, `${tool} prompt should mention docs/requirements/README.md`); + } +}); + +test('PR changed-file helpers detect docs/requirements markdown updates', async () => { + const { buildRequirementsDocsNotUpdatedHint, hasRequirementsTrackingDocumentChange, isRequirementsTrackingDocumentPath } = await import('../src/solve.results.lib.mjs'); + + assert.equal(isRequirementsTrackingDocumentPath('docs/requirements/README.md'), true); + assert.equal(isRequirementsTrackingDocumentPath('docs/requirements/product.md'), true); + assert.equal(isRequirementsTrackingDocumentPath('docs/requirements/product.txt'), false); + assert.equal(isRequirementsTrackingDocumentPath('docs/case-studies/issue-1743/README.md'), false); + assert.equal(hasRequirementsTrackingDocumentChange(['src/solve.mjs', 'docs/requirements/README.md']), true); + assert.equal(hasRequirementsTrackingDocumentChange(['src/solve.mjs', 'docs/CONFIGURATION.md']), false); + assert.ok(buildRequirementsDocsNotUpdatedHint().some(line => line.includes('docs/requirements/*.md'))); +}); + +test('solve.mjs has a requirements tracking auto-restart hook before finalize', async () => { + const source = await fs.readFile(pathModule.join(repoRoot, 'src', 'solve.mjs'), 'utf8'); + const verifyResultPos = source.indexOf('let verifyResult = await verifyResults('); + const requirementsRestartPos = source.indexOf('AUTO-RESTART: Requirements tracking docs not updated'); + const finalizePos = source.indexOf('const autoEnsureResult = await runAutoEnsureRequirements('); + + assert.ok(verifyResultPos > 0, 'verifyResult should be mutable so restart checks can use refreshed verification data'); + assert.ok(requirementsRestartPos > verifyResultPos, 'requirements tracking restart should run after verifyResults'); + assert.ok(requirementsRestartPos < finalizePos, 'requirements tracking restart should run before --finalize'); +}); + +let passed = 0; + +for (const { name, fn } of tests) { + await fn(); + passed++; + console.log(`ok ${passed} - ${name}`); +} + +console.log(`\n${passed} issue #1743 requirements tracking tests passed`);