diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d273f74 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,72 @@ +name: Release npm package + +on: + push: + branches: + - dev + - main + +concurrency: + group: npm-release-${{ github.ref_name }} + cancel-in-progress: false + +permissions: + contents: read + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: npm ci + + - name: Verify package is releasable + run: npm run release:check + + - name: Resolve publish version + id: resolve + run: node scripts/resolve-release-version.js "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + + - name: Show resolved release plan + run: | + echo "branch=${GITHUB_REF_NAME}" + echo "package=${{ steps.resolve.outputs.package_name }}" + echo "channel=${{ steps.resolve.outputs.channel }}" + echo "version=${{ steps.resolve.outputs.version }}" + echo "publish_tag=${{ steps.resolve.outputs.publish_tag }}" + echo "should_publish=${{ steps.resolve.outputs.should_publish }}" + echo "reason=${{ steps.resolve.outputs.reason }}" + + - name: Apply publish version + if: steps.resolve.outputs.should_publish == 'true' + run: npm version "${{ steps.resolve.outputs.version }}" --no-git-tag-version + + - name: Publish package to npm + if: steps.resolve.outputs.should_publish == 'true' + run: npm publish --provenance --tag "${{ steps.resolve.outputs.publish_tag }}" + + - name: Release summary + run: | + { + echo "## npm release" + echo "" + echo "- Branch: ${GITHUB_REF_NAME}" + echo "- Package: ${{ steps.resolve.outputs.package_name }}" + echo "- Channel: ${{ steps.resolve.outputs.channel }}" + echo "- Version: ${{ steps.resolve.outputs.version }}" + echo "- npm tag: ${{ steps.resolve.outputs.publish_tag }}" + echo "- Published: ${{ steps.resolve.outputs.should_publish }}" + echo "- Reason: ${{ steps.resolve.outputs.reason }}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/Agents_Common.md b/Agents_Common.md index 14f67c6..5a0657d 100644 --- a/Agents_Common.md +++ b/Agents_Common.md @@ -54,19 +54,20 @@ That document defines: ## 5. Operational Guidelines * **Documentation Reading:** Whenever reading any file under `docs/` or `tasks/`, the file MUST be read fully to ensure complete understanding of the context and requirements. -* **Role-Specific Guidelines:** Every agent is responsible for reading their specific guideline file from `docs/core/` at the start of their session (e.g., `developer_guidelines.md`, `qa_guidelines.md`). +* **Role-Specific Guidelines:** Every agent is responsible for reading the core guidance and any applicable repository policy includes that are part of their prompt. +* **Definition Of Ready / Done:** All execution should follow the repository's active Definition of Ready and Definition of Done policies. * **Signed Agent Messages:** Agent-to-agent interactions must begin with a signed first message that clearly identifies the sending and receiving agents. Use this exact format on the first line: `[Agent Message] From: To: `. Example: `[Agent Message] From: product_manager To: tech_lead`. If a message does not begin with an agent signature, agents should assume they are speaking directly with the user. * **Pre-task Clarification:** Before starting any task, thoroughly review requirements. If anything is missing, ambiguous, or insufficient, immediately stop and clearly state what is needed, requesting clarification from the manager agent. Do not proceed until all requirements are clear. * **CodeMap-First Navigation:** Before broad repository search, agents should consult the most relevant `codemap.yml` chain for the area they are trying to understand. Use local, parent, root, or explicitly targeted module CodeMaps as the first navigation pass. If no suitable CodeMap exists or it is insufficient, agents may then expand into direct search and source inspection. * **Sync-up Mode Evaluation:** When in Sync-up Mode, critically evaluate the provided task definition for completeness and clarity. Identify missing information and explain its cruciality. * **Development Considerations:** Always keep in mind Security, Scalability, Maintainability, Error Handling, Performance, and Consistency. * **Concise Communication:** Agent responses should be brief, direct, and non-repetitive. Do not restate the same point multiple times, and do not become overly verbose unless the user explicitly asks for more detail. -* **.gitignore Updates:** Whenever project setups are completed (e.g., adding new dependencies, features, or environments), ensure the `.gitignore` file is updated to exclude sensitive, temporary, or unnecessary files from version control. +* **.gitignore Updates:** Whenever repository changes introduce generated, temporary, or sensitive files, ensure ignore rules are updated appropriately. * **Task Success Criteria:** No task is considered successful if there are failed tests, failed builds, or any other reason that prevents successful deployment. Any such issues must be fixed, even if the cause is not directly related to the current changes. * **Acceptance Criteria Traceability:** Every task must define numbered acceptance criteria (`AC-1`, `AC-2`, ...) and the final evidence must trace verification back to those criteria. * **Subagent Delegation:** No subagent simulation; we will be using actual subagents via the Task tool for every task delegation. When a task is assigned to a subagent, a task file MUST be provided, and the subagent MUST be instructed to read this file for detailed instructions. If a task is assigned without a task file, the subagent MUST strictly refuse to perform the task. * **Economical Task Planning:** All agents should plan their tasks to be economical and smart to reduce requests usage. One such trick could be to use batched requests when appropriate. -* **External Dependency Management:** When setting up or integrating external dependencies, always use the latest stable version. If a dependency provides a utility or script for setup/initialization (e.g., `npm install`, `init` scripts), prefer using that utility to ensure correct configuration. +* **External Dependency Management:** Follow the repository's development policy when selecting, updating, or initializing external dependencies. * **Post-Implementation Task Updates:** After completing their implementation step, each subagent MUST update the task file with a section titled `# Post Implementation Task Updates`, followed by a `## : Post Implementation Expectations` heading. Under this heading, they should provide a bulleted list of observable outcomes or expected changes. * **Discrepancy Resolution Policy:** Any discrepancy found during a task, regardless of its perceived impact or direct relevance to the current task, MUST be explicitly noted, documented, and rectified. No discrepancies, minor or otherwise, shall be overlooked or excluded from the resolution process. * **100% Automated Test Pass Rate Policy:** All automated tests MUST pass successfully with a 100% pass rate. No 'expected skips' or failures are acceptable. Any test that currently skips or fails must either be fixed to pass or removed (with documented reasoning). @@ -77,31 +78,25 @@ That document defines: * **Task Lifecycle:** PMA reviews -> Updates task file -> Assigns next agent. * **Discussion Tasks:** When a discussion between PMA, BA, and Tech Lead becomes workflow-relevant, it should be captured in a normal task file, assigned to the next responsible agent, and tracked under `Active Discussions` in `tasks/current.md` until it resolves into execution, SCR work, clarification, or closure. * **Task Reopening:** If a task that was thought to be complete later needs unresolved discrepancies fixed or minor same-scope changes after implementation, reuse the same task file, move it back into `Active`, and record the reason in the task's `Reopen History` rather than creating a brand new task. -* **Resume Continuity:** When resuming a reopened task, keep the same task file ID. Reuse the same Task tool `task_id` for delegated task work when possible, and for workflow-runner execution reuse both the same Task tool `task_id` and the same Workflow Runner `session_id` when possible, so prior context remains available. +* **Resume Continuity:** When resuming a reopened task, keep the same task file ID. Reuse the same Task tool `task_id` for delegated task work when possible, and for Workflow Runner execution reuse both the same Task tool `task_id` and the same Workflow Runner `session_id` when possible, so prior context remains available. * **Documentation Closure Ownership:** The Product Manager Agent is the final owner of confirming whether product and technical documentation updates were completed or explicitly marked unnecessary before task closure. * **Git Strategy:** PMA remains the final workflow-closure authority. Tech Lead is the default commit authority for direct execution paths, and Workflow Runner may perform the delegated final commit only in explicit full-team complex workflows. * **Authority Matrix:** Follow the canonical authority and output rules in `docs/core/role_contracts.md` for ownership, verification, commit authority, and closure decisions. -* **Commit Message Policy:** Every commit message must use a concise subject line in the format `: ` and must include a brief body explaining exactly what the commit is for. If the commit is associated with a task, include the task ID in the subject. -* **Implementation Evidence Collection:** Every `implementation` task must produce an **Evidence Packet** in `evidences/[feature_task_name]/`. This MUST include: - * `SUMMARY.md`: A brief explanation of what was tested and what the attached files prove. - * `logs/`: Terminal output from verification commands. - * `screenshots/`: Visual proof (mandatory for UI changes). +* **Commit Message Policy:** Every commit message must follow the repository's active commit messaging policy. +* **Implementation Evidence Collection:** Every `implementation` task must produce the verification artifacts required by the repository's testing and evidence policy. * **Atomic Commitment:** A task is only complete when the code AND the "Truth" documentation (`docs/product/`, `docs/architecture/`, etc.) are updated in a single atomic commit. The SCR file is then marked as `Implemented`. * **Batch Integrity:** In delegated workflow mode, the PMA should aim to complete the entire assigned batch. If a single task is blocked, it is isolated in `tasks/blocked/`, and the PMA continues with the rest of the batch if possible. -## 7. Mandatory Documentation Update Matrix +## 7. Repository Documentation Policy -Every task MUST ensure the following files are updated if relevant: +All documentation updates must follow the repository's documentation policy for: -| Document Level | File Path | Responsible Agent | -| :--- | :--- | :--- | -| **Product Overview** | `docs/product/PRODUCT_OVERVIEW.md` | Business Analyst | -| **Features List** | `docs/product/FEATURES_LIST.md` | Business Analyst | -| **Architecture** | `docs/architecture/TECHNICAL_ARCHITECTURE.md` | Technical Architect | -| **Feature Spec** | `docs/features/[feature]/SPECIFICATION.md` | BA & Architect | -| **Tech Guidelines** | `docs/core/technical_guidelines.md` | Tech Lead / Architect | -| **CodeMap** | `codemap.yml` | Developer / Architect | +- where steady-state product and technical truth belongs +- which documents must be updated for a given change +- documentation ownership, naming, and layout conventions - - - + + + + + diff --git a/README.md b/README.md index 1fcf811..671084b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,27 @@ PMA will guide the repository setup flow and, when needed, initialize NomadWorks ## Configure -During setup, PMA can initialize the repository and create `.codenomad/nomadworks.yaml`. NomadWorks reads this file for repository-local defaults, feature flags, and per-agent overrides. +During setup, PMA can initialize the repository and create `.nomadworks/nomadworks.yaml`. NomadWorks reads this file for repository-local defaults, feature flags, policy extraction settings, and per-agent config overrides. + +Repository-local policy overrides live in `.nomadworks/policies/`. If a policy file is not present there, NomadWorks falls back to the bundled plugin default automatically. + +Repository-local full agent definitions can live in `.nomadworks/agents/`. Use this folder to override a bundled agent's base prompt or define a brand new custom repository agent. + +Repository-specific additive agent instructions can live in `.nomadworks/agent-additions/`. + +## Repository Customization + +- `.nomadworks/policies/*.md`: shared repository policy overrides used by multiple agents +- `.nomadworks/agents/.md`: full repository-local agent definition that overrides a bundled agent or defines a new custom agent +- `.nomadworks/agent-additions/.md`: additive repository-specific instructions appended to a bundled or custom agent prompt +- `.nomadworks/generated/agents/`: generated final prompt dumps for inspection when `features.debug_dumps` is enabled +- `.nomadworks/generated/policies/`: generated reference copies of bundled default policies when `policies.extract_defaults` is set to `all` + +`nomadworks_init` also creates README placeholders in these folders so repositories can discover what each folder is for without those README files being treated as agents. + +The scaffolded README files in `.nomadworks/agents/` and `.nomadworks/agent-additions/` also list the common available `plugin:` and `policy:` includes that custom agents can reuse. + +Runtime prompt resolution prefers repository-local policies, agent definitions, and agent additions when present, while keeping the plugin-owned workflow and role model intact by default. NomadWorks supports two team presets: @@ -36,6 +56,7 @@ Quick links: - [Installation](docs/setup/INSTALLATION.md) - [Configuration](docs/setup/CONFIGURATION.md) +- [Releasing](docs/setup/RELEASING.md) - [Workflow Agents](docs/guides/AGENTS.md) - [Workflow Model](docs/guides/WORKFLOW.md) - [Plugin Tools](docs/guides/TOOLS.md) @@ -43,6 +64,16 @@ Quick links: - [Full Team Mode](docs/guides/TEAM_MODE_FULL.md) - [Documentation Structure](docs/core/documentation_structure.md) +## Release + +NomadWorks ships as the npm package `@neuralnomads/nomadworks`. + +- Local verification: `npm run release:check` +- Push to `dev`: auto-publish prerelease (`rc`) +- Push to `main`: auto-publish stable release + +For the full release setup, required secrets, and branch-based versioning behavior, see [Releasing](docs/setup/RELEASING.md). + ## Team Modes | Team Mode | Available Agents | Supported Task Complexity | Flow Guide | @@ -55,7 +86,7 @@ Quick links: The NomadWorks Collective operates like a role-based software development team: - `product_manager` (Product Manager Agent, PMA): Default orchestrator and routing agent. -- `workflow_runner` (Workflow Runner): Delegated executor for complex implementation tasks. +- `workflow_runner` (Workflow Runner): Delegated orchestrator for complex implementation tasks. - `business_analyst` (Business Analyst, BA): Requirements and product-truth steward. - `technical_architect` (Technical Architect): Architecture, interfaces, and impact mapping. - `tech_lead` (Tech Lead): Behavioral verification and technical sign-off. diff --git a/agents/business_analyst.md b/agents/business_analyst.md index 24b730e..bc00557 100644 --- a/agents/business_analyst.md +++ b/agents/business_analyst.md @@ -31,6 +31,6 @@ Critically evaluate the provided task definition. Ensure it contains all necessa * **Logical:** Constructs unambiguous user stories and acceptance criteria. * **Inquisitive:** Proactively identifies gaps and hidden assumptions in task definitions. - - - + + + diff --git a/agents/developer.md b/agents/developer.md index e98c5b2..63804b3 100644 --- a/agents/developer.md +++ b/agents/developer.md @@ -30,6 +30,7 @@ Critically evaluate the task definition. Ensure it has sufficient detail for you * **Consistent:** Adheres strictly to established project patterns and standards. * **Collaborative:** Communicates clearly and works effectively within the orchestrated workflow. - - - + + + + diff --git a/agents/product_manager.md b/agents/product_manager.md index 3c71c00..a2107fb 100644 --- a/agents/product_manager.md +++ b/agents/product_manager.md @@ -40,7 +40,7 @@ You are the Product Manager Agent (PMA). You are the central orchestrator for al - Orchestrate the Post-Task Sync yourself when you retain control of the task lifecycle. - Ensure evidence, documentation closure, finalization updates, final commit, and archiving are completed before closure. * **Delegated Batch Execution:** When the PO triggers a batch of implementation SCRs, execute them sequentially within the shared worktree. Investigation and spec tasks may still run in parallel when they are isolated from the active implementation task. -* **Post-Task Sync & Evidence:** You are the gatekeeper of the **Evidence Packet**. Ensure the Developer/QA has provided a `SUMMARY.md`, logs, and screenshots before calling the specialists for the Post-Task Sync. Instruct each specialist to **introduce themselves and their role** when providing verification feedback. +* **Post-Task Sync & Evidence:** You are the gatekeeper of implementation evidence. Ensure the Developer/QA has provided the verification artifacts required by the repository testing/evidence policy before calling the specialists for the Post-Task Sync. Instruct each specialist to **introduce themselves and their role** when providing verification feedback. * **Bounce Back Protocol:** If an implementation is rejected during the Post-Task Sync, reuse the original Task tool `task_id` when sending it back to the agent. This ensures they have the full execution history of the rejection. * **Formal Reopen Protocol:** If a task was marked done but later needs discrepancies fixed or minor same-scope changes after implementation, move that same task back into `Active`, append a `Reopen History` entry, and continue using the same task file ID. Reuse the same Task tool `task_id` when resuming delegated task work, and when resuming Workflow Runner execution, reuse both the same Task tool `task_id` and the same Workflow Runner `session_id` when possible. * **Commit Authority:** You own final closure in all modes. Tech Lead is the default commit authority for direct execution paths, while Workflow Runner may perform the final commit only when you explicitly delegated a full-team complex workflow to it. @@ -52,7 +52,8 @@ You are the Product Manager Agent (PMA). You are the central orchestrator for al * **Strategic:** Focused on long-term goals and how current decisions contribute to them. * **Decisive:** Able to make clear decisions and drive the product forward. - - - - + + + + + diff --git a/agents/qa_engineer.md b/agents/qa_engineer.md index 5d6af12..6c70524 100644 --- a/agents/qa_engineer.md +++ b/agents/qa_engineer.md @@ -33,6 +33,5 @@ All automated tests MUST pass successfully with a 100% pass rate. No 'expected s * **Analytical:** Interprets results to find the root cause of failures. * **User-Flow Focused:** Always views the system through the eyes of the end-user. - - - + + diff --git a/agents/tech_lead.md b/agents/tech_lead.md index bd25eb3..14e2d72 100644 --- a/agents/tech_lead.md +++ b/agents/tech_lead.md @@ -32,7 +32,9 @@ Critically evaluate the provided task definition. Ensure it contains all necessa * **Mentor-Minded:** Dedicated to leveling up the team and providing clear guidance. * **Decisive:** Able to resolve complex blockers and drive the team forward. - - - - + + + + + + diff --git a/agents/technical_architect.md b/agents/technical_architect.md index 45f4dbb..b1936a1 100644 --- a/agents/technical_architect.md +++ b/agents/technical_architect.md @@ -33,5 +33,6 @@ Critically evaluate the provided task definition. Ensure it contains all necessa * **Visionary:** Able to design robust patterns that anticipate future growth. * **Pragmatic:** Balances technical excellence with practical delivery goals. - - + + + diff --git a/agents/ui_ux_designer.md b/agents/ui_ux_designer.md index 6732009..71795d3 100644 --- a/agents/ui_ux_designer.md +++ b/agents/ui_ux_designer.md @@ -35,5 +35,5 @@ Critically evaluate the provided task definition for design clarity. Identify mi * **Minimalist:** Focused on clean, clutter-free, and intuitive design. * **Aesthetically Sharp:** An expert eye for hierarchy, color, and typography. - - + + diff --git a/agents/workflow_runner.md b/agents/workflow_runner.md index 9c88332..8872c02 100644 --- a/agents/workflow_runner.md +++ b/agents/workflow_runner.md @@ -1,35 +1,93 @@ --- -description: Delegated workflow executor for PMA-started task lifecycles, including implementation, verification, and delegated finalization. +description: Delegated workflow orchestrator for PMA-started complex task lifecycles. Owns task-management execution, specialist handoffs, evidence tracking, finalization, and blocker reporting; does not implement product code directly. mode: subagent tools: nomadworks_validate: true --- -You are the NomadWorks Workflow Runner. Your sole responsibility is to execute the delegated lifecycle of a specific task assigned to you by the Product Manager. You never self-initiate work; you only execute within a PMA-started task lifecycle. - -**Your Mandates:** -1. **Delegated Lifecycle Execution:** You are responsible for executing the delegated lifecycle defined by the task file. For `implementation` tasks this is Pre-Task Sync -> Implementation -> Post-Task Sync -> delegated finalization. For `investigation` and `spec` tasks, complete the requested research or documentation cycle and return the required artifacts to the Product Manager. -2. **Workflow Adherence:** You MUST follow the NomadWorks orchestrated workflow exactly. -3. **Task File as Law:** Read the assigned task file (`tasks/todo/...`) immediately. -4. **Collective Syncing:** Use the `Task` tool to orchestrate specialists (BA, Tech Lead, UI/UX, QA) during syncs. -5. **Evidence Packet:** Generate and verify the Evidence Packet (`SUMMARY.md`, logs, screenshots). -6. **Delegated Finalization Authority:** For `implementation` tasks in the full-team workflow-runner path, you are the delegated finalization executor. Once 100% approved in Post-Task Sync: - * Update the SCR status to `Implemented` in the SCR file and `docs/scrs/current.md`. - * Update all registries (`tasks/current.md` and `tasks/done.md`). - * Move the task folder to `tasks/done/`. - * **Perform the final Git commit** including all code changes, documentation updates, and registry updates in a single atomic commit. -7. **Communication:** At the end of your session, provide a concise summary of the execution outcome for the Product Manager, who remains the final workflow-closure authority. - -**Operational Cycle:** -1. **Initialize:** Read the task file and the `Agents_Common.md`. -2. **Pre-Task Sync:** Orchestrate a synchronous sync-up with specialists to confirm readiness. Reuse your current `task_id` for these calls. -3. **Execution Phase:** Execute the task according to its `track` and `slice`. -4. **Self-Verification:** Run the relevant tests and `nomadworks_validate` when repository changes are involved. -5. **Evidence Collection:** Populate the expected evidence or findings artifacts for the task. -6. **Post-Task Sync:** Orchestrate a synchronous verification session with specialists when required. -7. **Finalize:** For `implementation` tasks, complete delegated finalization and archiving. For `investigation` and `spec` tasks, return a concise final report and any produced artifacts to the PMA. -8. **Resume Awareness:** If PMA later reopens the same task because discrepancies or minor same-scope changes were found after implementation, resume work under the same task file ID, reuse the same Task tool `task_id` for specialist continuity, and reuse the same Workflow Runner `session_id` when possible so the prior execution context remains available. - - - - - +You are the NomadWorks Workflow Runner. You execute one PMA-started workflow lifecycle for one task file. + +You are not the Product Manager and you are not the implementation agent. PMA owns product/workflow closure and provides the task. You own disciplined task-management execution inside the delegated run. + +## Primary Boundary + +- You MUST NOT directly edit product source code, tests, application configuration, or implementation files. +- You MUST delegate implementation to `developer`. +- You MUST delegate verification to `qa_engineer` and `tech_lead`. +- You MAY edit workflow artifacts required to coordinate and close the task: task files, evidence notes, SCR status, task registries, archive/finalization metadata, and commit metadata. + +## Required PMA Inputs + +Before starting execution, verify the task file and PMA instructions include enough task-management context: + +- task path +- objective +- complexity, track, and slice +- assigned owner/current lifecycle phase +- acceptance criteria with AC IDs +- SCR link when required by the task model +- known constraints, dependencies, assumptions, and open questions +- expected evidence requirements +- documentation impact expectations +- commit/finalization expectations + +If required context is missing, stop immediately and return a final response beginning with `HARD BLOCKER:`. List the missing inputs and do not proceed to implementation or specialist delegation. + +## Deterministic Responsibility Matrix + +Use this ownership matrix for every delegated workflow. Do not improvise ownership unless the task file or PMA explicitly overrides it. + +| Phase | Owner | Required Output | +| :--- | :--- | :--- | +| Requirements and AC validation | `business_analyst` | Readiness notes, requirements gaps, AC coverage risks | +| Architecture and impact mapping | `technical_architect` | Technical approach, affected areas, interface/data impacts | +| Implementation | `developer` | Code changes, tests, implementation notes, changed-file summary | +| UI/UX review when relevant | `ui_ux_designer` | UI/UX findings or signoff | +| QA verification | `qa_engineer` | Verification evidence, test results, regression notes | +| Technical signoff | `tech_lead` | Behavioral verification, code quality signoff, bounce-back decision | +| Lifecycle orchestration and finalization | `workflow_runner` | Handoffs, evidence tracking, registry/SCR/archive updates, final report | +| Final closure | `product_manager` | Accepts or rejects runner outcome after plugin relay | + +## Workflow Execution Plan + +After the Task Readiness Check and Pre-Task Sync, write or append this plan to the task file before implementation begins. Update statuses as each step completes. + +| Step | Assigned Agent | Purpose | Expected Output | Status | +| :--- | :--- | :--- | :--- | :--- | +| 1 | `business_analyst` | Validate requirements and acceptance criteria | Readiness notes | pending | +| 2 | `technical_architect` | Confirm technical approach and impact surface | Impact and design notes | pending | +| 3 | `developer` | Implement code and tests | Changed files and test notes | pending | +| 4 | `qa_engineer` | Verify behavior and regression coverage | Evidence and test results | pending | +| 5 | `tech_lead` | Final technical signoff | Approval or bounce-back | pending | +| 6 | `workflow_runner` | Finalize lifecycle | Registries, SCR/archive updates, commit, final report | pending | + +## Operational Cycle + +1. **Task Readiness Check:** Read the full task file and verify Required PMA Inputs are present. +2. **Pre-Task Sync:** Use Task-tool specialist delegation to confirm readiness with the required specialist quorum. +3. **Plan:** Append/update the Workflow Execution Plan in the task file. +4. **Delegate Implementation:** Assign implementation to `developer` with the task file path, AC IDs, constraints, and expected evidence. +5. **Collect Evidence:** Ensure implementation output updates the task file and includes AC traceability. +6. **Delegate Verification:** Assign verification to `qa_engineer` and technical signoff to `tech_lead`; include the same task file path. +7. **Bounce Back If Needed:** If QA or Tech Lead rejects the work, send it back to the correct specialist using the same task context. Do not fix it yourself. +8. **Finalize:** Once approved, update task/SCR registries, run required validation, archive the task, and perform the authorized final commit for full-team complex workflows. +9. **Return Final Summary:** End with a concise PMA-facing report including Summary, Work Performed, AC Coverage, Evidence, Documentation Impact, Commit, Open Risks, and Closure Recommendation. + +## Hard Blocker Mechanism + +If you cannot proceed after reasonable orchestration attempts: + +- Stop further execution. +- End your current run by returning a final summary that starts with `HARD BLOCKER:`. +- Include the exact missing information, failed dependency, rejected evidence, or external issue PMA/user must resolve. +- Do not keep prompting or attempting additional work after declaring a hard blocker. +- Do not attempt to message PMA directly; the plugin relays your final output back to the PMA session. + +## Resume Awareness + +If PMA later reopens the same task because discrepancies or minor same-scope changes were found after implementation, resume work under the same task file ID, reuse the same Task tool `task_id` for specialist continuity, and reuse the same Workflow Runner `session_id` when possible so prior context remains available. + + + + + + diff --git a/docs/core/discussion_agent_guidelines.md b/docs/core/discussion_agent_guidelines.md index 68499bb..a283dcf 100644 --- a/docs/core/discussion_agent_guidelines.md +++ b/docs/core/discussion_agent_guidelines.md @@ -13,6 +13,12 @@ Discussion transcript tools: - `nomadworks_start_discussion(title, previous_message_count)` - `nomadworks_stop_discussion()` +Discussion lifecycle: + +- While a discussion is active, NomadWorks captures the raw transcript in `.nomadworks/runtime/discussions/`. +- When `nomadworks_stop_discussion()` is requested, the tool itself invokes `business_analyst` with a blocking prompt to rewrite the runtime transcript into a structured summary in `tasks/discussions/`. +- The archived workflow-facing summary is the artifact later agents should read. The raw transcript is archived in runtime after summarization. + ## Direct User Discussion - You may speak directly with the user in your area of responsibility. diff --git a/docs/core/pma_mode_full.md b/docs/core/pma_mode_full.md index 1ec72a5..c17ecbb 100644 --- a/docs/core/pma_mode_full.md +++ b/docs/core/pma_mode_full.md @@ -21,5 +21,5 @@ You are operating in **full team mode**. ## Full Team Complex Workflow -- When using `workflow_runner`, treat it as a separate execution session that owns pre-sync, execution, post-task sync, and final reporting. +- When using `workflow_runner`, treat it as a separate execution session that owns task-readiness validation, pre-sync, specialist delegation, post-task sync, finalization, and final reporting. - PMA remains the orchestrator of the overall program of work and reviews the runner's final output before closure. diff --git a/docs/domains/task-lifecycle/OVERVIEW.md b/docs/domains/task-lifecycle/OVERVIEW.md index 83d8965..defe310 100644 --- a/docs/domains/task-lifecycle/OVERVIEW.md +++ b/docs/domains/task-lifecycle/OVERVIEW.md @@ -13,6 +13,7 @@ This domain includes: - task classification by `tiny`, `standard`, and `complex` - task routing by `implementation`, `investigation`, and `spec` - slice-based task planning using `foundation`, `core`, `logic`, `ui`, `polish`, `qa`, and `docs` +- definition-of-ready and definition-of-done expectations for task execution - pre-sync quorum rules - task execution flow and handoff expectations - verification and archiving expectations @@ -61,6 +62,8 @@ This domain does not include: - `docs/core/task_model.md` - `docs/core/agent_orchestration.md` +- `.nomadworks/policies/definition-of-ready.md` +- `.nomadworks/policies/definition-of-done.md` - `tasks/task-template.md` - `tasks/subtask-template.md` - `docs/core/documentation_structure.md` diff --git a/docs/guides/AGENTS.md b/docs/guides/AGENTS.md index 86fcd2d..bbfbb3b 100644 --- a/docs/guides/AGENTS.md +++ b/docs/guides/AGENTS.md @@ -7,7 +7,7 @@ The collective is designed so each agent represents a professional function insi ## Primary orchestration agents - `product_manager`: The default primary agent. Routes work by complexity, delegates specialists, and decides when to use the Workflow Runner. -- `workflow_runner`: Delegated executor for complex implementation tasks. Handles pre-task sync, implementation orchestration, post-task sync, and final reporting inside a PMA-started workflow. +- `workflow_runner`: Delegated orchestrator for complex implementation tasks. Handles task-readiness validation, pre-task sync, specialist delegation, post-task sync, finalization, and final reporting inside a PMA-started workflow. ## Specialist agents @@ -34,6 +34,14 @@ These agents can talk directly with the user and turn meaningful discussions int `mode: all` does not make an agent an orchestrator by default. PMA remains the sole workflow orchestrator. Discussion-capable agents may speak directly with the user, but workflow-relevant work must still be handed back through task files and PMA-owned orchestration. +## Repository Customization + +- `.nomadworks/agents/.md`: full repository-local agent definition. Use this to override a bundled agent's base prompt or define a brand new custom repository agent. +- `.nomadworks/agent-additions/.md`: appends repository-specific instructions to a bundled or custom agent prompt. +- `.nomadworks/policies/*.md`: overrides shared repository policy files used by multiple agents. + +Use shared policies and additive agent files by default. Use full agent definitions in `.nomadworks/agents/` when a repository needs a custom agent or needs to take over an agent's base prompt. + ## Typical usage by task complexity ### Tiny @@ -52,4 +60,4 @@ These agents can talk directly with the user and turn meaningful discussions int - PMA links the task to an approved SCR. - Architect helps decompose the work into slice-based subtasks. -- `workflow_runner` executes the end-to-end delivery cycle. +- `workflow_runner` executes the end-to-end delivery cycle through specialist delegation while PMA waits for completion notification. diff --git a/docs/guides/TEAM_MODE_FULL.md b/docs/guides/TEAM_MODE_FULL.md index e76dfa5..a89f0e5 100644 --- a/docs/guides/TEAM_MODE_FULL.md +++ b/docs/guides/TEAM_MODE_FULL.md @@ -37,7 +37,7 @@ User request ## Full Team Responsibilities - **PMA:** orchestration, routing, final closure -- **Workflow Runner:** separate-session execution for complex workflows +- **Workflow Runner:** separate-session orchestration for complex workflows - **BA:** product truth and acceptance criteria - **Technical Architect:** architecture and decomposition - **Tech Lead:** technical leadership and behavioral verification diff --git a/docs/guides/TOOLS.md b/docs/guides/TOOLS.md index 36f3853..092bfb6 100644 --- a/docs/guides/TOOLS.md +++ b/docs/guides/TOOLS.md @@ -12,13 +12,27 @@ Initializes NomadWorks in the current repository. ### What it creates -- `.codenomad/nomadworks.yaml` +- `.nomadworks/nomadworks.yaml` +- `.nomadworks/policies/README.md` +- `.nomadworks/agents/README.md` +- `.nomadworks/agent-additions/README.md` +- `.nomadworks/generated/agents/README.md` +- `.nomadworks/generated/policies/README.md` - `codemap.yml` - `tasks/current.md` - `tasks/done.md` - `docs/scrs/current.md` - `docs/scrs/done.md` +### Notes + +- Full repository-local agent definitions or custom agents are optional and can be created later under `.nomadworks/agents/`. +- Repository-specific additive agent instructions are optional and can be created later under `.nomadworks/agent-additions/`. +- Generated prompt dumps go to `.nomadworks/generated/agents/` when `features.debug_dumps` is enabled. +- Generated reference policy files go to `.nomadworks/generated/policies/` when `policies.extract_defaults` is set to `all`. +- The scaffolded README files in `.nomadworks/agents/` and `.nomadworks/agent-additions/` list common `plugin:` and `policy:` includes that custom agents can reuse. +- After a successful init, NomadWorks will request the OpenCode instance be disposed so the new config/agents can be reloaded. + ## `nomadworks_validate` Validates NomadWorks workflow artifacts and CodeMap integrity. @@ -51,15 +65,22 @@ Also provide: - Use `0` if the discussion starts now. - Use `existing_discussion_id` plus `previous_message_count` to reopen an older discussion and include a small amount of newer conversation that happened before the reopen call. - Only one active discussion is allowed per session. -- Discussion transcripts are stored in `tasks/discussions/`. -- Active discussion state is persisted in `.codenomad/runtime/discussions.json`. +- While active, raw discussion transcripts are stored in `.nomadworks/runtime/discussions/`. +- The durable workflow artifact is written to `tasks/discussions/` when the discussion is stopped and summarized. +- Active discussion state is persisted in `.nomadworks/runtime/discussions.json`. - Only discussion-capable agents should use these discussion tools. ## `nomadworks_stop_discussion` Stops the automatic discussion transcript for the current session. -The discussion is first marked `closing`, the current assistant reply is captured, and then the file is marked `closed`. +This tool performs the full close flow synchronously: + +- marks the runtime transcript as summarizing +- invokes `business_analyst` with a blocking prompt to write the structured summary to `tasks/discussions/` +- verifies the summary file was written successfully +- archives the raw runtime transcript +- returns the final closed result from the tool call itself ## `nomadflow_run_workflow` @@ -75,6 +96,10 @@ Starts a `workflow_runner` session for a complex task. - Only available in `full` team mode. - Used for `complex` implementation tasks. - The runner executes in a separate session and reports completion back to PMA. +- The runner is expected to orchestrate the lifecycle by validating task readiness, delegating implementation and verification work to specialists, and driving the task to delivery or a hard blocker. +- For implementation tasks, the runner must create or append a Workflow Execution Plan in the task file after Pre-Task Sync and before implementation starts. +- The runner must not directly edit product source code, tests, application configuration, or implementation files. +- When a hard blocker is reached, the runner should end its run and return a final summary starting with `HARD BLOCKER:` so the plugin relays it back to the PMA session. ## `nomadflow_prompt_workflow` diff --git a/docs/product/DOMAIN_MAP.md b/docs/product/DOMAIN_MAP.md index b4d1f99..7277820 100644 --- a/docs/product/DOMAIN_MAP.md +++ b/docs/product/DOMAIN_MAP.md @@ -37,5 +37,5 @@ This document maps the major product domains and the features that belong to the ### Plugin Setup And Configuration - **Purpose:** Defines how NomadWorks is installed, configured, and enabled in an OpenCode environment. -- **Owned Features:** plugin installation, OpenCode config wiring, `nomadworks.yaml`, agent overrides. +- **Owned Features:** plugin installation, OpenCode config wiring, `nomadworks.yaml`, repo-local agent definitions, agent additions, policy overrides. - **Primary Docs:** `docs/setup/INSTALLATION.md`, `docs/setup/CONFIGURATION.md` diff --git a/docs/setup/CONFIGURATION.md b/docs/setup/CONFIGURATION.md index 94f172b..88da84f 100644 --- a/docs/setup/CONFIGURATION.md +++ b/docs/setup/CONFIGURATION.md @@ -1,6 +1,6 @@ # Configuration -NomadWorks reads repository-local configuration from `.codenomad/nomadworks.yaml`. +NomadWorks reads repository-local configuration from `.nomadworks/nomadworks.yaml`. This file is typically created during the PMA-led repository setup flow. @@ -18,6 +18,9 @@ features: debug_dumps: true codemap_verification: true +policies: + extract_defaults: none + agents: product_manager: enabled: true @@ -29,7 +32,8 @@ agents: - `team_mode`: The supported team preset. Use `mini` for PMA + BA + Tech Lead only, or `full` for the complete collective. If omitted in an existing repository, NomadWorks defaults to `full`. - `defaults`: Shared defaults for providers, models, permissions, and other agent config fields. - `features`: Plugin feature flags such as debug dumps and validation behavior. -- `agents`: Per-agent enablement and overrides. +- `policies`: Policy extraction controls for generated reference policy files. +- `agents`: Per-agent enablement and config overrides from `nomadworks.yaml`. ## Supported team modes @@ -79,8 +83,35 @@ agents: - nomadworks_validate ``` +### Generate bundled policy references + +```yaml +policies: + extract_defaults: all +``` + +This writes the bundled default policy files to `.nomadworks/generated/policies/` for reference. Those generated files are not used directly at runtime. To customize one, copy it into `.nomadworks/policies/` and edit the copy. + +### Add repository-specific agent instructions + +Create `.nomadworks/agent-additions/.md` to append repository-specific instructions to a bundled or custom agent prompt. + +### Add repository-local agents or override a bundled base prompt + +Create `.nomadworks/agents/.md` to: + +- replace the bundled base prompt for an existing agent, or +- define a brand new custom repository agent + ## Operational notes - The `product_manager` agent becomes the default primary agent when NomadWorks is enabled. -- Repository-local agent markdown overrides can live in `.codenomad/nomadworks/agents/`. -- Final agent prompts are dumped to `.nomadworks/agents/` when `features.debug_dumps` is enabled. +- Repository-local full agent definitions can live in `.nomadworks/agents/`. +- Repository-local additive agent instructions can live in `.nomadworks/agent-additions/`. +- Repository-local policy overrides can live in `.nomadworks/policies/`. +- Generated reference policy files are written to `.nomadworks/generated/policies/` when `policies.extract_defaults` is set to `all`. +- Final agent prompts are dumped to `.nomadworks/generated/agents/` when `features.debug_dumps` is enabled. + +## Feature flags + +- `features.keep_builtin_agents`: when `true`, NomadWorks will not disable agents that OpenCode already registered, including built-in agents such as `build`, `plan`, `general`, and `explore`. NomadWorks will still set `product_manager` as the default agent. diff --git a/docs/setup/INSTALLATION.md b/docs/setup/INSTALLATION.md index ef31c6c..4206870 100644 --- a/docs/setup/INSTALLATION.md +++ b/docs/setup/INSTALLATION.md @@ -42,7 +42,12 @@ You do not need to manually run NomadWorks initialization commands as a first st When PMA initializes the repository, NomadWorks creates: -- `.codenomad/nomadworks.yaml` +- `.nomadworks/nomadworks.yaml` +- `.nomadworks/policies/README.md` +- `.nomadworks/agents/README.md` +- `.nomadworks/agent-additions/README.md` +- `.nomadworks/generated/agents/README.md` +- `.nomadworks/generated/policies/README.md` - `codemap.yml` - `tasks/current.md` - `tasks/done.md` @@ -51,7 +56,7 @@ When PMA initializes the repository, NomadWorks creates: ## 5. Configure NomadWorks -Edit `.codenomad/nomadworks.yaml` to set defaults, features, and per-agent overrides. +Edit `.nomadworks/nomadworks.yaml` to set defaults, features, policy extraction behavior, and per-agent config overrides. Use `.nomadworks/agents/` for full repository-local agent definitions or custom agents, and `.nomadworks/agent-additions/` for additive repo-specific agent instructions. See: diff --git a/docs/setup/RELEASING.md b/docs/setup/RELEASING.md new file mode 100644 index 0000000..5351670 --- /dev/null +++ b/docs/setup/RELEASING.md @@ -0,0 +1,101 @@ +# Releasing + +NomadWorks publishes to npm as `@neuralnomads/nomadworks`. + +## Release Model + +- Versioning, build verification, and npm publishing are handled by the GitHub Actions workflow `Release npm package`. +- Pushes to `dev` automatically publish npm prereleases. +- Pushes to `main` automatically publish stable npm releases. +- GitHub Actions publishes through npm Trusted Publishing with provenance enabled. +- The workflow does not commit or tag version changes back to the repository. It derives the publish version from `package.json` and npm's already-published versions. + +## Trusted Publishing Setup + +Configure npm Trusted Publishing for this GitHub repository before relying on CI publishes. + +At a minimum, npm must trust this repository's GitHub Actions workflow as a publisher for `@neuralnomads/nomadworks`. + +Expected setup: + +1. Open the npm package settings for `@neuralnomads/nomadworks`. +2. Configure a Trusted Publisher for this GitHub repository. +3. Allow GitHub Actions from this repository to publish the package. + +Critical npm-side details: + +- GitHub organization or user: `NeuralNomadsAI` +- Repository: `NomadWorks` +- Workflow filename: `release.yml` +- If you use an optional environment in npm's Trusted Publisher settings, it must exactly match the GitHub Actions environment name used by the workflow. +- `package.json` must include a `repository.url` that exactly matches `https://github.com/NeuralNomadsAI/NomadWorks`. +- GitHub Actions must run on a supported Node.js version for npm Trusted Publishing. This workflow uses Node.js `24`. + +No `NPM_TOKEN` repository secret is required once Trusted Publishing is configured correctly. + +## Workflow Behavior + +The release workflow performs these steps: + +1. Triggers automatically on pushes to `dev` and `main`. +2. Installs dependencies with `npm ci`. +3. Runs `npm run release:check`, which executes tests, builds `dist/`, and previews the publish tarball. +4. Resolves the publish version based on the current branch and npm registry history. +5. Applies that version locally with `npm version --no-git-tag-version`. +6. Publishes the package with `npm publish --provenance` using npm Trusted Publishing. + +## Branch Behavior + +### `dev` + +- Publishes prereleases using the `rc` dist-tag. +- Reads the stable base version from `package.json`. +- Looks up already published versions on npm. +- Publishes the next version in the sequence: `-rc.N` + +Example: + +- `package.json`: `1.4.0` +- published prereleases: `1.4.0-rc.0`, `1.4.0-rc.1` +- next `dev` publish: `1.4.0-rc.2` + +### `main` + +- Publishes stable releases using the default `latest` dist-tag. +- Publishes the exact stable version in `package.json`. +- Skips publishing if that version already exists on npm. + +Example: + +- `package.json`: `1.4.0` +- push to `main` +- publish: `1.4.0` + +## Versioning Expectations + +- Keep `package.json` on a stable semver base such as `1.4.0`. +- Do not commit prerelease versions like `1.4.0-rc.2` into `package.json`. +- Use `dev` to publish release candidates for the current base version. +- When the package is ready, merge the versioned changes to `main` to publish the stable release. + +## Local Verification + +Before triggering a release, you can run the same verification locally: + +```bash +npm run release:check +``` + +This command: + +- runs the test suite +- builds `dist/` +- runs `npm pack --dry-run` to preview the package contents + +## Notes + +- `prepack` runs `npm run build`, so local `npm pack` and `npm publish` always include a fresh `dist/` build. +- `publishConfig.access` is set to `public` so the scoped package can publish correctly on npm. +- The prerelease counter is remembered via npm registry history, not via git tags or committed prerelease versions. +- CI publishing depends on npm Trusted Publishing plus the workflow permission `id-token: write`. +- If you need to dry-run a release locally without publishing, use `npm run release:check` and inspect the tarball preview output. diff --git a/package.json b/package.json index 761c56a..cbb6dd6 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,26 @@ { "name": "@neuralnomads/nomadworks", "version": "0.1.0", + "repository": { + "type": "git", + "url": "https://github.com/NeuralNomadsAI/NomadWorks" + }, "type": "module", "main": "dist/index.js", "scripts": { "build": "node scripts/build.js", + "prepack": "npm run build", + "release:check": "npm test && npm run build && npm pack --dry-run", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" }, + "publishConfig": { + "access": "public" + }, "files": [ "dist", "agents", "docs", + "policies", "templates", "Agents_Common.md" ], diff --git a/policies/README.md b/policies/README.md new file mode 100644 index 0000000..9adc62c --- /dev/null +++ b/policies/README.md @@ -0,0 +1,62 @@ +# NomadWorks Policies + +NomadWorks keeps core workflow behavior in the plugin and lets repositories override opinionated delivery policies here. + +## How Policy Resolution Works + +For any `.md>` include, NomadWorks resolves policy files in this order: + +1. `.nomadworks/policies/.md` +2. bundled plugin default `policies/.md` + +Files under `.nomadworks/generated/policies/` are reference copies only. They are not read directly at runtime. + +## Available Policies + +- `development-guidelines.md` + - Repository-specific engineering rules, stack notes, and implementation conventions. + - Used by: `developer`, `technical_architect`, `tech_lead`, `workflow_runner` + +- `testing-guidelines.md` + - Testing, evidence, regression, and verification conventions. + - Used by: `developer`, `qa_engineer`, `tech_lead`, `workflow_runner` + +- `documentation-guidelines.md` + - Documentation layout, naming, ownership, and update expectations. + - Used by all agents through the shared prompt. + +- `definition-of-ready.md` + - Canonical readiness criteria before execution begins. + - Used by all agents through the shared prompt and reflected in task templates. + +- `definition-of-done.md` + - Canonical completion criteria before closure. + - Used by all agents through the shared prompt and reflected in task templates. + +- `git-commit-messaging.md` + - Commit subject and body rules. + - Used by: `tech_lead`, `workflow_runner` + +- `product-guidelines.md` + - User story, acceptance criteria, terminology, and product-truth conventions. + - Used by: `product_manager`, `business_analyst` + +- `ui-ux-guidelines.md` + - UI review standards and visual quality expectations. + - Used by: `ui_ux_designer` + +## Customizing A Policy + +1. Set `.nomadworks/nomadworks.yaml` `policies.extract_defaults` to `all` if you want reference copies of all bundled defaults. +2. Inspect `.nomadworks/generated/policies/` for the default files. +3. Copy the policy you want to customize into `.nomadworks/policies/`. +4. Edit the copied file. The repo-local version will override the plugin default automatically. + +## Policy Extraction + +`policies.extract_defaults` supports: + +- `none`: do not generate reference policy files +- `all`: write all bundled default policy files to `.nomadworks/generated/policies/` + +Only files in `.nomadworks/policies/` affect runtime prompt behavior. diff --git a/policies/definition-of-done.md b/policies/definition-of-done.md new file mode 100644 index 0000000..efd033b --- /dev/null +++ b/policies/definition-of-done.md @@ -0,0 +1,26 @@ +# Definition Of Done + +A task is done only when the implementation, verification, documentation, and workflow closure requirements are all complete. + +## Completion Criteria + +- All in-scope acceptance criteria are satisfied or explicitly marked blocked with documented reason. +- Required tests, builds, and other verification commands pass according to the repository testing policy. +- Required evidence and verification artifacts are recorded. +- Product and technical documentation impact is resolved according to the repository documentation policy. +- Relevant CodeMap updates are completed when the changed code affects entrypoints, wiring, or maintained source structure. +- Task files, discussion references, and workflow registries are updated as needed. +- The authorized review and closure roles have completed their required checks. +- The final committed state includes all required code, documentation, and registry updates for closure. + +## Not Done Conditions + +- Any required test or build fails. +- Evidence is missing for claimed verification. +- Documentation or CodeMap impact remains unresolved. +- Acceptance criteria are incomplete, unclear, or unverified. +- Required finalization or archiving steps are missing. + +## Operational Rule + +A task must not be marked complete while any Definition of Done item remains open. diff --git a/policies/definition-of-ready.md b/policies/definition-of-ready.md new file mode 100644 index 0000000..fe0d324 --- /dev/null +++ b/policies/definition-of-ready.md @@ -0,0 +1,27 @@ +# Definition Of Ready + +A task is ready to begin only when the repository has enough information to execute safely and efficiently without inventing scope. + +## Readiness Criteria + +- Scope is clear, bounded, and appropriate for the task's declared complexity. +- The task objective is specific enough that the next responsible agent can act without guessing intent. +- Acceptance criteria are present, testable, and aligned with the stated scope. +- Complexity, track, and slice are set correctly for the work being requested. +- Required dependencies, assumptions, blockers, and open questions are either resolved or explicitly recorded. +- Required pre-sync specialists have reviewed the task definition according to the active task model. +- An approved SCR exists whenever the workflow requires one. +- The relevant repository areas are identified well enough to begin safe investigation, design, or implementation. + +## Not Ready Conditions + +- Requirements are ambiguous or contradictory. +- Acceptance criteria are missing or too vague to verify. +- The task is larger or riskier than its current routing metadata suggests. +- Required specialist review has not happened yet. +- A required SCR is missing or not approved. +- Critical blockers or dependencies are unknown or unrecorded. + +## Operational Rule + +If the task fails the Definition of Ready, execution should pause until the missing information is resolved or explicitly recorded for follow-up. diff --git a/policies/development-guidelines.md b/policies/development-guidelines.md new file mode 100644 index 0000000..b72d6e2 --- /dev/null +++ b/policies/development-guidelines.md @@ -0,0 +1,21 @@ +# Development Guidelines + +These defaults are intended to be customized per repository when needed. + +## Stack Notes + +- Language: define in the repository if needed. +- Runtime / Framework: define in the repository if needed. +- Frontend stack: define in the repository if needed. +- Testing stack: define in the repository if needed. +- Database / storage: define in the repository if needed. + +## Default Engineering Conventions + +- Prefer clear module or feature boundaries over ad-hoc file placement. +- Keep external integrations behind stable interfaces or wrappers when practical. +- Update `.gitignore` when repository changes introduce generated, temporary, or sensitive files. +- Prefer stable dependency versions unless repository compatibility requires otherwise. +- Use dependency-provided setup or initialization utilities when they are the standard way to integrate the dependency safely. +- Document meaningful architecture changes in the repository's documentation before or alongside implementation. +- Keep code changes aligned with existing repository conventions unless the repository policy explicitly changes them. diff --git a/policies/documentation-guidelines.md b/policies/documentation-guidelines.md new file mode 100644 index 0000000..23d84ad --- /dev/null +++ b/policies/documentation-guidelines.md @@ -0,0 +1,39 @@ +# Documentation Guidelines + +## Documentation Goals + +- Keep documentation easy to locate and update. +- Separate steady-state truth from change proposals and workflow records. +- Update documentation in the same change set as the implementation whenever the documented truth changes. + +## Default Documentation Layout + +- `docs/product/`: whole-product truth and top-level feature inventory +- `docs/domains/`: stable product-area truth shared by multiple features +- `docs/features/`: one concrete capability or feature specification +- `docs/architecture/`: technical design, contracts, and cross-cutting decisions +- `docs/scrs/`: proposed and approved changes, not steady-state truth + +## Update Expectations + +Update the relevant documentation when work changes: + +- product behavior, terminology, or feature inventory +- architecture, interfaces, or technical invariants +- feature specifications or acceptance criteria +- documentation ownership, naming, or structure conventions + +## Default Ownership + +- Business Analyst: product, domain, and feature truth from the product perspective +- Technical Architect: architecture truth and technical design documentation +- Product Manager: verifies documentation closure during workflow execution +- Developer / Tech Lead / QA: contribute technical accuracy when implementation changes documented truth + +## Default Repository Matrix + +- Product overview: `docs/product/PRODUCT_OVERVIEW.md` +- Features list: `docs/product/FEATURES_LIST.md` +- Architecture: `docs/architecture/TECHNICAL_ARCHITECTURE.md` +- Feature specification: `docs/features//SPECIFICATION.md` +- CodeMap updates: relevant `codemap.yml` files for changed code areas diff --git a/policies/git-commit-messaging.md b/policies/git-commit-messaging.md new file mode 100644 index 0000000..bcdfe23 --- /dev/null +++ b/policies/git-commit-messaging.md @@ -0,0 +1,14 @@ +# Git Commit Messaging + +Use a concise subject line in this format: + +`: ` + +Examples: + +- `docs: update workflow guidance` +- `fix: TASK-014 correct task archive logic` + +Always include a brief body that explains what the commit is for and why the change exists. + +If the commit is associated with a task, include the task ID in the subject when practical. diff --git a/policies/product-guidelines.md b/policies/product-guidelines.md new file mode 100644 index 0000000..4c65289 --- /dev/null +++ b/policies/product-guidelines.md @@ -0,0 +1,20 @@ +# Product Guidelines + +## Product Writing Defaults + +- Write user stories and requirements in clear, unambiguous language. +- Keep acceptance criteria specific, testable, and easy to map to verification evidence. +- Use numbered acceptance criteria (`AC-1`, `AC-2`, ...) for tracked work. +- Maintain consistent product terminology across SCRs, tasks, and steady-state docs. + +## User Story And Acceptance Criteria Conventions + +- User stories may use the format: `As a , I want , so that .` +- Acceptance criteria should describe observable behavior or outcomes rather than implementation details. +- When requirements are incomplete or ambiguous, stop and push for clarification instead of inventing scope. + +## Product Truth Stewardship + +- Keep product documentation cross-linked and internally consistent. +- When behavior changes, update the relevant product-facing docs and SCR registries. +- If the repository establishes domain or feature naming conventions, apply them consistently. diff --git a/policies/testing-guidelines.md b/policies/testing-guidelines.md new file mode 100644 index 0000000..a384a83 --- /dev/null +++ b/policies/testing-guidelines.md @@ -0,0 +1,29 @@ +# Testing Guidelines + +## Test Levels + +1. Unit tests verify isolated logic, functions, and classes. +2. Integration tests verify interactions between multiple modules or external services. +3. End-to-end tests verify real user or system flows through the product. +4. Manual verification is allowed for visual or interaction checks that cannot be automated effectively. + +## Verification Policy + +- All automated tests must pass. No expected skips or tolerated failures are allowed by default. +- Tests should live close to the code they verify unless the repository uses a clearly defined alternative structure. +- Every `implementation` task must produce the verification artifacts needed for review. +- Verification artifacts should map back to the task's numbered acceptance criteria. +- Run the relevant regression coverage before handing implementation back for technical review. + +## Evidence Defaults + +By default, implementation evidence should include: + +- a short summary of what was verified +- command output or logs for relevant automated checks +- screenshots for UI changes or visual reviews + +## Non-Implementation Outputs + +- `investigation` tasks should produce findings, reproduction notes, useful logs, and a recommended next step. +- `spec` tasks should produce SCR or documentation updates that define the accepted change and its impact. diff --git a/policies/ui-ux-guidelines.md b/policies/ui-ux-guidelines.md new file mode 100644 index 0000000..15d3b94 --- /dev/null +++ b/policies/ui-ux-guidelines.md @@ -0,0 +1,33 @@ +# UI/UX Guidelines + +## Core Principles + +1. Prioritize ease of use, accessibility, and intuitive navigation. +2. Aim for a modern, clean, and polished visual design. +3. Keep UI elements visually consistent with the repository's design language. +4. Use layout, color, and typography to create clear visual hierarchy. + +## Review Workflow + +- Define the intended screens, interactions, and layout before implementation when UI work is involved. +- Review screenshots and other visual evidence from the task's evidence artifacts after implementation. +- Evaluate the result visually rather than by reading code. +- If the available evidence is insufficient, say so clearly and ask for better screenshots or artifacts. + +## Visual Quality Checklist + +Reject or request fixes when you see: + +- obvious misalignment against the page or component grid +- inconsistent spacing between similar elements +- weak typography hierarchy that makes the screen hard to scan +- interactive elements that do not look interactive +- low-contrast text or other readability issues +- cluttered, dated, or visibly unpolished presentation + +## Required Fix Triggers + +- overlapping UI or clipped text +- missing key interaction steps that were part of the intended flow +- ignored design system conventions for color, typography, or spacing +- an overall result that feels amateur or not ready for users diff --git a/scripts/resolve-release-version.js b/scripts/resolve-release-version.js new file mode 100644 index 0000000..9b4856c --- /dev/null +++ b/scripts/resolve-release-version.js @@ -0,0 +1,99 @@ +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const packageJson = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")); + +const branch = process.argv[2]; +const packageName = packageJson.name; +const baseVersion = packageJson.version; + +if (!branch) { + throw new Error("Branch name is required."); +} + +function fail(message) { + throw new Error(message); +} + +function isStableSemver(version) { + return /^\d+\.\d+\.\d+$/.test(version); +} + +function shellValue(value) { + return String(value).replace(/\r/g, " ").replace(/\n/g, " "); +} + +function loadPublishedVersions(name) { + try { + const raw = execFileSync("npm", ["view", name, "versions", "--json"], { + cwd: ROOT, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"] + }).trim(); + + if (!raw) return []; + + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) return parsed; + if (typeof parsed === "string") return [parsed]; + return []; + } catch { + return []; + } +} + +const publishedVersions = loadPublishedVersions(packageName); + +let version = baseVersion; +let publishTag = "latest"; +let channel = "release"; +let shouldPublish = true; +let reason = "stable release from main"; + +if (branch === "dev") { + if (!isStableSemver(baseVersion)) { + fail(`dev prereleases require package.json version to be a stable base semver. Found '${baseVersion}'.`); + } + + const prefix = `${baseVersion}-rc.`; + const rcNumbers = publishedVersions + .filter(candidate => candidate.startsWith(prefix)) + .map(candidate => Number(candidate.slice(prefix.length))) + .filter(Number.isInteger) + .filter(candidate => candidate >= 0); + + const nextRc = rcNumbers.length === 0 ? 0 : Math.max(...rcNumbers) + 1; + version = `${baseVersion}-rc.${nextRc}`; + publishTag = "rc"; + channel = "prerelease"; + reason = rcNumbers.length === 0 + ? `first rc publish for ${baseVersion}` + : `incremented rc from ${Math.max(...rcNumbers)} to ${nextRc}`; +} else if (branch === "main") { + if (!isStableSemver(baseVersion)) { + fail(`main releases require package.json version to be stable semver. Found '${baseVersion}'.`); + } + + if (publishedVersions.includes(baseVersion)) { + shouldPublish = false; + reason = `version ${baseVersion} is already published on npm`; + } +} else { + fail(`Unsupported branch '${branch}'. Expected 'dev' or 'main'.`); +} + +const outputs = { + package_name: packageName, + version, + publish_tag: publishTag, + channel, + should_publish: shouldPublish, + reason +}; + +for (const [key, value] of Object.entries(outputs)) { + console.log(`${key}=${shellValue(value)}`); +} diff --git a/src/index.js b/src/index.js index cb57251..6b51122 100644 --- a/src/index.js +++ b/src/index.js @@ -9,34 +9,143 @@ import { nomadworks_validate_logic } from "./validate_logic.js"; const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const BUNDLE_AGENTS_DIR = path.join(PKG_ROOT, "agents"); +const BUNDLE_POLICIES_DIR = path.join(PKG_ROOT, "policies"); const TEMPLATES_DIR = path.join(PKG_ROOT, "templates"); const MANDATORY_AGENTS = new Set(["product_manager", "business_analyst", "tech_lead"]); const MINI_MODE_AGENTS = new Set(["product_manager", "business_analyst", "tech_lead"]); const DISCUSSION_BACKFILL_FETCH_LIMIT = 100; +const NOMADWORKS_DIRNAME = ".nomadworks"; +const LEGACY_NOMADWORKS_DIRNAME = ".codenomad"; const activeWorkflows = new Map(); // sessionId -> { pmaSessionId, taskPath, track } +function nomadworksDir(worktree) { + return path.join(worktree, NOMADWORKS_DIRNAME); +} + +function legacyNomadworksDir(worktree) { + return path.join(worktree, LEGACY_NOMADWORKS_DIRNAME); +} + +function repoConfigPath(worktree) { + return path.join(nomadworksDir(worktree), "nomadworks.yaml"); +} + +function legacyRepoConfigPath(worktree) { + return path.join(legacyNomadworksDir(worktree), "nomadworks.yaml"); +} + +function repoPoliciesDir(worktree) { + return path.join(nomadworksDir(worktree), "policies"); +} + +function generatedPoliciesDir(worktree) { + return path.join(nomadworksDir(worktree), "generated", "policies"); +} + +function generatedAgentsDir(worktree) { + return path.join(nomadworksDir(worktree), "generated", "agents"); +} + +function repoAgentsDir(worktree) { + return path.join(nomadworksDir(worktree), "agents"); +} + +function repoAgentAdditionsDir(worktree) { + return path.join(nomadworksDir(worktree), "agent-additions"); +} + +function legacyRepoAgentsDir(worktree) { + return path.join(legacyNomadworksDir(worktree), "nomadworks", "agents"); +} + +function runtimeDiscussionRegistryPath(worktree) { + return path.join(nomadworksDir(worktree), "runtime", "discussions.json"); +} + +function legacyDiscussionRegistryPath(worktree) { + return path.join(legacyNomadworksDir(worktree), "runtime", "discussions.json"); +} + +function resolveConfigPath(worktree) { + const repoPath = repoConfigPath(worktree); + if (fs.existsSync(repoPath)) return repoPath; + + const legacyPath = legacyRepoConfigPath(worktree); + if (fs.existsSync(legacyPath)) return legacyPath; + + return repoPath; +} + +function listMarkdownFiles(dirPath) { + if (!fs.existsSync(dirPath)) return []; + + try { + return fs.readdirSync(dirPath) + .filter(file => file.endsWith(".md") && file.toLowerCase() !== "readme.md"); + } catch (e) { + console.error(`[NomadWorks] Failed to read markdown files from ${dirPath}:`, e); + return []; + } +} + +function normalizePolicyExtraction(value) { + if (typeof value !== "string") return "none"; + return value.trim().toLowerCase() === "all" ? "all" : "none"; +} + +function resolveIncludeFile(includeRef, repoRoot, bundleRoot) { + const trimmed = includeRef.trim(); + const scopedMatch = trimmed.match(/^([a-z]+):(.*)$/i); + const scope = scopedMatch?.[1]?.toLowerCase(); + const target = scopedMatch ? scopedMatch[2].trim() : trimmed; + + const resolveRelative = (baseDir, relativePath) => { + if (!relativePath) return null; + return path.isAbsolute(relativePath) ? relativePath : path.join(baseDir, relativePath); + }; + + if (scope === "plugin") { + const filePath = resolveRelative(bundleRoot, target); + return fs.existsSync(filePath) ? filePath : null; + } + + if (scope === "repo") { + const filePath = resolveRelative(nomadworksDir(repoRoot), target); + return fs.existsSync(filePath) ? filePath : null; + } + + if (scope === "policy") { + const repoPolicyPath = resolveRelative(repoPoliciesDir(repoRoot), target); + if (repoPolicyPath && fs.existsSync(repoPolicyPath)) return repoPolicyPath; + + const bundledPolicyPath = resolveRelative(BUNDLE_POLICIES_DIR, target); + return bundledPolicyPath && fs.existsSync(bundledPolicyPath) ? bundledPolicyPath : null; + } + + const repoPath = resolveRelative(repoRoot, target); + if (repoPath && fs.existsSync(repoPath)) return repoPath; + + const bundlePath = resolveRelative(bundleRoot, target); + return bundlePath && fs.existsSync(bundlePath) ? bundlePath : null; +} + /** - * Resolves markers recursively. - * Checks repo (worktree) first, then falls back to bundle root. + * Resolves markers recursively. + * Supported forms: + * - (legacy: repo root first, then plugin bundle) + * - + * - + * - (.nomadworks/policies first, then bundled defaults) */ function resolveIncludes(text, repoRoot, bundleRoot) { const includeRegex = //g; - return text.replace(includeRegex, (match, filename) => { - // Check repo first, then bundle - const repoPath = path.isAbsolute(filename) ? filename : path.join(repoRoot, filename); - const bundlePath = path.isAbsolute(filename) ? filename : path.join(bundleRoot, filename); - - let filePath = null; - if (fs.existsSync(repoPath)) { - filePath = repoPath; - } else if (fs.existsSync(bundlePath)) { - filePath = bundlePath; - } + return text.replace(includeRegex, (match, includeRef) => { + const filePath = resolveIncludeFile(includeRef, repoRoot, bundleRoot); if (!filePath) { - console.warn(`[NomadWorks] Include file not found: ${filename}`); - return `\n\n# ERROR: Include file not found: ${filename}\n\n`; + console.warn(`[NomadWorks] Include file not found: ${includeRef}`); + return `\n\n# ERROR: Include file not found: ${includeRef}\n\n`; } const content = fs.readFileSync(filePath, "utf8"); @@ -117,12 +226,10 @@ function slugifyTitle(input) { .slice(0, 80) || "discussion"; } -function discussionRegistryPath(worktree) { - return path.join(worktree, ".codenomad", "runtime", "discussions.json"); -} - function loadDiscussionRegistry(worktree) { - const registryPath = discussionRegistryPath(worktree); + const registryPath = fs.existsSync(runtimeDiscussionRegistryPath(worktree)) + ? runtimeDiscussionRegistryPath(worktree) + : legacyDiscussionRegistryPath(worktree); if (!fs.existsSync(registryPath)) { return { version: 1, active: {} }; } @@ -138,41 +245,71 @@ function loadDiscussionRegistry(worktree) { } function saveDiscussionRegistry(worktree, registry) { - const registryPath = discussionRegistryPath(worktree); + const registryPath = runtimeDiscussionRegistryPath(worktree); const runtimeDir = path.dirname(registryPath); if (!fs.existsSync(runtimeDir)) fs.mkdirSync(runtimeDir, { recursive: true }); fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf8"); } +function runtimeDiscussionsDir(worktree) { + return path.join(nomadworksDir(worktree), "runtime", "discussions"); +} + +function archivedRuntimeDiscussionsDir(worktree) { + return path.join(runtimeDiscussionsDir(worktree), "archive"); +} + +function finalDiscussionsDir(worktree) { + return path.join(worktree, "tasks", "discussions"); +} + function nextDiscussionIdentity(worktree, title) { - const discussionsDir = path.join(worktree, "tasks", "discussions"); + const discussionsDir = finalDiscussionsDir(worktree); + const runtimeDir = runtimeDiscussionsDir(worktree); if (!fs.existsSync(discussionsDir)) fs.mkdirSync(discussionsDir, { recursive: true }); + if (!fs.existsSync(runtimeDir)) fs.mkdirSync(runtimeDir, { recursive: true }); let sequence = 1; while (true) { const id = `DISCUSSION-${String(sequence).padStart(3, "0")}`; const filename = `${id}-${slugifyTitle(title)}.md`; - const relativePath = path.join("tasks", "discussions", filename); - const absolutePath = path.join(worktree, relativePath); - if (!fs.existsSync(absolutePath)) { - return { id, filename, relativePath, absolutePath }; + const summaryRelativePath = path.join("tasks", "discussions", filename); + const summaryAbsolutePath = path.join(worktree, summaryRelativePath); + const transcriptFilename = `${id}-transcript.md`; + const transcriptRelativePath = path.join(".nomadworks", "runtime", "discussions", transcriptFilename); + const transcriptAbsolutePath = path.join(worktree, transcriptRelativePath); + if (!fs.existsSync(summaryAbsolutePath) && !fs.existsSync(transcriptAbsolutePath)) { + return { + id, + filename, + summaryRelativePath, + summaryAbsolutePath, + transcriptFilename, + transcriptRelativePath, + transcriptAbsolutePath + }; } sequence += 1; } } function findDiscussionById(worktree, discussionID) { - const discussionsDir = path.join(worktree, "tasks", "discussions"); + const discussionsDir = finalDiscussionsDir(worktree); if (!fs.existsSync(discussionsDir)) return null; const entries = fs.readdirSync(discussionsDir).filter(name => name.startsWith(`${discussionID}-`) && name.endsWith(".md")); if (entries.length === 0) return null; const filename = entries.sort()[0]; + const transcriptFilename = `${discussionID}-transcript.md`; return { + id: discussionID, filename, - relativePath: path.join("tasks", "discussions", filename), - absolutePath: path.join(discussionsDir, filename) + summaryRelativePath: path.join("tasks", "discussions", filename), + summaryAbsolutePath: path.join(discussionsDir, filename), + transcriptFilename, + transcriptRelativePath: path.join(".nomadworks", "runtime", "discussions", transcriptFilename), + transcriptAbsolutePath: path.join(runtimeDiscussionsDir(worktree), transcriptFilename) }; } @@ -249,12 +386,167 @@ async function appendMessageIfNeeded(client, worktree, registry, sessionID, mess const text = extractTextParts(response.data.parts || []); if (!text) return; - appendDiscussionMessage(path.join(worktree, discussion.filePath), speaker, text, messageID); + appendDiscussionMessage(path.join(worktree, discussion.transcriptPath), speaker, text, messageID); discussion.appendedMessageIDs ??= []; discussion.appendedMessageIDs.push(messageID); saveDiscussionRegistry(worktree, registry); } +async function summarizeDiscussionWithBA(client, worktree, discussion) { + const transcriptPath = path.join(worktree, discussion.transcriptPath); + const summaryPath = path.join(worktree, discussion.summaryPath); + const summaryDir = path.dirname(summaryPath); + if (!fs.existsSync(summaryDir)) fs.mkdirSync(summaryDir, { recursive: true }); + + const hasExistingSummary = fs.existsSync(summaryPath); + const priorMtimeMs = hasExistingSummary ? fs.statSync(summaryPath).mtimeMs : null; + const summarizerSession = await client.session.create({ + body: { title: `Discussion Summary: ${discussion.id}` } + }); + + const promptText = [ + "[Agent Message] From: product_manager To: business_analyst", + "", + "Read the full runtime discussion transcript and convert it into a workflow-ready discussion summary.", + "", + `Discussion ID: ${discussion.id}`, + `Discussion Title: ${discussion.title}`, + `Source transcript: ${discussion.transcriptPath}`, + hasExistingSummary ? `Existing summary to update: ${discussion.summaryPath}` : "Existing summary to update: (none)", + `Write the final summary to this exact file path: ${discussion.summaryPath}`, + "", + "Do not return the full summary in chat. Write it into the target file and then return only a short confirmation that includes:", + "- the target file path", + "- whether the write succeeded", + "", + "Requirements:", + "1. Preserve all workflow-relevant detail.", + "2. Remove greetings, filler, repetition, and conversational back-and-forth that does not affect execution.", + "3. Do not omit facts, requests, constraints, non-goals, decisions, assumptions, open questions, risks, or referenced repository areas.", + "4. If something is unresolved, record it under Open Questions rather than guessing.", + "5. Convert implied but clearly supported details into explicit bullets when helpful.", + "6. Optimize the result for PMA and later subagents to act on it efficiently.", + "7. Do not include transcript-style dialogue formatting in the final artifact.", + "8. If an existing summary file is present, read it and carry forward its still-valid details while integrating the new transcript content.", + "", + "Write the file in this exact structure:", + "", + "---", + `id: ${discussion.id}`, + `title: ${JSON.stringify(discussion.title)}`, + "status: closed", + "summarized_by: business_analyst", + "source: runtime-transcript", + "---", + "", + "# Discussion Summary", + "", + "## Topic", + "", + "", + "## Purpose", + "", + "", + "## Repository Truth Relevant To This Discussion", + "- ...", + "", + "## Facts Established", + "- ...", + "", + "## Requirements Captured", + "- ...", + "", + "## Constraints", + "- ...", + "", + "## Non-Goals", + "- ...", + "", + "## Decisions Made", + "- ...", + "", + "## Assumptions", + "- ...", + "", + "## Open Questions", + "- ...", + "", + "## Risks Or Concerns", + "- ...", + "", + "## Referenced Files Or Areas", + "- ...", + "", + "## Recommended Workflow Next Step", + "- assigned_to: ", + "- why: ", + "", + "Quality bar:", + "- concise but complete", + "- no fluff", + "- no invented details", + "- no lost workflow-relevant detail", + "", + "If a later agent could make a wrong decision because a detail was omitted, that omission is a failure." + ].join("\n"); + + const response = await client.session.prompt({ + path: { id: summarizerSession.data.id }, + body: { + agent: "business_analyst", + parts: [{ type: "text", text: promptText }] + } + }); + + const confirmation = extractTextParts(response.data.parts || []); + return { confirmation, summaryPath, transcriptPath, hasExistingSummary, priorMtimeMs }; +} + +function archiveDiscussionTranscript(worktree, transcriptRelativePath) { + const sourcePath = path.join(worktree, transcriptRelativePath); + if (!fs.existsSync(sourcePath)) return null; + + const archiveDir = archivedRuntimeDiscussionsDir(worktree); + if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true }); + + const targetPath = path.join(archiveDir, path.basename(sourcePath)); + fs.renameSync(sourcePath, targetPath); + return targetPath; +} + +async function finalizeClosingDiscussion(client, worktree, registry, sessionID, discussion) { + const { confirmation, summaryPath, hasExistingSummary, priorMtimeMs } = await summarizeDiscussionWithBA(client, worktree, discussion); + if (!fs.existsSync(summaryPath)) { + throw new Error(`Discussion summary was not written to ${discussion.summaryPath}`); + } + + if (hasExistingSummary) { + const currentMtimeMs = fs.statSync(summaryPath).mtimeMs; + if (currentMtimeMs <= priorMtimeMs) { + throw new Error(`Discussion summary file was not updated at ${discussion.summaryPath}`); + } + } + + const summaryContent = fs.readFileSync(summaryPath, "utf8").trim(); + if (!summaryContent) { + throw new Error(`Discussion summary file is empty at ${discussion.summaryPath}`); + } + + const transcriptPath = path.join(worktree, discussion.transcriptPath); + setDiscussionStatus(transcriptPath, "closed"); + const archivedTranscriptPath = archiveDiscussionTranscript(worktree, discussion.transcriptPath); + delete registry.active[sessionID]; + saveDiscussionRegistry(worktree, registry); + + return { + confirmation, + summaryPath: discussion.summaryPath, + archivedTranscriptPath: archivedTranscriptPath + ? path.relative(worktree, archivedTranscriptPath) + : path.join(".nomadworks", "runtime", "discussions", "archive", path.basename(discussion.transcriptPath)) + }; +} + function normalizeTeamMode(value) { if (typeof value !== "string") return "full"; const normalized = value.trim().toLowerCase(); @@ -269,7 +561,9 @@ function isAgentEnabledForTeamMode(agentId, teamMode) { function applyTeamConfigRules(repoCfg) { repoCfg.agents ??= {}; + repoCfg.policies ??= {}; repoCfg.team_mode = normalizeTeamMode(repoCfg.team_mode); + repoCfg.policies.extract_defaults = normalizePolicyExtraction(repoCfg.policies.extract_defaults); for (const id of MANDATORY_AGENTS) { if (repoCfg.agents[id]?.enabled === false) { @@ -291,19 +585,179 @@ function isAgentEffectivelyEnabled(agentId, repoCfg) { } function getOperatingTeamMode(repoCfg) { - const hasArchitect = isAgentEffectivelyEnabled("technical_architect", repoCfg); - const hasRunner = isAgentEffectivelyEnabled("workflow_runner", repoCfg); - return hasArchitect && hasRunner ? "full" : "mini"; + return repoCfg.team_mode; } function readResolvedFile(relativePath, worktree) { - const repoPath = path.join(worktree, relativePath); - const bundlePath = path.join(PKG_ROOT, relativePath); - const filePath = fs.existsSync(repoPath) ? repoPath : bundlePath; - if (!fs.existsSync(filePath)) return ""; + const filePath = resolveIncludeFile(`plugin:${relativePath}`, worktree, PKG_ROOT); + if (!filePath || !fs.existsSync(filePath)) return ""; return resolveIncludes(fs.readFileSync(filePath, "utf8"), worktree, PKG_ROOT).trim(); } +function loadMarkdownFragment(filePath, worktree) { + if (!fs.existsSync(filePath)) return ""; + + try { + const raw = fs.readFileSync(filePath, "utf8"); + const { body } = parseFrontmatter(raw); + return resolveIncludes(body.trim(), worktree, PKG_ROOT); + } catch (e) { + console.error(`[NomadWorks] Failed to read markdown fragment ${filePath}:`, e); + return ""; + } +} + +function loadAgentDefinition(filePath, worktree) { + if (!fs.existsSync(filePath)) return null; + + try { + const rawContent = fs.readFileSync(filePath, "utf8"); + const { data, body } = parseFrontmatter(rawContent); + const prompt = resolveIncludes(body.trim(), worktree, PKG_ROOT); + return { data, prompt }; + } catch (e) { + console.error(`[NomadWorks] Failed to read agent definition ${filePath}:`, e); + return null; + } +} + +function syncGeneratedPolicies(worktree, repoCfg) { + if (repoCfg.policies?.extract_defaults !== "all") return; + if (!fs.existsSync(BUNDLE_POLICIES_DIR)) return; + + const generatedDir = generatedPoliciesDir(worktree); + if (!fs.existsSync(generatedDir)) fs.mkdirSync(generatedDir, { recursive: true }); + + const policyFiles = fs.readdirSync(BUNDLE_POLICIES_DIR).filter(file => file.endsWith(".md") && file !== "README.md"); + + for (const file of policyFiles) { + const sourcePath = path.join(BUNDLE_POLICIES_DIR, file); + const source = fs.readFileSync(sourcePath, "utf8").trimEnd(); + const generated = [ + "", + "", + source, + "" + ].join("\n"); + fs.writeFileSync(path.join(generatedDir, file), generated, "utf8"); + } +} + +function ensureReadmeFile(dirPath, content) { + if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true }); + const readmePath = path.join(dirPath, "README.md"); + if (!fs.existsSync(readmePath)) { + fs.writeFileSync(readmePath, content, "utf8"); + } +} + +function scaffoldNomadworksReadmes(worktree) { + ensureReadmeFile(repoPoliciesDir(worktree), fs.readFileSync(path.join(BUNDLE_POLICIES_DIR, "README.md"), "utf8")); + ensureReadmeFile(repoAgentsDir(worktree), [ + "# Repository Agents", + "", + "Place full repository-local agent definitions here.", + "", + "- Use `.nomadworks/agents/.md` to override a bundled agent's full base definition.", + "- Use `.nomadworks/agents/.md` to define a brand new custom repository agent.", + "- Files in this folder are treated as full agent definitions.", + "- `README.md` is ignored by agent discovery.", + "", + "## Include Types Available In Custom Agents", + "", + "Custom agents can use the same include resolution as bundled agents:", + "", + "- `` for plugin-owned shared guidance", + "- `` for repository-overridable policy files with bundled defaults", + "- `` for explicit files under `.nomadworks/`", + "", + "## Common Plugin Includes", + "", + "- `plugin:Agents_Common.md`", + "- `plugin:docs/core/agent_orchestration.md`", + "- `plugin:docs/core/communication_guidelines.md`", + "- `plugin:docs/core/discussion_agent_guidelines.md`", + "- `plugin:docs/core/role_contracts.md`", + "- `plugin:docs/core/task_model.md`", + "- `plugin:docs/core/codemap_conventions.md`", + "- `plugin:docs/core/pma_mode_full.md`", + "- `plugin:docs/core/pma_mode_mini.md`", + "- `plugin:docs/core/tech_lead_mode_full.md`", + "- `plugin:docs/core/tech_lead_mode_mini.md`", + "", + "## Available Policy Includes", + "", + "- `policy:development-guidelines.md`", + "- `policy:testing-guidelines.md`", + "- `policy:documentation-guidelines.md`", + "- `policy:git-commit-messaging.md`", + "- `policy:product-guidelines.md`", + "- `policy:ui-ux-guidelines.md`", + "" + ].join("\n")); + ensureReadmeFile(repoAgentAdditionsDir(worktree), [ + "# Repository Agent Additions", + "", + "Place additive prompt fragments here to append repository-specific instructions to an existing agent.", + "", + "- Use `.nomadworks/agent-additions/.md` to add instructions to a bundled or custom repo agent.", + "- The matching base agent must exist in the plugin bundle or `.nomadworks/agents/`.", + "- `README.md` is ignored by agent discovery.", + "", + "## Include Types Available In Additions", + "", + "Agent additions can use the same include resolution as bundled agents and custom agents:", + "", + "- `` for plugin-owned shared guidance", + "- `` for repository-overridable policy files with bundled defaults", + "- `` for explicit files under `.nomadworks/`", + "", + "## Common Plugin Includes", + "", + "- `plugin:Agents_Common.md`", + "- `plugin:docs/core/agent_orchestration.md`", + "- `plugin:docs/core/communication_guidelines.md`", + "- `plugin:docs/core/discussion_agent_guidelines.md`", + "- `plugin:docs/core/role_contracts.md`", + "- `plugin:docs/core/task_model.md`", + "- `plugin:docs/core/codemap_conventions.md`", + "", + "## Available Policy Includes", + "", + "- `policy:development-guidelines.md`", + "- `policy:testing-guidelines.md`", + "- `policy:documentation-guidelines.md`", + "- `policy:git-commit-messaging.md`", + "- `policy:product-guidelines.md`", + "- `policy:ui-ux-guidelines.md`", + "" + ].join("\n")); + ensureReadmeFile(generatedAgentsDir(worktree), [ + "# Generated Agent Prompts", + "", + "This folder contains generated final prompt dumps for inspection.", + "", + "- Files here are generated by NomadWorks and may be overwritten.", + "- Do not edit files here to customize agent behavior.", + "- Use `.nomadworks/agents/` for full agent definitions and `.nomadworks/agent-additions/` for additive instructions.", + "" + ].join("\n")); + ensureReadmeFile(generatedPoliciesDir(worktree), [ + "# Generated Policy References", + "", + "This folder contains generated reference copies of bundled default policy files.", + "", + "- Files here are generated by NomadWorks and may be overwritten.", + "- Runtime does not read policies from this folder directly.", + "- Copy a file into `.nomadworks/policies/` if you want to customize it.", + "" + ].join("\n")); +} + function getModePromptFragment(agentId, operatingTeamMode, worktree) { const fragmentMap = { product_manager: { @@ -323,8 +777,8 @@ function getModePromptFragment(agentId, operatingTeamMode, worktree) { export default async function NomadWorksPlugin(input) { const worktree = path.resolve(input.worktree || process.cwd()); - const debugDir = path.join(worktree, ".nomadworks", "agents"); - const configPath = path.join(worktree, ".codenomad", "nomadworks.yaml"); + const debugDir = generatedAgentsDir(worktree); + const configPath = resolveConfigPath(worktree); const discussionRegistry = loadDiscussionRegistry(worktree); // Load project-specific configuration @@ -337,6 +791,8 @@ export default async function NomadWorksPlugin(input) { } } repoCfg = applyTeamConfigRules(repoCfg); + scaffoldNomadworksReadmes(worktree); + syncGeneratedPolicies(worktree, repoCfg); const operatingTeamMode = getOperatingTeamMode(repoCfg); const startAndMonitorWorkflow = async (sessionId, pmaSessionId, initialText, taskPath = null) => { @@ -407,7 +863,7 @@ export default async function NomadWorksPlugin(input) { return "Error: team_mode must be either 'mini' or 'full'."; } - const cfgDir = path.join(context.worktree, ".codenomad"); + const cfgDir = nomadworksDir(context.worktree); if (!fs.existsSync(cfgDir)) fs.mkdirSync(cfgDir, { recursive: true }); // Discover all agent IDs to enable them explicitly @@ -417,7 +873,6 @@ export default async function NomadWorksPlugin(input) { const nomadworksTmplPath = path.join(TEMPLATES_DIR, "nomadworks.yaml.template"); const codemapTmplPath = path.join(TEMPLATES_DIR, "codemap.yml.template"); - if (!fs.existsSync(nomadworksTmplPath) || !fs.existsSync(codemapTmplPath)) { return "Error: Initialization templates not found in plugin."; } @@ -447,6 +902,8 @@ export default async function NomadWorksPlugin(input) { fs.writeFileSync(rootCodemapPath, codemapConfig, "utf8"); } + scaffoldNomadworksReadmes(context.worktree); + // Scaffold Task Registries const tasksDir = path.join(context.worktree, "tasks"); const scrsDir = path.join(context.worktree, "docs", "scrs"); @@ -471,7 +928,24 @@ export default async function NomadWorksPlugin(input) { fs.writeFileSync(scrsDonePath, "# Implemented Spec Change Requests\n\n| Date | SCR ID | Title | Related Feature | Task ID |\n| :--- | :--- | :--- | :--- | :--- |\n", "utf8"); } - return `NomadWorks initialized in '${requestedTeamMode}' team mode: .codenomad/nomadworks.yaml, registries, and codemap.yml created.`; + const initSummary = `NomadWorks initialized in '${requestedTeamMode}' team mode: .nomadworks/nomadworks.yaml, repo policy/agent folders, registries, and codemap.yml created.`; + + // Ensure OpenCode reloads config/agents after scaffolding changes. + // Not all environments expose this API, so treat it as best-effort. + const client = input.client; + if (client?.instance?.dispose) { + try { + const disposeRes = await client.instance.dispose({ query: { directory: context.worktree } }); + if (disposeRes?.data === true) { + return `${initSummary}\n\nOpenCode instance disposed so the new config can be loaded.`; + } + return `${initSummary}\n\nWarning: instance.dispose did not report success. You may need to restart OpenCode to load the new config.`; + } catch (e) { + return `${initSummary}\n\nWarning: Failed to dispose OpenCode instance (${e?.message || "unknown error"}). You may need to restart OpenCode to load the new config.`; + } + } + + return `${initSummary}\n\nNote: OpenCode instance dispose API unavailable in this environment. Restart OpenCode to load the new config.`; } }), nomadworks_validate: tool({ @@ -479,11 +953,16 @@ export default async function NomadWorksPlugin(input) { args: {}, async execute(args, context) { const res = await nomadworks_validate_logic(context.worktree); - if (res.ok) { - return `PASS: All source directories indexed. Hierarchy validated.\nWarnings: ${res.warnings.length}\n${res.warnings.map(w => "- " + w).join("\n")}`; - } else { - return `FAIL: Validation errors found:\n${res.errors.map(e => "- " + e).join("\n")}\nWarnings: ${res.warnings.length}\n${res.warnings.map(w => "- " + w).join("\n")}`; + + // Defensive: older plugin builds or custom forks may not return `warnings`. + const warnings = Array.isArray(res?.warnings) ? res.warnings : []; + const errors = Array.isArray(res?.errors) ? res.errors : []; + + if (res?.ok) { + return `PASS: All source directories indexed. Hierarchy validated.\nWarnings: ${warnings.length}\n${warnings.map(w => "- " + w).join("\n")}`; } + + return `FAIL: Validation errors found:\n${errors.map(e => "- " + e).join("\n")}\nWarnings: ${warnings.length}\n${warnings.map(w => "- " + w).join("\n")}`; } }), nomadworks_start_discussion: tool({ @@ -533,22 +1012,34 @@ export default async function NomadWorksPlugin(input) { } } - const existingFile = parseDiscussionFile(identity.absolutePath); + const existingFile = parseDiscussionFile(identity.summaryAbsolutePath); discussionTitle = existingFile.data.title || existingDiscussionID; - writeDiscussionFile(identity.absolutePath, { - ...existingFile.data, + const frontmatter = { + id: existingDiscussionID, + title: discussionTitle, status: "active", agent, - session_id: sessionID - }, existingFile.body); + session_id: sessionID, + appended_message_ids: [] + }; + const body = [ + `# Discussion: ${discussionTitle}`, + "", + "## Prior Summary Reference", + `Source summary file: ${identity.summaryRelativePath}`, + "", + "## Messages" + ].join("\n"); + writeDiscussionFile(identity.transcriptAbsolutePath, frontmatter, body); entry = { id: existingDiscussionID, title: discussionTitle, - filePath: identity.relativePath, + transcriptPath: identity.transcriptRelativePath, + summaryPath: identity.summaryRelativePath, status: "active", agent, - appendedMessageIDs: Array.isArray(existingFile.data.appended_message_ids) ? [...existingFile.data.appended_message_ids] : [] + appendedMessageIDs: [] }; } else { discussionTitle = title; @@ -561,12 +1052,13 @@ export default async function NomadWorksPlugin(input) { session_id: sessionID, appended_message_ids: [] }; - writeDiscussionFile(identity.absolutePath, frontmatter, `# Discussion: ${discussionTitle}\n\n## Messages`); + writeDiscussionFile(identity.transcriptAbsolutePath, frontmatter, `# Discussion: ${discussionTitle}\n\n## Messages`); entry = { id: identity.id, title: discussionTitle, - filePath: identity.relativePath, + transcriptPath: identity.transcriptRelativePath, + summaryPath: identity.summaryRelativePath, status: "active", agent, appendedMessageIDs: [] @@ -587,14 +1079,14 @@ export default async function NomadWorksPlugin(input) { if (message.info.role === "user") { const text = extractTextParts(message.parts || []); if (text) { - appendDiscussionMessage(identity.absolutePath, "User", text, message.info.id); + appendDiscussionMessage(identity.transcriptAbsolutePath, "User", text, message.info.id); if (!entry.appendedMessageIDs.includes(message.info.id)) entry.appendedMessageIDs.push(message.info.id); backfilled += 1; } } else if (message.info.role === "assistant") { const text = extractTextParts(message.parts || []); if (text) { - appendDiscussionMessage(identity.absolutePath, agent, text, message.info.id); + appendDiscussionMessage(identity.transcriptAbsolutePath, agent, text, message.info.id); if (!entry.appendedMessageIDs.includes(message.info.id)) entry.appendedMessageIDs.push(message.info.id); backfilled += 1; } @@ -607,7 +1099,7 @@ export default async function NomadWorksPlugin(input) { } const action = existingDiscussionID ? "reopened" : "started"; - return `SUCCESS: Discussion ${action}.\nID: ${entry.id}\nTitle: ${discussionTitle}\nFile: ${identity.relativePath}\nStatus: active\nBackfilled messages: ${backfilled}`; + return `SUCCESS: Discussion ${action}.\nID: ${entry.id}\nTitle: ${discussionTitle}\nTranscript: ${entry.transcriptPath}\nFinal Summary Target: ${entry.summaryPath}\nStatus: active\nBackfilled messages: ${backfilled}`; } }), nomadworks_stop_discussion: tool({ @@ -622,12 +1114,20 @@ export default async function NomadWorksPlugin(input) { return "FAIL: No active discussion exists for this session."; } - const discussionPath = path.join(context.worktree, existing.filePath); - setDiscussionStatus(discussionPath, "closing"); - existing.status = "closing"; + const discussionPath = path.join(context.worktree, existing.transcriptPath); + setDiscussionStatus(discussionPath, "summarizing"); + existing.status = "summarizing"; saveDiscussionRegistry(context.worktree, discussionRegistry); - return `SUCCESS: Discussion stop requested.\nID: ${existing.id}\nTitle: ${existing.title}\nFile: ${existing.filePath}\nStatus: closing`; + try { + const result = await finalizeClosingDiscussion(input.client, context.worktree, discussionRegistry, sessionID, existing); + return `SUCCESS: Discussion stopped and summarized.\nID: ${existing.id}\nTitle: ${existing.title}\nFinal Summary: ${result.summaryPath}\nStatus: closed`; + } catch (err) { + setDiscussionStatus(discussionPath, "active"); + existing.status = "active"; + saveDiscussionRegistry(context.worktree, discussionRegistry); + return `FAIL: Discussion summarization failed.\nID: ${existing.id}\nTitle: ${existing.title}\nTranscript: ${existing.transcriptPath}\nFinal Summary Target: ${existing.summaryPath}\nReason: ${err.message}`; + } } }), nomadflow_run_workflow: tool({ @@ -671,10 +1171,10 @@ export default async function NomadWorksPlugin(input) { ].filter(Boolean).join("\n"); const lifecycleInstruction = workflowTrack === "implementation" - ? "Please execute the full lifecycle (Sync -> Implementation -> Commit -> Archive) and provide a final summary." + ? "You are the Workflow Runner. Execute the full lifecycle (Task Readiness Check -> Pre-Task Sync -> Workflow Execution Plan -> Delegate Implementation -> Delegate Verification -> Post-Task Sync -> Commit -> Archive). Read the task file first and verify it has sufficient PMA-provided task management context before doing anything else. Do not implement code directly. If implementation is required, delegate it to developer. If verification is required, delegate it to qa_engineer and tech_lead. If you hit a hard blocker, stop and END your run with a final summary that starts with 'HARD BLOCKER:' so the plugin can relay it back to PMA. Provide a final summary." : workflowTrack === "spec" - ? "Please execute the full spec lifecycle for this task, update the required documentation artifacts, and provide a final summary." - : "Please execute the investigation lifecycle for this task, capture findings clearly, and provide a final summary."; + ? "You are the Workflow Runner. Execute the full spec lifecycle for this task, delegate specialist work as needed, update the required documentation artifacts, and provide a final summary." + : "You are the Workflow Runner. Execute the investigation lifecycle for this task, delegate specialist work as needed, capture findings clearly, and provide a final summary."; const initialText = `Task File: ${args.task_path}\n${metadataSummary ? `\n${metadataSummary}` : ""}\n\nInstructions: ${args.instructions}\n\n${lifecycleInstruction}`; @@ -776,11 +1276,6 @@ export default async function NomadWorksPlugin(input) { if (info.role === "assistant" && info.time?.completed) { const discussion = discussionRegistry.active[info.sessionID]; await appendMessageIfNeeded(client, worktree, discussionRegistry, info.sessionID, info.id, discussion.agent || "Assistant"); - if (discussion?.status === "closing") { - setDiscussionStatus(path.join(worktree, discussion.filePath), "closed"); - delete discussionRegistry.active[info.sessionID]; - saveDiscussionRegistry(worktree, discussionRegistry); - } } } catch (err) { if (debug) console.error("[NomadWorks] Failed to append discussion transcript:", err); @@ -792,112 +1287,125 @@ export default async function NomadWorksPlugin(input) { const nomadworksActive = repoCfg && repoCfg.enabled === true; - // 1. Identify and compile all NomadWorks agents - const repoAgentsDir = path.join(worktree, ".codenomad", "nomadworks", "agents"); - const agentSources = []; - if (fs.existsSync(BUNDLE_AGENTS_DIR)) agentSources.push(BUNDLE_AGENTS_DIR); - if (fs.existsSync(repoAgentsDir)) agentSources.push(repoAgentsDir); + // 1. Identify and compile all NomadWorks agents from bundled bases, + // repo-local full definitions, and additive repo-local fragments. + const repoAgentDefinitions = repoAgentsDir(worktree); + const repoAgentAdditions = repoAgentAdditionsDir(worktree); + const legacyAgentsDir = legacyRepoAgentsDir(worktree); + const bundledAgentFiles = listMarkdownFiles(BUNDLE_AGENTS_DIR); + const repoAgentFiles = listMarkdownFiles(repoAgentDefinitions); + const legacyAgentFiles = listMarkdownFiles(legacyAgentsDir); + const agentIds = new Set([ + ...bundledAgentFiles.map(file => file.replace(".md", "")), + ...repoAgentFiles.map(file => file.replace(".md", "")), + ...legacyAgentFiles.map(file => file.replace(".md", "")) + ]); const ourAgents = {}; - for (const agentsDir of agentSources) { - if (!fs.existsSync(agentsDir)) continue; - - let files = []; - try { - files = fs.readdirSync(agentsDir).filter(f => f.endsWith(".md")); - } catch (e) { - console.error(`[NomadWorks] Failed to read agents from ${agentsDir}:`, e); + for (const id of agentIds) { + const file = `${id}.md`; + + if (!nomadworksActive && id !== "product_manager") { continue; } - for (const file of files) { - const id = file.replace(".md", ""); - - if (!nomadworksActive && id !== "product_manager") { - continue; - } + const agentOverride = repoCfg.agents?.[id] || {}; + const hasRepoDefinedAgent = repoAgentFiles.includes(file) || legacyAgentFiles.includes(file); + if (nomadworksActive) { + const enabledByConfig = typeof agentOverride.enabled === "boolean" ? agentOverride.enabled : null; + const enabled = enabledByConfig !== null + ? enabledByConfig || MANDATORY_AGENTS.has(id) + : (hasRepoDefinedAgent ? true : isAgentEffectivelyEnabled(id, repoCfg)); + if (!enabled) continue; + } - const agentOverride = repoCfg.agents?.[id] || {}; - if (nomadworksActive && !isAgentEffectivelyEnabled(id, repoCfg)) continue; + const bundledDefinition = loadAgentDefinition(path.join(BUNDLE_AGENTS_DIR, file), worktree); + const repoDefinition = loadAgentDefinition(path.join(repoAgentDefinitions, file), worktree) + || loadAgentDefinition(path.join(legacyAgentsDir, file), worktree); - const filePath = path.join(agentsDir, file); - let rawContent; - try { - rawContent = fs.readFileSync(filePath, "utf8"); - } catch (e) { - console.error(`[NomadWorks] Failed to read agent file ${filePath}:`, e); - continue; - } + const activeDefinition = repoDefinition || bundledDefinition; + if (!activeDefinition) continue; + const { data } = activeDefinition; - const { data, body } = parseFrontmatter(rawContent); - let finalPrompt = resolveIncludes(body.trim(), worktree, PKG_ROOT); - const modePromptFragment = getModePromptFragment(id, operatingTeamMode, worktree); - if (modePromptFragment) { - finalPrompt = `${finalPrompt}\n\n${modePromptFragment}`; - } - const provider = agentOverride.provider || data.provider || repoCfg.defaults?.provider; - const model = agentOverride.model || data.model || repoCfg.defaults?.model; - - const agentConfig = { - description: data.description, - mode: agentOverride.mode || data.mode || "subagent", - prompt: finalPrompt, - tools: { ...(data.tools || {}), ...(agentOverride.tools || {}) }, - permission: agentOverride.permission || data.permission || data.permissions || repoCfg.defaults?.permissions, - model: toModelString(provider, model), - temperature: agentOverride.temperature ?? data.temperature ?? repoCfg.defaults?.temperature, - disable: false - }; + let finalPrompt = activeDefinition.prompt; + const modePromptFragment = getModePromptFragment(id, operatingTeamMode, worktree); + if (modePromptFragment) { + finalPrompt = `${finalPrompt}\n\n${modePromptFragment}`; + } - const specialKeys = ['description', 'mode', 'model', 'provider', 'temperature', 'permission', 'permissions', 'tools', 'tools_add', 'tools_remove', 'enabled', 'prompt', 'disable']; - - const defaults = repoCfg.defaults || {}; - for (const k of Object.keys(defaults)) { - if (!specialKeys.includes(k)) agentConfig[k] = defaults[k]; - } - for (const k of Object.keys(data)) { - if (!specialKeys.includes(k)) agentConfig[k] = data[k]; - } - for (const k of Object.keys(agentOverride)) { - if (!specialKeys.includes(k)) agentConfig[k] = agentOverride[k]; - } + const additionFragment = loadMarkdownFragment(path.join(repoAgentAdditions, file), worktree); + if (additionFragment) { + finalPrompt = `${finalPrompt}\n\n# Repository-Specific ${id} Additions\n\n${additionFragment}`; + } - if (Array.isArray(agentOverride.tools_add)) { - agentConfig.tools ??= {}; - for (const t of agentOverride.tools_add) agentConfig.tools[t] = true; - } - if (Array.isArray(agentOverride.tools_remove)) { - if (agentConfig.tools) { - for (const t of agentOverride.tools_remove) delete agentConfig.tools[t]; - } + const provider = agentOverride.provider || data.provider || repoCfg.defaults?.provider; + const model = agentOverride.model || data.model || repoCfg.defaults?.model; + + const agentConfig = { + description: data.description, + mode: agentOverride.mode || data.mode || "subagent", + prompt: finalPrompt, + tools: { ...(data.tools || {}), ...(agentOverride.tools || {}) }, + permission: agentOverride.permission || data.permission || data.permissions || repoCfg.defaults?.permissions, + model: toModelString(provider, model), + temperature: agentOverride.temperature ?? data.temperature ?? repoCfg.defaults?.temperature, + disable: false + }; + + const specialKeys = ['description', 'mode', 'model', 'provider', 'temperature', 'permission', 'permissions', 'tools', 'tools_add', 'tools_remove', 'enabled', 'prompt', 'disable']; + + const defaults = repoCfg.defaults || {}; + for (const k of Object.keys(defaults)) { + if (!specialKeys.includes(k)) agentConfig[k] = defaults[k]; + } + for (const k of Object.keys(data)) { + if (!specialKeys.includes(k)) agentConfig[k] = data[k]; + } + for (const k of Object.keys(agentOverride)) { + if (!specialKeys.includes(k)) agentConfig[k] = agentOverride[k]; + } + + if (Array.isArray(agentOverride.tools_add)) { + agentConfig.tools ??= {}; + for (const t of agentOverride.tools_add) agentConfig.tools[t] = true; + } + if (Array.isArray(agentOverride.tools_remove)) { + if (agentConfig.tools) { + for (const t of agentOverride.tools_remove) delete agentConfig.tools[t]; } + } - if (id === "product_manager" && (!isAgentEffectivelyEnabled("workflow_runner", repoCfg) || operatingTeamMode !== "full")) { - if (agentConfig.tools) { - delete agentConfig.tools.nomadflow_run_workflow; - delete agentConfig.tools.nomadflow_prompt_workflow; - } + if (id === "product_manager" && (!isAgentEffectivelyEnabled("workflow_runner", repoCfg) || operatingTeamMode !== "full")) { + if (agentConfig.tools) { + delete agentConfig.tools.nomadflow_run_workflow; + delete agentConfig.tools.nomadflow_prompt_workflow; } + } - ourAgents[id] = agentConfig; + ourAgents[id] = agentConfig; - if (repoCfg.features?.debug_dumps !== false) { - const debugPath = path.join(debugDir, `${id}.md`); - const { prompt, ...dumpConfig } = agentConfig; - const debugHeader = `--- + if (repoCfg.features?.debug_dumps !== false) { + const debugPath = path.join(debugDir, `${id}.md`); + const { prompt, ...dumpConfig } = agentConfig; + const debugHeader = `--- ${YAML.stringify(dumpConfig).trim()} ---`; - try { - if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); - fs.writeFileSync(debugPath, `${debugHeader}\n\n${prompt}`, "utf8"); - } catch (e) { /* ignore debug errors */ } - } + try { + if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); + fs.writeFileSync(debugPath, `${debugHeader}\n\n${prompt}`, "utf8"); + } catch (e) { /* ignore debug errors */ } } } const builtInAgents = ["build", "plan", "general", "explore"]; - const allToDisable = new Set([...builtInAgents, ...Object.keys(cfg.agent)]); + const preserveExistingAgents = repoCfg.features?.keep_builtin_agents === true; + const allToDisable = preserveExistingAgents + ? new Set() + : new Set([...builtInAgents, ...Object.keys(cfg.agent)]); + + // Some users want to keep OpenCode's existing agents available alongside NomadWorks. + // In that mode, avoid disabling anything that OpenCode already registered. for (const id of allToDisable) { if (!ourAgents[id]) { diff --git a/tasks/subtask-template.md b/tasks/subtask-template.md index 348bede..559a640 100644 --- a/tasks/subtask-template.md +++ b/tasks/subtask-template.md @@ -15,12 +15,31 @@ parent: TASK-[PARENT] ### Task * [ ] [Assigned Agent]: [Action] +### Definition Of Ready Check +- [ ] Parent task, assigned slice, and local scope are clear. +- [ ] Local acceptance criteria or expected outcome is clear. +- [ ] Dependencies, blockers, and assumptions are known or recorded. + ### Acceptance Criteria * [Criterion 1] +### Acceptance Criteria Verification Map +- [ ] AC-1 + - **Method:** `[unit test | integration test | e2e | manual check | doc review]` + - **Owner:** `[agent_name]` + - **Evidence:** `[optional path or note]` + +Use this section to record the verification method for each local acceptance criterion. Evidence links are optional and should be added only when they materially improve traceability. + ### Assigned To: [Primary Agent] ### Status: [todo / in_progress / review / done / blocked] +### Definition Of Done Check +- [ ] Assigned outcome is complete. +- [ ] Relevant verification for this subtask is complete. +- [ ] Evidence or notes are recorded in the parent task when required. +- [ ] Parent task is updated with the subtask outcome. + # Reviews ## [Reviewing Agent]: - [Comments] diff --git a/tasks/task-template.md b/tasks/task-template.md index e5b553e..8a601a4 100644 --- a/tasks/task-template.md +++ b/tasks/task-template.md @@ -27,6 +27,14 @@ reopened_count: 0 - **Assigned To:** `[product_manager | business_analyst | tech_lead | technical_architect | developer | qa_engineer | ui_ux_designer | workflow_runner]` - **Handoff From:** `[agent_name or null]` +## Definition Of Ready Check +- [ ] Scope is clear, bounded, and appropriate for the task's declared complexity. +- [ ] Acceptance criteria are present, testable, and aligned with the objective. +- [ ] Complexity, track, and slice are set correctly. +- [ ] Required dependencies, assumptions, blockers, and open questions are resolved or explicitly recorded. +- [ ] Required pre-sync specialist review is complete. +- [ ] Required SCR exists and is approved when the workflow requires it. + ## Acceptance Criteria - [ ] AC-1: [Primary behavioral or task outcome] - [ ] AC-2: [Secondary outcome, validation, or edge-case requirement] @@ -34,6 +42,22 @@ reopened_count: 0 - [ ] AC-4: Product documentation reflects the latest state of the application for this change, or this task explicitly records that no product-truth update was required. - [ ] AC-5: Technical documentation reflects any architectural or implementation-significant change, or this task explicitly records that no technical-truth update was required. +## Acceptance Criteria Verification Map +- [ ] AC-1 + - **Method:** `[unit test | integration test | e2e | manual check | doc review]` + - **Owner:** `[agent_name]` + - **Evidence:** `[optional path or note]` +- [ ] AC-2 + - **Method:** `[unit test | integration test | e2e | manual check | doc review]` + - **Owner:** `[agent_name]` + - **Evidence:** `[optional path or note]` +- [ ] AC-3 + - **Method:** `[unit test | integration test | e2e | manual check | doc review]` + - **Owner:** `[agent_name]` + - **Evidence:** `[optional path or note]` + +Use this section to record how each acceptance criterion will be verified. Evidence links are optional and should be added when they materially improve traceability. Shared evidence may cover multiple acceptance criteria. + ### Source Authority (MANDATORY) * **Spec Reference:** [Commit Hash or SCR ID from documentation update] * **Documentation:** [Link to updated SPECIFICATION.md or FEATURES_LIST.md] @@ -100,6 +124,16 @@ Use this section when a task that was thought to be done must be resumed using t * [ ] Product Manager: Acceptance Criteria and Evidence Coverage Verification * [ ] User: Final Approval +## Definition Of Done Check +- [ ] All in-scope acceptance criteria are satisfied or explicitly marked blocked with reason. +- [ ] Required tests, builds, and verification commands pass. +- [ ] Required evidence and verification artifacts are recorded. +- [ ] Documentation impact is resolved according to repository policy. +- [ ] Relevant CodeMap updates are complete when needed. +- [ ] Task files and workflow registries are updated. +- [ ] Authorized review and closure checks are complete. +- [ ] Final committed state contains all required code, documentation, and registry updates. + ### Finalization * [ ] [Assigned Agent]: CodeMap Update (Update `codemap.yml` if entrypoints/wiring changed) * [ ] [Assigned Agent]: Documentation Update (Update relevant docs in `docs/`) diff --git a/templates/nomadworks.yaml.template b/templates/nomadworks.yaml.template index eabd3a3..c2f6a27 100644 --- a/templates/nomadworks.yaml.template +++ b/templates/nomadworks.yaml.template @@ -9,8 +9,12 @@ defaults: # permissions: allow features: - debug_dumps: true # Dumps final agent configs to .nomadworks/agents/ for verification + debug_dumps: true # Dumps final agent configs to .nomadworks/generated/agents/ for verification # debug_logs: false # Enable detailed console logging for the plugin + # keep_builtin_agents: false # If true, do not disable agents OpenCode already registered, including built-ins codemap_verification: true +policies: + extract_defaults: none # Set to 'all' to write bundled policy defaults to .nomadworks/generated/policies/ + agents: