From 9010e39b5745317dbc73b6f835440b0cb3ac7481 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 02:06:50 +0200 Subject: [PATCH 01/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Align=20delivery?= =?UTF-8?q?=20workflow=20with=20issue=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Ways-of-Working/Branching-and-Merging.md | 6 +-- .../Definition-of-Ready-and-Done.md | 47 ++++++++++--------- src/docs/Ways-of-Working/Goal-Setting.md | 13 +++-- src/docs/Ways-of-Working/PR-Format.md | 8 ++-- .../Spec-Driven-Development.md | 16 +++---- src/docs/Ways-of-Working/Workflow.md | 38 +++++++-------- src/docs/Ways-of-Working/index.md | 4 +- 7 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/docs/Ways-of-Working/Branching-and-Merging.md b/src/docs/Ways-of-Working/Branching-and-Merging.md index 6dffd95..93759c5 100644 --- a/src/docs/Ways-of-Working/Branching-and-Merging.md +++ b/src/docs/Ways-of-Working/Branching-and-Merging.md @@ -1,6 +1,6 @@ --- title: Branching and Merging -description: Topic branches, pull-request-only integration, and merge models. +description: Delivery-leaf topic branches, pull-request-only integration, and merge models. --- # Branching and Merging @@ -9,7 +9,7 @@ How changes move from a working branch into a protected branch. The model is sma ## Topic branches -- Work happens on short-lived branches — one per issue, each in its own [worktree](Git-Worktrees.md). A branch is cut from the default branch unless a different base is explicitly given; an agent follows the same default. +- Repository delivery happens on short-lived branches — one per Task or Bug, each in its own [worktree](Git-Worktrees.md). Epic and PBI aggregates do not own branches, and an operational Task has no branch. A branch is cut from the default branch unless a different base is explicitly given; an agent follows the same default. - Name branches `/-`, e.g. `feat/42-pagination` or `fix/99-null-context`. The type matches the change type. - Branches stay short-lived. The longer a branch lives, the further it diverges and the harder it is to merge. @@ -33,7 +33,7 @@ Build the stack from its destination upward: Every layer follows the ordinary [Contribution Workflow](Contribution-Workflow.md): -1. **Plan the dependency.** Give each layer its own Task issue. Record `Blocked by: #N` on dependent issues as described in [Issue Hierarchy](Issues/Types/Hierarchy.md#how-to-express-the-hierarchy), and order the implementation so the shared foundation is the first layer. +1. **Plan the dependency.** Give each layer its own Task or Bug issue. Add a native blocked-by edge from every dependent leaf to its prerequisite as described in [Issue Relationships](Issues/Process/Relationships.md#execution-order); prose, sub-issue order, and stack position do not create the gate. 2. **Open every layer as a draft.** After the initial commit, push the branch and open its pull request immediately. Use the standard user-facing title and description from [PR Format](PR-Format.md); do not add stack position or an internal branch name to the title. 3. **Link the stack in Technical Details.** Add fully qualified pull request links for the immediate dependency and dependent, using `Depends on Owner/Repo#N` and `Followed by Owner/Repo#N`. Each pull request closes only its own Task issue. 4. **Keep each delta isolated.** The pull request diff against its current base contains only that layer's change. Run its tests, checks, and automated review even when an earlier layer already exercised the combined code. diff --git a/src/docs/Ways-of-Working/Definition-of-Ready-and-Done.md b/src/docs/Ways-of-Working/Definition-of-Ready-and-Done.md index 285152c..a502dfa 100644 --- a/src/docs/Ways-of-Working/Definition-of-Ready-and-Done.md +++ b/src/docs/Ways-of-Working/Definition-of-Ready-and-Done.md @@ -5,20 +5,21 @@ description: The three gates that bracket every piece of work. # Definition of Ready and Done -Three gates bracket every piece of work: one gates when it can start, one gates when it is ready for other people to review, and one gates when it can be called complete. All are shared contracts, not personal preferences. +Three gates bracket work: one gates when an issue can move at its own altitude, one gates when a delivery pull request is ready for other people to review, and one gates when an issue can be called complete. All are shared contracts, not personal preferences. ## Definition of Ready -An item is ready to be pulled into work when: +Readiness is type-specific. The canonical type pages own the detailed criteria; this gate determines what may move next. -- It is at the right level — a single deliverable, not a bundle. See [Issue Hierarchy](Issues/Types/Hierarchy.md). -- The intent is clear — the problem and the desired outcome are understood, written from the user's perspective. -- It has acceptance criteria — "done" is described and testable. -- It is sized to fit comfortably within one cycle of work. -- Dependencies and blockers are known. -- Open questions are resolved. +### Aggregate readiness -Ready gates *starting*, not scope. An item that isn't ready stays in the backlog and gets refined — it is not pulled in and figured out on the fly. +An [Epic](Issues/Types/Epic.md#ready) or [PBI](Issues/Types/PBI.md#ready) is ready to decompose or coordinate when its aggregate outcome and acceptance criteria are testable, its native containment and dependency relationships are current, and no open decision prevents the first required children from becoming ready. Readiness does not send an aggregate into Build. + +### Delivery-leaf readiness + +A [Task](Issues/Types/Task.md#ready) or [Bug](Issues/Types/Bug.md#ready) is ready for Build when it owns one independently verifiable deliverable, its local acceptance criteria and implementation plan are executable, its native blockers are clear, and it fits one reviewable pull request. An operational Task is ready only when the action, durable evidence, and independent verifier are identified before execution. + +Ready gates starting, not scope. An issue that is not ready stays in refinement or planning; it is not pulled into Build and figured out on the fly. See [Issue Hierarchy](Issues/Types/Hierarchy.md) for routing and [Issue Relationships](Issues/Process/Relationships.md) for authoritative blockers. ## Definition of Ready for Review @@ -26,12 +27,11 @@ A pull request stays a **draft** until it is genuinely ready for other people to A pull request is ready for review when: -- Every task in the linked issue's checklist is complete — or explicitly moved to a follow-up issue. No half-done work is left implicit. -- Dependencies are current — any dependency update the change relies on is already merged or included; nothing waits on a bump that has not landed yet. +- It closes exactly one Task or Bug, and every item in that delivery leaf's implementation plan is complete or explicitly moved to a follow-up issue. +- Native dependencies are current and every prerequisite the change relies on has landed; the pull request is not used to bypass a blocked-by edge. - All required checks are green — not just tests that pass locally. CI is complete, not in progress. - The automated review loop has converged — a clean [Copilot round](Contribution-Workflow.md#the-copilot-review-loop) with no unresolved review threads. - The title, release-note description, and exactly one change-type label are finalized. See [PR Format](PR-Format.md). -- A linked issue exists. If any item is open, the pull request stays a draft. Marking it ready with known-open work shifts the author's unfinished job onto reviewers — the opposite of what the signal means. @@ -39,15 +39,18 @@ Once every item holds, hand the change off: mark it ready for review and enable ## Definition of Done -An item is done when: +Completion follows the issue's delivery or aggregate path. Across all paths, the issue's acceptance criteria are verified, required documentation is current, and no known regression is left implicit. + +### Repository delivery leaf + +A repository-delivery Task or Bug is done when its reviewed pull request is merged and closes that one leaf, required checks and tests pass, applicable coding standards hold, and the affected evergreen specification and documentation describe the delivered behavior. Release or deploy it where that applies. + +### Operational Task + +An operational Task is done without a pull request only after the action has durable audit evidence, an independent verifier confirms every acceptance criterion, and the Task is closed. The canonical [operational delivery path](Issues/Types/Task.md#operational-delivery) owns the required record. + +### Aggregate issue -- The acceptance criteria are met. -- The change is merged through a reviewed pull request. -- It conforms to the [Coding Standards](../Coding-Standards/index.md); linters and checks are green. -- Tests cover the behavior and pass. -- The [evergreen specification](Documentation-Model.md#evergreen-and-evolutionary) for the affected capability describes the new behaviour — the spec leads, the code matches it. -- Documentation is updated in the same change — both owning-team docs (README, in-code help, and this site) and user-facing documentation. -- It is released or deployed, where that applies. -- No known regressions remain, and the issue is closed. +A PBI or Epic is done only when every required native child is complete and its own aggregate acceptance criteria and evidence are verified. Close the aggregate directly after that verification; a delivery pull request must not close it. -Done is binary. Skip a criterion only when it genuinely does not apply — a docs-only change has no deploy step. If "done" repeatedly needs exceptions, fix the definition rather than quietly lowering the bar. +Done is binary. Skip a criterion only when it genuinely does not apply. If "done" repeatedly needs exceptions, fix the definition rather than quietly lowering the bar. diff --git a/src/docs/Ways-of-Working/Goal-Setting.md b/src/docs/Ways-of-Working/Goal-Setting.md index 764d69f..d5cdbc0 100644 --- a/src/docs/Ways-of-Working/Goal-Setting.md +++ b/src/docs/Ways-of-Working/Goal-Setting.md @@ -1,6 +1,6 @@ --- title: Goal-Setting Framework -description: Mission, OKRs, and initiatives — strategy connected to delivery. +description: Mission, OKRs, and Initiatives — the strategy above repository Epics. --- # Goal-Setting Framework @@ -12,8 +12,8 @@ MSX uses a lightweight OKR-based framework to connect strategic direction to day | Layer | Lives in | Purpose | | -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------- | | **Mission** | Pinned issue in the org `.github` repository | The org's reason to exist — *make great software delivery the default: easy, fast, and safe* | -| **OKR** | Sub-issues of the Mission | Qualitative objectives + measurable key results | -| **Initiative** | Sub-issues of an OKR | A concrete bet to move a Key Result — becomes an Epic in a repo | +| **OKR** | Sub-issues of the Mission | Qualitative objectives + measurable key results | +| **Initiative** | Sub-issues of an OKR | A concrete organization-level bet to move a Key Result | ## Why OKRs and not KPIs @@ -32,11 +32,14 @@ OKRs are tracked as sub-issues of the Mission issue in the org `.github` reposit ## From strategy to delivery -Initiatives are the bridge between strategy and execution. An Initiative is a sub-issue of an OKR and maps directly to an **Epic** in the relevant repository. From there it decomposes into PBIs and Tasks through the [Issue Hierarchy](Issues/Types/Hierarchy.md). +Mission, OKR, and Initiative are the strategy layers. The strategy boundary sits **above Epic**: an Initiative links to the repository Epics that deliver its outcome, and each participating repository owns its own Epic. The canonical [Issue Hierarchy](Issues/Types/Hierarchy.md) owns all decomposition and delivery semantics from Epic downward. ```text Mission (org-level, evergreen) └── OKR (qualitative objective + key results) └── Initiative (concrete bet to move a KR) - └── Epic (in a repository) → PBIs → Tasks + ├── Epic (repository A) + └── Epic (repository B) ``` + +The arrow from Initiative to Epic is a strategy-to-delivery link, not a replacement for the native relationships inside a repository. Epic and lower issue types follow the [Issue Planning](Issues/Process/Planning.md) and [Issue Relationships](Issues/Process/Relationships.md) guidance. diff --git a/src/docs/Ways-of-Working/PR-Format.md b/src/docs/Ways-of-Working/PR-Format.md index 7f9d6af..4b76aa3 100644 --- a/src/docs/Ways-of-Working/PR-Format.md +++ b/src/docs/Ways-of-Working/PR-Format.md @@ -119,19 +119,19 @@ The **Technical details** block is for reviewers and maintainers. Include intern - Which internal functions, classes, or files were changed. - Implementation approach and design decisions. - Backward compatibility notes for developers. -- **Implementation plan progress** — cross-reference Section 3 of the linked issue. Which tasks does this PR complete? Which remain? +- **Implementation plan progress** — cross-reference the closing Task or Bug's plan. Which checklist items does this PR complete? Which were moved to follow-up delivery issues? The **Relevant issues (or links)** block is required and uses fully qualified references (`Owner/Repo#N`) so links work across repositories. -Use one bullet per linked issue. Use a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue) (`Fixes`, `Closes`, `Resolves`) when the PR fully addresses the issue. Otherwise, list the reference without a keyword to indicate a relationship without auto-closing. +Use one bullet per linked issue. Exactly one bullet uses a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue) (`Fixes`, `Closes`, or `Resolves`), and it closes the one Task or Bug delivered by the pull request. A parent PBI or Epic may appear as context without a closing keyword; never close an aggregate through a delivery pull request. Other supporting links are also non-closing. -If no issue is linked: **stop**. PRs without issues break the workflow. Route back to the Ideator role or proceed only on explicit user confirmation. The Shipper enforces this. +If there is not exactly one closing Task or Bug: **stop**. Route back to [Define](../Agents/define.md) to create or correctly route the delivery leaf. The [Issue Hierarchy](Issues/Types/Hierarchy.md) and [Issue Lifecycle](Issues/Process/Lifecycle.md) own type and closure semantics. ## Formatting - Paragraphs are written as a **single unbroken line**. GitHub renders mid-paragraph newlines as spaces. - The PR description is **the release note**. Write it for users, not reviewers. -- If a linked issue exists, the PR title and description should align with the issue's user-facing framing and the Technical Decisions section (Section 2). +- The PR title and description align with the closing Task or Bug's user-facing framing and recorded technical decisions. ## Example diff --git a/src/docs/Ways-of-Working/Spec-Driven-Development.md b/src/docs/Ways-of-Working/Spec-Driven-Development.md index ec0a7fd..ba77e1d 100644 --- a/src/docs/Ways-of-Working/Spec-Driven-Development.md +++ b/src/docs/Ways-of-Working/Spec-Driven-Development.md @@ -5,7 +5,7 @@ description: The specification is the source of truth — the spec (why and what # Spec-Driven Development -Spec-driven development treats the specification as the source of truth: the spec captures *why* a change matters and *what* it must do, and everything downstream — design, tasks, code, tests — serves it. When intent changes, the spec changes first and the rest follows. +Spec-driven development treats the specification as the source of truth: the spec captures *why* a change matters and *what* it must do, and everything downstream — design, delivery issues, code, tests — serves it. When intent changes, the spec changes first and the rest follows. This standard is the shape of a spec. It defines the artifacts of the method — the **specification** and its **design** — what each contains, at what level of detail, and how they move through the life of a change. It builds on the [evergreen documentation](Principles/Engineering-Practices.md#evergreen-documentation) principle (how a spec is written) and the [engineering practices](Principles/Engineering-Practices.md) (how we plan, build, ship, and measure). @@ -18,15 +18,15 @@ A change moves down a ladder of artifacts. Each sits at a fixed altitude, is ver | **Need** | Is this worth doing? | one line | the request or issue | — | | **Spec** | Why, what, for whom, and what "done" means | implementation-agnostic | the capability folder, in the owning repo | intent changes | | **Design** | How it is built | technical, still readable | beside its spec (`design.md`) | the approach changes | -| **Tasks** | In what steps | actionable | issues, at the levels of the [issue hierarchy](Issues/Types/Hierarchy.md) | the plan changes | +| **Delivery issues** | In what tracked outcomes and deliverables | progressively actionable | Epic and PBI aggregates, then Task and Bug leaves | the delivery plan changes | | **Code & tests** | The working expression | concrete | the codebase | continuously | ```mermaid flowchart LR need["Need\nrequest · incident · gap"] --> spec["Spec\nwhy · what · impact"] spec --> design["Design\nhow"] - design --> tasks["Tasks\nepic → item → task"] - tasks --> code["Code & tests\nthe expression"] + design --> issues["Delivery issues\nEpic → PBI → Task / Bug"] + issues --> code["Code & tests\nthe expression"] code -. "production feedback" .-> spec ``` @@ -56,7 +56,7 @@ A spec **excludes** — this is the design's job: - The task breakdown and rollout sequence. - Links to the code that fulfils it — implementations come and go. -The altitude test: push detail *down* into the design, and push scope *up* into the epic. If a sentence would change when the team picks a different library, it belongs in the design, not the spec. This is the same rule the [Issue Format](Issues/Process/Format.md) applies to issues — describe the *what* and *why*, never the *how*. +The altitude test: push implementation detail *down* into the design, and keep delivery scope at the appropriate [issue planning altitude](Issues/Process/Planning.md). If a sentence would change when the team picks a different library, it belongs in the design, not the spec. ## Specify the minimum @@ -111,9 +111,9 @@ The method is requirements-first. Work does not start from a solution; it starts 1. **A need surfaces.** A stakeholder request, an incident, or a platform gap — or the team authors one when it sees the opportunity. 2. **Draft the spec.** Capture the why, the outcome, and the requirements collaboratively. Agents draft, research context, and check the spec for ambiguity and gaps; humans supply the intent and make the calls ([AI-first development](Principles/AI-First-Development.md)). Unknowns are marked, not guessed (see [Authoring conventions](#authoring-conventions)). 3. **Review the spec as a pull request.** The spec is versioned and reviewed like any change, following [PR Format](PR-Format.md) and [Review Etiquette](Review-Etiquette.md). Review argues about intent while it is still cheap to change. -4. **Pass the readiness bar.** The spec is ready when it meets the [Definition of Ready](Definition-of-Ready-and-Done.md#definition-of-ready) — clear intent, testable acceptance criteria, no open questions that would change the approach. -5. **Design and decompose.** Write the design, then break the work into the levels of the [issue hierarchy](Issues/Types/Hierarchy.md) — an epic into smaller, independently deliverable items. -6. **Build against the spec.** Implement in thin vertical slices, test-first where it pays, to the [Definition of Done](Definition-of-Ready-and-Done.md#definition-of-done) ([engineering practices](Principles/Engineering-Practices.md)). [Test-driven development](Principles/Engineering-Practices.md#test-driven-development) is an implementation practice governed by the coding standards and the Definition of Done, not something each spec re-specifies. +4. **Accept the spec.** Resolve clarification markers, confirm testable requirements and acceptance criteria, and review the intent before committing to delivery. +5. **Design and decompose.** Write the design, then use the canonical [Issue Planning](Issues/Process/Planning.md) and [Issue Hierarchy](Issues/Types/Hierarchy.md) guidance to create Epic or PBI aggregates and ready Task or Bug delivery leaves. +6. **Build against the spec.** Pull only a ready Task or Bug into Build, implement in thin vertical slices, and finish through its applicable [Definition of Done](Definition-of-Ready-and-Done.md#definition-of-done) ([engineering practices](Principles/Engineering-Practices.md)). [Test-driven development](Principles/Engineering-Practices.md#test-driven-development) is an implementation practice governed by the coding standards and the Definition of Done, not something each spec re-specifies. 7. **Feed reality back.** Metrics and incidents update the spec, and the cycle repeats. ## Where specs and designs live diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index eb7f0d3..267e89e 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -26,9 +26,9 @@ flowchart TD subgraph CM["Context maintenance"] Cap[Capture] --> Ref[Refine] --> Pl[Plan] - Pl --> T[simple task] - Pl --> S[sub-issues] - Pl --> CL[checklist] + Pl --> Agg["Epic / PBI
decompose and aggregate"] + Agg --> Pl + Pl --> Leaf["Ready Task / Bug
delivery leaf"] end subgraph SD["Software delivery"] @@ -38,9 +38,7 @@ flowchart TD Rsp --> Rev end - T --> Bld - S --> Bld - CL --> Bld + Leaf --> Bld Rev --> Ops["Run and operate (DevOps + SRE loop)"] Ops --> Sig["Signals, errors, feedback"] Sig --> Cap @@ -61,7 +59,7 @@ A desire for change enters the system. It can come from anywhere: The goal is to **write it down** — quickly, in a GitHub issue — so it exists for the world to see and "remember". At this stage, precision is less important than existence. The issue captures the current state, the pain or opportunity, and the desired outcome. -See [Issue Format § Section 1](Issues/Process/Format.md) for structure. +See the [Issue Lifecycle](Issues/Process/Lifecycle.md) for how the issue evolves during Capture. ### Refine @@ -82,25 +80,27 @@ The goal is **shared understanding** — everyone (humans and agents) agrees on Turn the refined understanding into actionable work: - **Gap analysis** — diff the [evergreen specification](Documentation-Model.md#evergreen-and-evolutionary) for the affected capability against the current implementation. The gap is the work. -- **Decisions** — what approach will we take? What trade-offs are we making? Document them in the issue. -- **Decomposition** — if the work is large, break it into sub-issues. Each sub-issue should be deliverable in a single pull request. -- **Checklist** — for a single task, list the concrete steps in the issue body. +- **Route and decide** — assign the native type and add decisions at its [planning altitude](Issues/Process/Planning.md). +- **Decompose aggregates** — use native sub-issues for Epic and PBI children, and native dependency edges only where execution is genuinely gated. +- **Prepare delivery leaves** — refine Task and Bug children until they satisfy their type-specific readiness gate. -The plan is the contract. It drives implementation. +The issue graph is the delivery plan. Only a ready Task or Bug is eligible for Build; Epic and PBI remain in Plan while their children deliver and their aggregate criteria stay current. -See [Documentation Model](Documentation-Model.md), [Issue Format § Sections 2–3](Issues/Process/Format.md), [Issue Hierarchy](Issues/Types/Hierarchy.md). +See [Documentation Model](Documentation-Model.md), [Issue Planning](Issues/Process/Planning.md), [Issue Relationships](Issues/Process/Relationships.md), and [Issue Hierarchy](Issues/Types/Hierarchy.md). ### Build -Execute the plan: +Execute one ready, unblocked Task or Bug. For repository delivery: -1. **Branch** — create a branch (and [worktree](Git-Worktrees.md)) for the issue. -2. **Draft PR** — push early and open a draft pull request. Link it to the issue. This makes progress visible and attaches CI from the start. +1. **Branch** — create a branch (and [worktree](Git-Worktrees.md)) for the delivery leaf. +2. **Draft PR** — push early and open a draft pull request that closes exactly that Task or Bug. This makes progress visible and attaches CI from the start. 3. **Implement** — work through the checklist. One logical change per commit. Update the issue as each task completes. 4. **Test locally** — don't push known failures to CI. Push work as far inward as it can go. 5. **Self-review with automation** — run the [Copilot review loop](Contribution-Workflow.md#the-copilot-review-loop) until it reports a clean round, fixing in-scope feedback and filing follow-up issues for the rest. 6. **Ready and auto-merge** — when the change meets the [Definition of Ready for Review](Definition-of-Ready-and-Done.md#definition-of-ready-for-review), finalize the pull request per [PR Format](PR-Format.md), mark it ready, and enable auto-merge. +An audited operational Task follows its [operational completion path](Issues/Types/Task.md#operational-delivery) instead of creating a branch or pull request. + See [Commit Conventions](Commit-Conventions.md), [PR Format](PR-Format.md), [Contribution Workflow](Contribution-Workflow.md). ### Review @@ -118,11 +118,11 @@ See [Review Etiquette](Review-Etiquette.md). ### Ship -Human review approves the ready pull request and the required checks stay green, so auto-merge lands the change — squash-merged into the protected branch, its branch deleted. Where the project releases from the trunk, the merge cuts the release. +Human review approves the ready pull request and the required checks stay green, so auto-merge lands the change — squash-merged into the protected branch, its branch deleted — and closes its Task or Bug. Where the project releases from the trunk, the merge cuts the release. The pull request description becomes the release note. Write it for end users, not reviewers. -See [PR Format](PR-Format.md), [Branching and Merging](Branching-and-Merging.md#required-checks-and-auto-merge). +Parent PBI and Epic issues close separately when their native children and aggregate acceptance criteria are complete. See [PR Format](PR-Format.md), [Issue Lifecycle](Issues/Process/Lifecycle.md), and [Branching and Merging](Branching-and-Merging.md#required-checks-and-auto-merge). ### Operate @@ -146,7 +146,7 @@ Planning happens at different time horizons and levels of detail: Detail increases as work moves from Later toward Now. - A single task lives in **Now / Detailed**. -- A product backlog item lives between **Now / Logical** and **Next / Detailed**. -- An epic spans **Now → Next → Later** at **Conceptual / Logical** fidelity. +- A PBI lives between **Now / Logical** and **Next / Detailed**. +- An Epic spans **Now → Next → Later** at **Conceptual / Logical** fidelity. This workflow follows the [Human–agent coexistence](Principles/AI-First-Development.md#human-agent-coexistence) principle — it is designed for humans first, with agents joining the same process rather than running a parallel one. diff --git a/src/docs/Ways-of-Working/index.md b/src/docs/Ways-of-Working/index.md index c6cfff5..4881329 100644 --- a/src/docs/Ways-of-Working/index.md +++ b/src/docs/Ways-of-Working/index.md @@ -28,12 +28,12 @@ This section documents the principles, processes, and norms that every contribut | [Repository Type Property](Repository-Type-Property.md) | How a single "Type" custom property classifies every repository in an initiative organization and drives which org-wide controls apply to it. | | [Principles](Principles/index.md) | The foundational beliefs and product mindset behind every decision. | | [Engineering Taste](Engineering-Taste.md) | The judgment that takes over when the standards run out. | -| [Goal-Setting Framework](Goal-Setting.md) | Mission, OKRs, and initiatives — strategy connected to delivery. | +| [Goal-Setting Framework](Goal-Setting.md) | Mission, OKRs, and Initiatives — the strategy above repository Epics. | | [Definition of Ready and Done](Definition-of-Ready-and-Done.md) | The three gates that bracket every piece of work. | | [Issues](Issues/index.md) | How issues are formatted and organized across the MSX ecosystem. | | [PR Format](PR-Format.md) | Pull request title, description, change types, and labels. | | [Commit Conventions](Commit-Conventions.md) | How commit messages are written. | -| [Branching and Merging](Branching-and-Merging.md) | Topic branches, pull-request-only integration, and merge models. | +| [Branching and Merging](Branching-and-Merging.md) | Delivery-leaf topic branches, pull-request-only integration, and merge models. | | [Review Etiquette](Review-Etiquette.md) | Tone, scope, severity, and how to disagree well. | | [Repository Segmentation](Repository-Segmentation.md) | What belongs in a repository, and when to split or combine. | | [README-Driven Context](Readme-Driven-Context.md) | Why the README is the front door and the source of truth. | From a840deffc38aa71eb88d601daf4754ecdba725e4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 02:10:10 +0200 Subject: [PATCH 02/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Align=20issue=20?= =?UTF-8?q?model=20consumers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Agents/define.md | 23 ++-- src/docs/Agents/implement.md | 23 ++-- src/docs/Agents/index.md | 4 +- src/docs/Agents/reviewer.md | 7 +- .../Ways-of-Working/Branching-and-Merging.md | 2 +- .../Ways-of-Working/Contribution-Workflow.md | 17 ++- .../Ways-of-Working/Documentation-Model.md | 10 +- .../Ways-of-Working/Fleet-Orchestration.md | 104 +++++++++--------- src/docs/Ways-of-Working/Git-Worktrees.md | 20 ++-- .../Principles/Purpose-and-Direction.md | 4 +- .../Ways-of-Working/Repository-Standard.md | 2 +- src/docs/Ways-of-Working/Review-Etiquette.md | 4 +- src/docs/Ways-of-Working/Workflow.md | 2 +- src/docs/Ways-of-Working/index.md | 2 +- 14 files changed, 112 insertions(+), 112 deletions(-) diff --git a/src/docs/Agents/define.md b/src/docs/Agents/define.md index 54088b8..8088735 100644 --- a/src/docs/Agents/define.md +++ b/src/docs/Agents/define.md @@ -1,11 +1,11 @@ --- title: Define -description: Capture, refine, and plan a change into an actionable issue ready for implementation. +description: Capture, route, refine, and plan work at the correct native issue altitude. --- # Define -Take something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and turn it into a planned, actionable issue. The output is either a single Task with its three sections populated, or a decomposed initiative with structured sub-issues. Define plans work; it does not build it. +Take something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and route it to the correct native issue type. The output is an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. ## When to use @@ -19,10 +19,10 @@ A description of a desired change, a feedback issue from a non-contributor (trea ### 1. Capture -Turn the input into an issue with Section 1 (context and request). +Turn the input into an issue with a provisional native type and enough local context to refine it. 1. Search for duplicates first — propose consolidation rather than creating a new issue. -2. Frame from the user's perspective per [Issue Format](../Ways-of-Working/Issues/Process/Format.md). +2. Follow the universal [Issue Format](../Ways-of-Working/Issues/Process/Format.md) and the selected type page for its body. 3. Acceptance criteria must be user-observable and testable. ### 2. Refine @@ -38,21 +38,22 @@ Ground the issue so anyone reading it agrees on what "done" means, up to the [De Decide how the work will happen and record the decisions. -1. **Task** — one deliverable, one pull request. Populate the technical decisions and the implementation plan per [Issue Format](../Ways-of-Working/Issues/Process/Format.md). -2. **Larger work** — decompose into child issues per [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md). -3. Find the minimum viable path — spike, then proof of concept, then minimum viable product, then improve. -4. Record decisions with their rationale and the alternatives considered. -5. Resolve open questions before finishing; defer anything that does not block this slice to a follow-up issue. +1. Route the work through the [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md); do not infer altitude from effort, priority, or a legacy label. +2. For an Epic or PBI, establish native containment and dependency relationships and refine the first required children to their own readiness gates. +3. For a Task or Bug, produce one executable delivery plan sized for one pull request. Use the audited operational Task path only when no repository artifact exists. +4. Find the minimum viable path — spike, then proof of concept, then minimum viable product, then improve. +5. Record decisions at the issue's [planning altitude](../Ways-of-Working/Issues/Process/Planning.md), with rationale and alternatives where they matter. +6. Resolve open questions before finishing; defer anything that does not block this slice to a follow-up issue. ## Operating rules 1. Tone is impersonal. The issue description is the source of truth; comments record what changed. 2. External references are hyperlinks. 3. Do not modify a feedback issue from a non-contributor — create an internal issue and cross-link. -4. Stop when the issue is plannable. Do not build, branch, or open pull requests — that is [Implement](implement.md). +4. Stop when the issue meets the readiness gate for its altitude. Do not build, branch, or open pull requests — that is [Implement](implement.md). ## Where this connects - [Workflow](../Ways-of-Working/Workflow.md) — the loop this opens. -- [Issue Format](../Ways-of-Working/Issues/Process/Format.md) and [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md) — issue structure and levels. +- [Issue Format](../Ways-of-Working/Issues/Process/Format.md), [Issue Planning](../Ways-of-Working/Issues/Process/Planning.md), [Issue Relationships](../Ways-of-Working/Issues/Process/Relationships.md), and [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md) — canonical issue ownership. - [Definition of Ready and Done](../Ways-of-Working/Definition-of-Ready-and-Done.md) — the readiness bar this aims for. diff --git a/src/docs/Agents/implement.md b/src/docs/Agents/implement.md index 32c1381..b8f09c5 100644 --- a/src/docs/Agents/implement.md +++ b/src/docs/Agents/implement.md @@ -1,43 +1,43 @@ --- title: Implement -description: Take a planned issue and deliver it as a review-ready pull request — branch, build, self-review, and finalize. +description: Take one ready Task or Bug and deliver it as a review-ready pull request. --- # Implement -Take a planned issue and deliver working software in a review-ready pull request. Owns the full delivery loop: branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds; it does not plan from scratch or review others' work. +Take one ready, unblocked Task or Bug and deliver working software in a review-ready pull request. Owns the full delivery loop: branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or review others' work. ## When to use -Implement an issue, build a feature, fix a bug, create a branch, open a pull request, respond to review feedback, or finalize a pull request. Given an initiative rather than a task, pick the next unfinished sub-issue. +Implement a Task, fix a Bug, create a branch, open a pull request, respond to review feedback, or finalize a pull request. Given an Initiative, Epic, or PBI, follow native containment and blocked-by relationships to a ready delivery leaf; do not treat list order as execution order. ## Input -A Task issue number or URL with its three sections populated. +A ready repository-delivery Task or Bug number or URL. An operational Task follows its canonical [operational delivery path](../Ways-of-Working/Issues/Types/Task.md#operational-delivery) instead of this pull-request flow. ## Flow ### 1. Orient -1. Read the issue fully — all three sections per [Issue Format](../Ways-of-Working/Issues/Process/Format.md). +1. Read the delivery issue fully, including its type-specific body, native parent, and dependency edges. 2. Read the repository README first per [README-Driven Context](../Ways-of-Working/Readme-Driven-Context.md). 3. Identify the stack and load the relevant [Coding Standards](../Coding-Standards/index.md). Repo-local linter config wins where it disagrees with a published standard. ### 2. Branch and draft pull request -Use [git worktrees](../Ways-of-Working/Git-Worktrees.md) for every issue. +Use [git worktrees](../Ways-of-Working/Git-Worktrees.md) for every repository-delivery Task or Bug. 1. Create a worktree from the default branch per [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md). 2. Push an initial commit and immediately open a **draft** pull request so CI attaches from the first push. -3. Link the issue with a closing keyword, and assign the pull request. +3. Close exactly that Task or Bug in the pull request. Reference a parent PBI or Epic only as non-closing context, then assign the pull request. ### 3. Build -For each task in the plan: +For each checklist item in the delivery plan: 1. Implement the change and self-review the staged diff. 2. Commit per [Commit Conventions](../Ways-of-Working/Commit-Conventions.md) — one logical change per commit. -3. Update the issue as each task completes — do not batch. +3. Update the issue as each checklist item completes — do not batch. 4. Push regularly so CI runs against current work. When the plan is wrong, stop and document the conflict in a comment, then update the plan before resuming. Out-of-scope problems go to [Define](define.md). @@ -57,13 +57,14 @@ When the change meets the [Definition of Ready for Review](../Ways-of-Working/De ## Operating rules 1. Micro-commits, one logical change each, with descriptive messages. -2. Progress is visible — issues updated as tasks complete, not in bulk. +2. Progress is visible — the delivery issue is updated as checklist items complete, not in bulk. 3. Draft pull request from the start; stay in the issue's scope. -4. Mark ready only when the change meets the Definition of Ready for Review — never with open tasks. +4. Mark ready only when the change meets the Definition of Ready for Review — never with open checklist items. 5. No planning from scratch (that is [Define](define.md)); no reviewing others' pull requests (that is [Reviewer](reviewer.md)). ## Where this connects - [Contribution Workflow](../Ways-of-Working/Contribution-Workflow.md) — the draft-first loop this runs. +- [Issue Lifecycle](../Ways-of-Working/Issues/Process/Lifecycle.md) and [Issue Relationships](../Ways-of-Working/Issues/Process/Relationships.md) — delivery-leaf eligibility and blockers. - [Definition of Ready and Done](../Ways-of-Working/Definition-of-Ready-and-Done.md) — the gate this hands off at. - [PR Format](../Ways-of-Working/PR-Format.md) and [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md) — packaging and landing. diff --git a/src/docs/Agents/index.md b/src/docs/Agents/index.md index 0d0efa6..219c39a 100644 --- a/src/docs/Agents/index.md +++ b/src/docs/Agents/index.md @@ -17,8 +17,8 @@ The lifecycle runs **Define → Implement → Review**: capture and plan the wor | Page | Description | | --- | --- | -| [Define](define.md) | Capture, refine, and plan a change into an actionable issue ready for implementation. | -| [Implement](implement.md) | Take a planned issue and deliver it as a review-ready pull request — branch, build, self-review, and finalize. | +| [Define](define.md) | Capture, route, refine, and plan work at the correct native issue altitude. | +| [Implement](implement.md) | Take one ready Task or Bug and deliver it as a review-ready pull request. | | [Reviewer](reviewer.md) | Review someone else's pull request for delivery, taste, security, and undiscussed decisions. | | [Security Reviewer](security-reviewer.md) | A structured, defensive security review that reports vulnerabilities as an actionable responsible-disclosure issue. | | [Agent Author](agent-author.md) | Create and maintain the agent role descriptions and the per-repository pointer files that reference them. | diff --git a/src/docs/Agents/reviewer.md b/src/docs/Agents/reviewer.md index 7090a58..efe1fe8 100644 --- a/src/docs/Agents/reviewer.md +++ b/src/docs/Agents/reviewer.md @@ -5,7 +5,7 @@ description: Review someone else's pull request for delivery, taste, security, a # Reviewer -Look at a pull request as the person on the other side of the contribution. Verify the work delivers the linked issue, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. The Reviewer comments and approves; it does not change code, fix CI, or merge. +Look at a pull request as the person on the other side of the contribution. Verify the work delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. The Reviewer comments and approves; it does not change code, fix CI, or merge. ## When to use @@ -17,9 +17,9 @@ A pull request number or URL. If the pull request was authored by the reviewing ## Flow -### 1. Read the issue +### 1. Read the delivery issue -The three sections are the contract. The pull request should deliver the acceptance criteria using the recorded approach. Note any gap. +Confirm the pull request closes exactly one Task or Bug, then review against that delivery leaf's acceptance criteria, decisions, and plan. Parent PBI or Epic links provide context but do not expand the pull request's scope or close the aggregate. ### 2. Read the README @@ -55,5 +55,6 @@ An approval co-signs the change, so approve once the blocking concerns are resol ## Where this connects - [Review Etiquette](../Ways-of-Working/Review-Etiquette.md) — tone, severity, and how to disagree well. +- [PR Format](../Ways-of-Working/PR-Format.md) — delivery-leaf closure and contextual aggregate links. - [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md) — who approves and how a change lands. - [Security Reviewer](security-reviewer.md) — the dedicated security pass. diff --git a/src/docs/Ways-of-Working/Branching-and-Merging.md b/src/docs/Ways-of-Working/Branching-and-Merging.md index 93759c5..68dc388 100644 --- a/src/docs/Ways-of-Working/Branching-and-Merging.md +++ b/src/docs/Ways-of-Working/Branching-and-Merging.md @@ -22,7 +22,7 @@ How changes move from a working branch into a protected branch. The model is sma ## Stacked pull requests -Use a stack when several reviewable changes depend on one another and cannot land independently. Each layer remains one Task issue, one branch, and one pull request; the stack only records their dependency and order. Independent changes use separate branches from the default branch instead. +Use a stack when several reviewable changes depend on one another and cannot land independently. Each layer remains one Task or Bug, one branch, and one pull request; the stack only records their dependency and order. Independent changes use separate branches from the default branch instead. Build the stack from its destination upward: diff --git a/src/docs/Ways-of-Working/Contribution-Workflow.md b/src/docs/Ways-of-Working/Contribution-Workflow.md index 4581076..9809968 100644 --- a/src/docs/Ways-of-Working/Contribution-Workflow.md +++ b/src/docs/Ways-of-Working/Contribution-Workflow.md @@ -10,10 +10,7 @@ puts an automated Copilot review *before* human review: the author iterates with Copilot on a **draft** pull request until it has no more feedback, then opens the pull request for people. -This is the operational "how". The conventions it builds on live in the ways of -working — [Issue Format](Issues/Process/Format.md), [PR Format](PR-Format.md), -[Branching and Merging](Branching-and-Merging.md), and -[Review Etiquette](Review-Etiquette.md). +This is the operational "how" for a repository-delivery Task or Bug. The conventions it builds on live in the ways of working — [Issue Lifecycle](Issues/Process/Lifecycle.md), [PR Format](PR-Format.md), [Branching and Merging](Branching-and-Merging.md), and [Review Etiquette](Review-Etiquette.md). Epic and PBI aggregates stay in planning and coordination rather than entering this branch-and-pull-request flow. ## The flow @@ -32,9 +29,10 @@ flowchart TD H --> M[Auto-merge lands it on approval + green checks] ``` -1. **Branch and implement.** Work on a `/` branch and keep the - per-change implementation detail in the issue and the pull request, not in the - spec or design. +1. **Branch and implement.** Pull one ready, unblocked Task or Bug into work on a + `/-` branch. Keep per-change implementation detail in + that delivery issue and the pull request, not in the spec, design, or parent + aggregate. 2. **Open the pull request as a draft.** A draft attaches CI and keeps the change out of people's review queues while you iterate. 3. **Run the Copilot review loop** (below) until Copilot reports a clean round — @@ -87,8 +85,8 @@ For each piece of feedback, decide: - **In scope** — it concerns the change under review. Address it in this pull request and push; the next round re-checks it. - **Out of scope** — it points at a pre-existing gap or an adjacent improvement. - File an issue ([Issue Format](Issues/Process/Format.md)) capturing the gap and reference - it; do not grow the pull request to cover it. + Route a follow-up through the [Issue Hierarchy](Issues/Types/Hierarchy.md) and + reference it; do not grow the pull request to cover it. - **Not actionable** — a false positive, or a matter of taste you disagree with. Reply with the reason and resolve the thread; a documented dismissal counts as handled ([Review Etiquette](Review-Etiquette.md)). @@ -122,6 +120,7 @@ the approval identity that satisfies the gate. ## Where this connects - [Workflow](Workflow.md) — the spec-led loop this operates within. +- [Issue Lifecycle](Issues/Process/Lifecycle.md) — why only ready Task and Bug leaves enter this flow. - [PR Format](PR-Format.md) — the pull request title, description, and labels. - [Definition of Ready and Done](Definition-of-Ready-and-Done.md#definition-of-ready-for-review) — the readiness gate this hands off at. - [Branching and Merging](Branching-and-Merging.md) — the branch model a pull request builds on. diff --git a/src/docs/Ways-of-Working/Documentation-Model.md b/src/docs/Ways-of-Working/Documentation-Model.md index 8eeff33..217ec03 100644 --- a/src/docs/Ways-of-Working/Documentation-Model.md +++ b/src/docs/Ways-of-Working/Documentation-Model.md @@ -27,8 +27,8 @@ mechanism, the moving parts, the configuration. Both are durable, and both evolve — neither is a one-time plan. Only the detail of a *single change* — the paths touched, the trade-off taken -this once — stays out of both, living in the [issue](Issues/Process/Format.md) and the -pull request where it belongs. +this once — stays out of both, living at the delivery leaf's canonical +[issue planning altitude](Issues/Process/Planning.md) and in its pull request. ## Capabilities live in folders @@ -57,7 +57,7 @@ two documents live with the code. | **How / what** we build to deliver it | the capability's **design** | | **How we work** — process, principles, conventions | [Ways of Working](index.md) | | **How code looks** — style applied to code | [Coding Standards](../Coding-Standards/index.md) | -| **How this one change is implemented** — paths, trade-offs | the [issue](Issues/Process/Format.md) and the PR | +| **How this one change is implemented** — paths, trade-offs | the Task or Bug delivery leaf and its PR; see [Issue Planning](Issues/Process/Planning.md) | Keeping implementation out of the spec is what makes the spec durable: implementation detail rots fastest, so the spec leaves it to the design, and the @@ -72,8 +72,8 @@ then code — and loops: 2. **Spec** — agree the next version's requirements: why it matters and what it must do. Nothing is committed to building yet. 3. **Design** — once committed to deliver, describe how and what we will build. -4. **Build** — implement, evolving the design *and* the spec as development - teaches you things. +4. **Build** — ready Task and Bug leaves implement the gap, evolving the design + *and* the spec as development teaches you things. 5. **Operate** — running the system surfaces new needs, and the loop returns. ```mermaid diff --git a/src/docs/Ways-of-Working/Fleet-Orchestration.md b/src/docs/Ways-of-Working/Fleet-Orchestration.md index a7bff83..afb5557 100644 --- a/src/docs/Ways-of-Working/Fleet-Orchestration.md +++ b/src/docs/Ways-of-Working/Fleet-Orchestration.md @@ -1,13 +1,14 @@ --- title: Fleet Orchestration -description: How one change is rolled out across many repositories — a campaign of branches, pull requests, and review loops tracked entirely on GitHub. +description: How one change is rolled out across many repositories through Task or Bug delivery leaves tracked entirely on GitHub. --- # Fleet Orchestration How a single change is applied across many repositories at once — a *campaign*. -Each repository gets its own branch, pull request, and review loop; the campaign -is the coordination layer that keeps them moving and visible. +Each repository gets its own Task or Bug delivery leaf, branch, pull request, +and review loop; the campaign is the coordination layer that keeps them moving +and visible. This is the multi-repository "how". Inside each repository the change follows the ordinary [Contribution Workflow](Contribution-Workflow.md) — draft first, the @@ -30,18 +31,16 @@ tool, can read and drive a campaign with the GitHub CLI alone. ## The campaign A campaign is one change rolled out across a set of repositories. Each -repository's slice of the change is a **work item**: a tracking issue, a pull -request, or both. A campaign has a short, stable **slug** (for example -`process-psmodule-v6`) that names it everywhere. - -A work item is usually created for the campaign, but an **existing open pull -request can be adopted** as one. When a repository already has a pull request -that does part of the change, add the remaining change to that branch and prefix -its title with the same bracketed slug (`[]`) instead of opening a duplicate — the existing pull request -*is* the work item. A separate tracking issue is optional in this case (a work -item may be a pull request alone); if one already exists, link it with -`Fixes #n` so merging still closes it. Reusing what is already open avoids two -competing pull requests touching the same files. +repository's slice is one Task or Bug delivery leaf and its pull request. A +campaign has a short, stable **slug** (for example `process-psmodule-v6`) that +names both artifacts everywhere. + +An **existing open pull request can be adopted** when its scope matches the +campaign slice. Ensure it closes exactly one correctly typed Task or Bug, +creating that delivery issue first when it is missing, then add the remaining +change to the existing branch instead of opening a duplicate. Reusing what is +already open avoids competing pull requests without bypassing the canonical +[Issue Hierarchy](Issues/Types/Hierarchy.md) or [PR Format](PR-Format.md). ## State lives on GitHub @@ -55,15 +54,15 @@ copied into another state-bearing field, because duplicated state drifts. | Fact | GitHub property | | --- | --- | -| Work item exists | the issue or pull request itself | +| Delivery exists | its Task or Bug issue | | Who holds it | `assignees` | | Work in progress | pull request is a **draft** | | Ready for a human | pull request is **not** a draft | | CI health | the status-check rollup | | Review outcome | `reviewDecision` and unresolved review threads | | Mergeability | `mergeable` / merge-state status | -| Done | pull request **merged**; tracking issue **closed** | -| Issue ↔ PR link | the pull request's closing references (`Fixes #n`) | +| Done | pull request **merged**; Task or Bug **closed** | +| Issue ↔ PR link | the pull request's one closing reference | "Ready for review" is the draft flag flipping off; "done" is the merge. The two signals people care about most are native, and are set by the same act that does @@ -71,9 +70,10 @@ the work — [marking ready](Contribution-Workflow.md) and merging. ### Campaign identity lives in the title -Campaign membership is carried in a stable square-bracket prefix on every work -item title, for example `[process-psmodule-v6]`. The prefix is the cross- -repository join key; labels stay reserved for mutable workflow state. +Campaign membership is carried in a stable square-bracket prefix on every +delivery issue and pull request title, for example `[process-psmodule-v6]`. The +prefix is the cross-repository join key; labels stay reserved for mutable +workflow state. ### Process labels fill the gap @@ -84,11 +84,11 @@ same way. | Label | Purpose | | --- | --- | -| `stage:queued` | Identified, not started (typically a tracking issue with no pull request yet). | +| `stage:queued` | Delivery issue exists; no pull request has started. | | `stage:in-progress` | Actively being changed; the assignee owns it. | | `stage:blocked` | Needs a human decision or a manual, off-platform action before it can proceed. | -Rules: the campaign prefix is mandatory on every work item; at most one +Rules: the campaign prefix is mandatory on every delivery issue and pull request; at most one `stage:*` label applies at a time; and `stage:*` may be dropped once a pull request carries the signal itself (a ready pull request needs no `stage` label, but `stage:blocked` stays explicit because "a human must act" has no built-in @@ -96,7 +96,7 @@ equivalent). The slug inside the prefix is lowercase and hyphenated. ## Effective status -A campaign view shows one **effective status** per work item, derived purely from +A campaign view shows one **effective status** per repository delivery, derived purely from the two layers above — no guessing. The first matching rule wins. | # | Effective status | Condition | @@ -108,7 +108,7 @@ the two layers above — no guessing. The first matching rule wins. | 5 | CI failing | checks are failing on a draft | | 6 | In review | draft with at least one review and CI not failing | | 7 | In progress | draft with no review yet, or `stage:in-progress` | -| 8 | Queued | tracking issue open, no pull request yet | +| 8 | Queued | Task or Bug open, no pull request yet | Terminal and attention states (merged, blocked, changes requested, ready) rank above transient progress states, because an explicit act — marking ready, or @@ -121,49 +121,46 @@ GitHub action, so the resulting state is always re-derivable. ```mermaid flowchart TD - Q[Queued: tracking issue] --> P[Open PR as draft] + Q[Queued: Task or Bug] --> P[Open PR as draft] A[Adopt existing PR: return to draft] --> R P --> R[Contribution Workflow: Copilot review loop] R -->|needs a human decision| B[Blocked] B -->|unblocked| R R -->|loop clean| Y[Mark ready for review] Y --> M[Human review and merge] - M --> D[Close tracking issue if linked] + M --> D[Close Task or Bug] ``` -1. **Queue the work.** Create a tracking issue per repository, with the campaign - prefix in the title and `stage:queued`, following the [Issue Format](Issues/Process/Format.md). - The whole fleet starts as *Queued*. Skip this for any repository whose work - item will be an **adopted pull request** (see step 2): that pull request is - the work item and needs no tracking issue. +1. **Queue the work.** Create one Task or Bug delivery issue per repository, with + the campaign prefix in the title and `stage:queued`. Route it through the + [Issue Hierarchy](Issues/Types/Hierarchy.md) and follow its canonical type + page. The whole fleet starts as *Queued*. 2. **Branch and open a draft.** Create a worktree and branch ([Git Worktrees](Git-Worktrees.md)), then open a **draft** pull request that - closes the tracking issue, per [PR Format](PR-Format.md). Use the same + closes exactly that delivery issue, per [PR Format](PR-Format.md). Use the same campaign prefix in the pull request title and move the stage to - `stage:in-progress`, clearing the tracking issue's `stage:queued` so the work - item never carries two stages at once. If the repository already has an open + `stage:in-progress`, clearing the issue's `stage:queued` so the repository + delivery never carries two stages at once. If the repository already has an open pull request that covers part of the change, adopt it instead of opening a new one: add the remaining change to its branch, return it to **draft** while work is in progress, and give it the same campaign prefix and - `stage:in-progress` label (clearing `stage:*` from any linked issue). The - adopted pull request is the work item, so a separate tracking issue is - optional — link one with `Fixes #n` if it exists; when there is none, the - tracking-issue steps (1 and the close-on-merge in 6) simply do not apply, and - the pull request's own draft and merge state carry the signal. + `stage:in-progress` label (clearing `stage:*` from its closing issue). Add the + one closing Task or Bug reference before continuing if the adopted pull + request did not already have it. 3. **Apply the change and run the loop.** Make the change and take the pull request through the [Contribution Workflow](Contribution-Workflow.md) — the Copilot review loop — exactly as any single-repository change. The [Implement](../Agents/implement.md) and [Reviewer](../Agents/reviewer.md) agent roles apply unchanged. -4. **Flag blockers, don't stall the fleet.** If an item needs a human decision or +4. **Flag blockers, don't stall the fleet.** If a delivery leaf needs a human decision or an off-platform action, set `stage:blocked` with a note and move on to the next repository. 5. **Mark ready only when the loop is clean.** When Copilot has no more feedback and CI is green, mark the pull request ready for review per the [Definition of Ready and Done](Definition-of-Ready-and-Done.md). The draft - flag flips and the item becomes *Ready for review*. -6. **Human review and merge land it.** Merging lands the change and, where the - pull request links a tracking issue, closes it via the `Fixes #n` reference. + flag flips and the repository delivery becomes *Ready for review*. +6. **Human review and merge land it.** Merging lands the change and closes its + Task or Bug through the pull request's one closing reference. The campaign's job is to get every pull request to *Ready*; [Branching and Merging](Branching-and-Merging.md) governs how it merges. @@ -193,19 +190,20 @@ concern that is invalid on one repository is usually invalid on the rest, barring a repository where the change genuinely differs. Discovery can also surface a repository where the change applies differently, or -not at all. Adapt that work item — its change and its pull request description — -to what the repository actually needs, rather than forcing an identical diff. +not at all. Adapt that delivery leaf — its scope, change, and pull request +description — to what the repository actually needs, rather than forcing an +identical diff. ## The dashboard A campaign is watched through a **dashboard** — a deterministic projection of -GitHub state, not a store of its own. A script enumerates the campaign's work -items by title prefix (`[]` across the owners), reads each pull request's +GitHub state, not a store of its own. A script enumerates the campaign's +repository deliveries by title prefix (`[]` across the owners), reads each pull request's built-in properties, computes the effective status, and renders a page. It can regenerate on an interval so the view refreshes as GitHub changes; deleting it loses nothing, because GitHub is the source of truth. -Typical columns: effective status, repository, work item (linked), draft/ready, +Typical columns: effective status, repository, delivery issue and pull request, draft/ready, CI, review decision and open-thread count, assignee, the latest progress note, and last-updated. Rows sort by status then repository name, so the items needing attention group together. *Ready for review* appears the instant a pull request @@ -219,8 +217,8 @@ source of truth that drifts. ## Progress notes To record *what* is being done — narration, not a state change — post a comment -on the work item and, when the pipeline stage changes, move the `stage:*` label. -The dashboard surfaces the latest comment as the item's note. Setting a status is +on the delivery issue and, when the pipeline stage changes, move the `stage:*` label. +The dashboard surfaces the latest comment as the delivery's note. Setting a status is a deterministic label-and-comment write, so it is scripted; the judgement of *which* status applies is the only human or agent decision. @@ -233,7 +231,7 @@ Because all state is on GitHub and the workflow labels are generic: `stage:*` labels, and merge. - **An automated system** — a scheduled workflow, a different agent framework, or a teammate's tooling — can enumerate the work with one query and pick up any - item. The state is portable and self-describing. + delivery. The state is portable and self-describing. If an automated run stops midway, nothing is lost: the board is complete, every in-flight pull request shows its true state, and anyone can finish the job. This @@ -271,7 +269,7 @@ the Pester version requirement to the test files, and migrate the tests. - **Slug:** `process-psmodule-v6`. - **Discover the fleet:** find consumers of the reusable workflow with a code - search for its `uses:` reference, then queue a tracking issue in each. + search for its `uses:` reference, then queue a Task delivery issue in each. - **Per repository:** bump the workflow pin, add the `#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' }` requirement to each `*.Tests.ps1`, migrate the tests, and take the pull request diff --git a/src/docs/Ways-of-Working/Git-Worktrees.md b/src/docs/Ways-of-Working/Git-Worktrees.md index 53aa28e..e71e6d9 100644 --- a/src/docs/Ways-of-Working/Git-Worktrees.md +++ b/src/docs/Ways-of-Working/Git-Worktrees.md @@ -5,17 +5,17 @@ description: How agentic development is implemented locally — a bare-clone and # Git Worktrees -Git worktrees are how [agentic development](Agentic-Development.md) is implemented on a local machine. They are purely a **local development** convenience: a way for one person — or a person and an agent, or several agents — to work on multiple issues in the same repository at the same time, without stashing, committing half-finished work, or switching branches. They change nothing about how a repository is built, reviewed, or shipped — that still happens through branches and pull requests, exactly as it would with an ordinary clone. +Git worktrees are how [agentic development](Agentic-Development.md) is implemented on a local machine. They are purely a **local development** convenience: a way for one person — or a person and an agent, or several agents — to work on multiple repository-delivery leaves at the same time, without stashing, committing half-finished work, or switching branches. They change nothing about how a repository is built, reviewed, or shipped — that still happens through branches and pull requests, exactly as it would with an ordinary clone. -All repositories are set up as **bare clones with worktrees**. Each piece of work gets its own worktree — an independent working directory for one branch — so parallel work never collides. +All repositories are set up as **bare clones with worktrees**. Each repository-delivery Task or Bug gets its own worktree — an independent working directory for one branch — so parallel work never collides. Epic and PBI aggregates, and operational Tasks without repository artifacts, do not get worktrees. ## Why this matters: working agentically in parallel -The reason this layout is the default — not the occasional convenience it is in most projects — is **parallelism**. [Agentic development](Agentic-Development.md) does not proceed one issue at a time. A single developer can have several agents working at once, each on a different issue, alongside their own hands-on changes. Worktrees are what make running many streams at once safe instead of chaotic: +The reason this layout is the default — not the occasional convenience it is in most projects — is **parallelism**. [Agentic development](Agentic-Development.md) does not proceed one delivery leaf at a time. A single developer can have several agents working at once, each on a different Task or Bug, alongside their own hands-on changes. Worktrees are what make running many streams at once safe instead of chaotic: -- **One worktree per issue, one agent per worktree.** Each agent gets its own working directory, its own branch, and its own uncommitted state. Two agents never write to the same checkout, so their edits cannot corrupt one another. -- **No stashing, no branch-switching, no waiting.** Because the worktrees are independent, the agent finishing issue #42 never disturbs the one still working on issue #99 — and neither touches the clean `/` reference you read from. Nobody has to reach a clean tree before anyone else can move. -- **Fan out, then integrate.** A batch of independent issues can be started together — one worktree each — worked concurrently, and merged back one at a time through the normal [branch-and-PR flow](Branching-and-Merging.md) as each finishes. +- **One worktree per repository-delivery Task or Bug, one agent per worktree.** Each agent gets its own working directory, its own branch, and its own uncommitted state. Two agents never write to the same checkout, so their edits cannot corrupt one another. +- **No stashing, no branch-switching, no waiting.** Because the worktrees are independent, the agent finishing Task #42 never disturbs the one still working on Bug #99 — and neither touches the clean `/` reference you read from. Nobody has to reach a clean tree before anyone else can move. +- **Fan out, then integrate.** A batch of independent delivery leaves can be started together — one worktree each — worked concurrently, and merged back one at a time through the normal [branch-and-PR flow](Branching-and-Merging.md) as each finishes. In a single ordinary clone the opposite is forced: one branch checked out at a time, every human and agent contending for the same files, constant stashing and switching. Worktrees turn "several issues at once" from a hazard into the default working mode — which is what makes local agentic development practical at all. @@ -23,7 +23,7 @@ In a single ordinary clone the opposite is forced: one branch checked out at a t | Problem with traditional clones | How worktrees solve it | | -------------------------------------------- | --------------------------------------------------------- | -| Only one branch checked out at a time | Each issue gets its own worktree — parallel by default | +| Only one branch checked out at a time | Each delivery leaf gets a worktree — parallel by default | | Switching branches requires clean state | Worktrees are independent — no stashing or committing WIP | | Agent work blocks human work on same repo | Different worktrees, no interference | | Default branch gets dirty during development | `/` worktree is always a clean reference | @@ -42,7 +42,7 @@ In a single ordinary clone the opposite is forced: one branch checked out at a t - **`.bare/`** — the shared git object store. All worktrees share this. - **`.git`** — a file (not a directory) that points git tooling to `.bare/`. - **`/`** — the default branch worktree (e.g. `main` or `master`). Kept as a clean reference. Used for diffing, reading docs, running comparisons. Never directly committed to. -- **`-/`** — one worktree folder per issue in flight, named by issue number and a short slug. The folder is a concise local path; its branch uses the required `/-` name, so the two names do not need to match. +- **`-/`** — one worktree folder per repository-delivery Task or Bug in flight, named by issue number and a short slug. The folder is a concise local path; its branch uses the required `/-` name, so the two names do not need to match. ## Remotes @@ -100,7 +100,7 @@ git -C .bare config "branch.$defaultBranch.merge" "refs/heads/$defaultBranch" > The [Checkout-GitHubRepo](https://github.com/MariusStorhaug/.dev/blob/main/.github/Checkout-GitHubRepo.ps1) script automates this for all repositories. -## Working on an issue +## Working on a delivery leaf ```powershell # From the repo root (where .bare/ lives) @@ -135,5 +135,5 @@ git -C .bare worktree prune ## Where this connects - [Agentic Development](Agentic-Development.md) — the framework these worktrees implement locally, so several pieces of work run in parallel. -- [Branching and Merging](Branching-and-Merging.md) — the branch-per-issue model each worktree holds. +- [Branching and Merging](Branching-and-Merging.md) — the branch-per-delivery-leaf model each worktree holds. - [Workflow](Workflow.md) — where creating a worktree fits in the flow from issue to delivery. diff --git a/src/docs/Ways-of-Working/Principles/Purpose-and-Direction.md b/src/docs/Ways-of-Working/Principles/Purpose-and-Direction.md index 686ad0a..bf38398 100644 --- a/src/docs/Ways-of-Working/Principles/Purpose-and-Direction.md +++ b/src/docs/Ways-of-Working/Principles/Purpose-and-Direction.md @@ -11,9 +11,9 @@ Every piece of work — at every level — should be groundable in three concent - **Why** — what change in the world are we trying to make? Vision. - **How** — what approach do we take to make that change happen? Mission. -- **What** — what concrete thing are we delivering right now? OKRs → initiatives → tasks. +- **What** — what concrete thing are we delivering right now? -When an issue is being written, the **Why** belongs in the Context part of Section 1. The **How** belongs in Section 2 (Technical Decisions). The **What** belongs in Section 3 (Implementation Plan). +[Goal Setting](../Goal-Setting.md) owns the strategy path from Mission through OKR and Initiative. [Issue Planning](../Issues/Process/Planning.md) applies Why, How, and What progressively from Epic and PBI aggregates to Task and Bug delivery leaves; this principle does not assign them to copied body sections. ## Product / service mindset diff --git a/src/docs/Ways-of-Working/Repository-Standard.md b/src/docs/Ways-of-Working/Repository-Standard.md index fa4f105..88ef370 100644 --- a/src/docs/Ways-of-Working/Repository-Standard.md +++ b/src/docs/Ways-of-Working/Repository-Standard.md @@ -110,7 +110,7 @@ Default title pattern: []: ``` -The description should lead with user-facing impact, continue with user-facing change sections, include optional technical details after those sections, and end with the related-issues block. +The description should lead with user-facing impact, continue with user-facing change sections, include optional technical details after those sections, and end with the related-issues block. It closes exactly one Task or Bug as required by [PR Format](PR-Format.md). Repository templates may be simpler than the full PR Manager body, but they must gather enough information to reconstruct it. diff --git a/src/docs/Ways-of-Working/Review-Etiquette.md b/src/docs/Ways-of-Working/Review-Etiquette.md index 6fbd14a..937cf9a 100644 --- a/src/docs/Ways-of-Working/Review-Etiquette.md +++ b/src/docs/Ways-of-Working/Review-Etiquette.md @@ -23,7 +23,7 @@ Consider each of the following dimensions when reviewing a PR: ### Stay in scope -The PR delivers a specific issue. Suggestions that go beyond that issue are **new issues**, not blocking review comments. File the follow-up and link it from the comment. +The PR delivers one Task or Bug. Suggestions that go beyond that delivery leaf are **new issues**, not blocking review comments. File the follow-up and link it from the comment. ### One concern per comment @@ -40,7 +40,7 @@ Use explicit prefixes so the author knows what is blocking: ### Reason from evidence -Cite the docs, the linter rule, the security advisory, the linked issue's Section 2. "It feels off" is rarely actionable. "This contradicts the decision in #N's Section 2" is. +Cite the docs, the linter rule, the security advisory, or a recorded decision in the closing delivery issue. "It feels off" is rarely actionable. "This contradicts the decision recorded in Owner/Repo#N" is. ### Don't review your own PR publicly diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index 267e89e..572ffa8 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -107,7 +107,7 @@ See [Commit Conventions](Commit-Conventions.md), [PR Format](PR-Format.md), [Con Every change gets a second perspective: -- Does the PR deliver what the linked issue asks for? +- Does the PR deliver what its closing Task or Bug asks for? - Does it follow the project's standards and conventions? - Are there security concerns or undiscussed decisions? - Is the code clear and maintainable? diff --git a/src/docs/Ways-of-Working/index.md b/src/docs/Ways-of-Working/index.md index 4881329..36d24e4 100644 --- a/src/docs/Ways-of-Working/index.md +++ b/src/docs/Ways-of-Working/index.md @@ -17,7 +17,7 @@ This section documents the principles, processes, and norms that every contribut | --- | --- | | [Workflow](Workflow.md) | How work flows from idea to delivery and back again. | | [Contribution Workflow](Contribution-Workflow.md) | How a change travels from a branch to a review-ready pull request — draft first, the Copilot review loop, then people. | -| [Fleet Orchestration](Fleet-Orchestration.md) | How one change is rolled out across many repositories — a campaign of branches, pull requests, and review loops tracked entirely on GitHub. | +| [Fleet Orchestration](Fleet-Orchestration.md) | How one change is rolled out across many repositories through Task or Bug delivery leaves tracked entirely on GitHub. | | [Documentation Model](Documentation-Model.md) | How every capability is documented — a spec for the why and a design for the how, colocated, concise, and kept evergreen for humans and agents alike. | | [Spec-Driven Development](Spec-Driven-Development.md) | The specification is the source of truth — the spec (why and what), its design (how), and how a change moves from need to shipped. | | [Evolutionary Development](Evolutionary-Development.md) | Grow software as bets under selection — variation, feedback, and survival of the fittest, run as one tight loop. | From 3f78fbd96bf7f5719147eac89652ac29d39c60b9 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 02:10:16 +0200 Subject: [PATCH 03/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Give=20propagate?= =?UTF-8?q?d=20updates=20delivery=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../downstream-release-propagation/design.md | 13 +++++++++---- .../downstream-release-propagation/spec.md | 5 +++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/docs/Capabilities/downstream-release-propagation/design.md b/src/docs/Capabilities/downstream-release-propagation/design.md index 6e8af4f..2245f48 100644 --- a/src/docs/Capabilities/downstream-release-propagation/design.md +++ b/src/docs/Capabilities/downstream-release-propagation/design.md @@ -9,7 +9,9 @@ The notification runs in the **producer** when a release is cut. It resolves the release coordinates, builds a self-contained prompt per dependent, and delegates the change to a cloud agent **in the dependent** via the [Agent Tasks API](https://docs.github.com/rest/agent-tasks/agent-tasks). The -brief travels entirely in the prompt; the agent opens the pull request directly. +brief travels entirely in the prompt. The agent first creates or reuses the +dependent's Task delivery issue, then opens the pull request with that Task as +its one closing reference. ```mermaid flowchart TD @@ -17,7 +19,8 @@ flowchart TD notify --> resolve["Resolve version + immutable ref (SHA / digest) + notes"] resolve --> fan{"For each dependent"} fan --> delegate["Create agent task in dependent
self-contained prompt with full context"] - delegate --> pr["Agent opens PR: bump + related fixes + impact"] + delegate --> issue["Create or reuse Task delivery issue"] + issue --> pr["Agent opens closing PR: bump + related fixes + impact"] pr --> review["Human review + merge"] ``` @@ -71,11 +74,12 @@ a single configured `notify_repo` (published-artifact shape), with ## Agent instructions -The agent is told to: **apply the bump** (every matching reference, bringing any +The agent is told to: **create or reuse one Task delivery issue** for the +dependent's slice; **apply the bump** (every matching reference, bringing any mutable-tag pins into SHA-pinned compliance); **apply the related changes it can make safely**; **call out** larger or riskier work under a follow-up section rather than forcing it into the bump; **summarise impact** in the PR body; and -**open the pull request**. +**open the pull request** with exactly that Task as its closing issue. ## Permissions and credentials @@ -107,3 +111,4 @@ and a release it publishes cannot trigger a `release:` workflow. So the job: - [Release Management](../release-management/design.md) — produces the release and note this consumes. - [GitHub Actions](../../Coding-Standards/GitHub-Actions.md) — SHA pinning, least-privilege permissions, explicit secret passing. - [Security](../../Coding-Standards/Security.md#supply-chain) — the supply-chain rationale for immutable references. +- [PR Format](../../Ways-of-Working/PR-Format.md) — the delivery-leaf closure contract used by the agent. diff --git a/src/docs/Capabilities/downstream-release-propagation/spec.md b/src/docs/Capabilities/downstream-release-propagation/spec.md index ca1d351..e0cbb47 100644 --- a/src/docs/Capabilities/downstream-release-propagation/spec.md +++ b/src/docs/Capabilities/downstream-release-propagation/spec.md @@ -37,13 +37,13 @@ Two shapes occur; both are the same mechanism with a different artifact: - **Automatic on stable release.** A stable producer release MUST trigger propagation to every declared dependent. Prereleases MUST NOT propagate. - **Full context, not just a number.** Each dependent receives the new version, the immutable reference (commit SHA or image digest), the release notes, and any related-change context the update implies. -- **A PR per dependent, opened by an agent.** The mechanical work — the bump plus the fixes that make it work — is delegated to a cloud agent *in the dependent*, which opens the pull request directly. No tracking issue is created. +- **A Task and PR per dependent, opened by an agent.** The mechanical work — the bump plus the fixes that make it work — is delegated to a cloud agent *in the dependent*. The agent creates or reuses one Task delivery leaf and opens a pull request that closes exactly that Task. - **Humans decide.** A human reviews and merges each PR; the agent applies what it can safely do now and calls out larger or riskier work as follow-up. - **Backfill on demand.** Propagation MUST be re-runnable for a specific release — for a missed event, or a dependent added after the release. ## Success criteria -- A stable release yields one PR in each declared dependent, carrying the immutable reference and an impact summary, with no manual tracking. +- A stable release yields one Task and closing PR in each declared dependent, carrying the immutable reference and an impact summary without manual coordination. - A prerelease yields none. - A dependent added after a release can be back-filled without cutting a new release. @@ -52,3 +52,4 @@ Two shapes occur; both are the same mechanism with a different artifact: - [Design](design.md) — how these requirements are delivered. - [Release Management](../release-management/spec.md) — the release this propagates. - [Dependency Updates](../dependency-updates/spec.md) — the inbound counterpart, for external dependencies. +- [Issue Hierarchy](../../Ways-of-Working/Issues/Types/Hierarchy.md) and [PR Format](../../Ways-of-Working/PR-Format.md) — the delivery leaf and closure rules this automation follows. From 133ad22bbab5d569d60c3cf0aad838a9a7375c71 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 02:14:38 +0200 Subject: [PATCH 04/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Clarify=20delive?= =?UTF-8?q?ry=20checklist=20terminology?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Ways-of-Working/Branching-and-Merging.md | 2 +- src/docs/Ways-of-Working/Workflow.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/Ways-of-Working/Branching-and-Merging.md b/src/docs/Ways-of-Working/Branching-and-Merging.md index 68dc388..28eeae2 100644 --- a/src/docs/Ways-of-Working/Branching-and-Merging.md +++ b/src/docs/Ways-of-Working/Branching-and-Merging.md @@ -35,7 +35,7 @@ Every layer follows the ordinary [Contribution Workflow](Contribution-Workflow.m 1. **Plan the dependency.** Give each layer its own Task or Bug issue. Add a native blocked-by edge from every dependent leaf to its prerequisite as described in [Issue Relationships](Issues/Process/Relationships.md#execution-order); prose, sub-issue order, and stack position do not create the gate. 2. **Open every layer as a draft.** After the initial commit, push the branch and open its pull request immediately. Use the standard user-facing title and description from [PR Format](PR-Format.md); do not add stack position or an internal branch name to the title. -3. **Link the stack in Technical Details.** Add fully qualified pull request links for the immediate dependency and dependent, using `Depends on Owner/Repo#N` and `Followed by Owner/Repo#N`. Each pull request closes only its own Task issue. +3. **Link the stack in Technical Details.** Add fully qualified pull request links for the immediate dependency and dependent, using `Depends on Owner/Repo#N` and `Followed by Owner/Repo#N`. Each pull request closes only its own Task or Bug. 4. **Keep each delta isolated.** The pull request diff against its current base contains only that layer's change. Run its tests, checks, and automated review even when an earlier layer already exercised the combined code. 5. **Make one layer ready at a time.** Only the lowest unmerged layer is marked ready and given auto-merge. Every later layer stays draft, even when its current checks are green. 6. **Advance after merge.** When the lowest layer lands, refresh the next branch against the landed target, retarget its pull request to that target, and verify that the diff still contains only its intended change. Run CI and the automated review loop again before marking it ready. diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index 572ffa8..04281aa 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -94,7 +94,7 @@ Execute one ready, unblocked Task or Bug. For repository delivery: 1. **Branch** — create a branch (and [worktree](Git-Worktrees.md)) for the delivery leaf. 2. **Draft PR** — push early and open a draft pull request that closes exactly that Task or Bug. This makes progress visible and attaches CI from the start. -3. **Implement** — work through the checklist. One logical change per commit. Update the issue as each task completes. +3. **Implement** — work through the checklist. One logical change per commit. Update the issue as each checklist item completes. 4. **Test locally** — don't push known failures to CI. Push work as far inward as it can go. 5. **Self-review with automation** — run the [Copilot review loop](Contribution-Workflow.md#the-copilot-review-loop) until it reports a clean round, fixing in-scope feedback and filing follow-up issues for the rest. 6. **Ready and auto-merge** — when the change meets the [Definition of Ready for Review](Definition-of-Ready-and-Done.md#definition-of-ready-for-review), finalize the pull request per [PR Format](PR-Format.md), mark it ready, and enable auto-merge. From eab23ca5a549b9f867e42bd5110fc6446a921c64 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 02:18:41 +0200 Subject: [PATCH 05/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Clarify=20propag?= =?UTF-8?q?ation=20delivery=20pair?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Capabilities/downstream-release-propagation/spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/Capabilities/downstream-release-propagation/spec.md b/src/docs/Capabilities/downstream-release-propagation/spec.md index e0cbb47..dee44d8 100644 --- a/src/docs/Capabilities/downstream-release-propagation/spec.md +++ b/src/docs/Capabilities/downstream-release-propagation/spec.md @@ -37,13 +37,13 @@ Two shapes occur; both are the same mechanism with a different artifact: - **Automatic on stable release.** A stable producer release MUST trigger propagation to every declared dependent. Prereleases MUST NOT propagate. - **Full context, not just a number.** Each dependent receives the new version, the immutable reference (commit SHA or image digest), the release notes, and any related-change context the update implies. -- **A Task and PR per dependent, opened by an agent.** The mechanical work — the bump plus the fixes that make it work — is delegated to a cloud agent *in the dependent*. The agent creates or reuses one Task delivery leaf and opens a pull request that closes exactly that Task. +- **A Task and a PR per dependent, opened by an agent.** The mechanical work — the bump plus the fixes that make it work — is delegated to a cloud agent *in the dependent*. The agent creates or reuses one Task delivery leaf and opens a pull request that closes exactly that Task. - **Humans decide.** A human reviews and merges each PR; the agent applies what it can safely do now and calls out larger or riskier work as follow-up. - **Backfill on demand.** Propagation MUST be re-runnable for a specific release — for a missed event, or a dependent added after the release. ## Success criteria -- A stable release yields one Task and closing PR in each declared dependent, carrying the immutable reference and an impact summary without manual coordination. +- A stable release yields one Task and one closing PR in each declared dependent, carrying the immutable reference and an impact summary without manual coordination. - A prerelease yields none. - A dependent added after a release can be back-filled without cutting a new release. From a71bd387c0b36d3b9ec74b86855c87fce4b7da1f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 08:53:26 +0200 Subject: [PATCH 06/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Frame=20agent=20?= =?UTF-8?q?guidance=20as=20workflow=20stages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Agents/agent-author.md | 28 ++++++------- src/docs/Agents/define.md | 12 +++--- src/docs/Agents/implement.md | 12 +++--- src/docs/Agents/index.md | 41 +++++++++++++------ src/docs/Agents/reviewer.md | 18 ++++---- src/docs/Agents/security-reviewer.md | 10 ++--- .../Frameworks/Agentic-Development/design.md | 6 +-- .../Agentic-Development/memory-template.md | 4 +- .../Frameworks/Agentic-Development/spec.md | 2 +- .../Ways-of-Working/Agentic-Development.md | 4 +- .../Ways-of-Working/Fleet-Orchestration.md | 4 +- .../Principles/AI-First-Development.md | 4 +- src/docs/index.md | 2 +- src/zensical.toml | 8 ++-- 14 files changed, 86 insertions(+), 69 deletions(-) diff --git a/src/docs/Agents/agent-author.md b/src/docs/Agents/agent-author.md index 8d3db9e..dee1c2e 100644 --- a/src/docs/Agents/agent-author.md +++ b/src/docs/Agents/agent-author.md @@ -1,27 +1,27 @@ --- -title: Agent Author -description: Create and maintain the agent role descriptions and the per-repository pointer files that reference them. +title: Maintain Agent Workflow +description: Maintain the shared workflow stages and the thin repository pointers that reference them. --- -# Agent Author +# Maintain Agent Workflow -Create and maintain the agent roles in this section, and the per-repository pointer files that reference them. Every role description is grounded in the [Ways of Working](../Ways-of-Working/index.md); every pointer file stays thin. Agent Author keeps descriptions and pointers honest — it does not encode standards into either. +Maintain the workflow stages in this section and the per-repository pointer files that reference them. Every stage description is grounded in the [Ways of Working](../Ways-of-Working/index.md), and every pointer file stays thin. This maintenance path keeps the workflow and pointers honest; it does not encode standards into either. -## When to use +## Enter this maintenance path when -Create a new agent role, update an existing one, review agent quality, or refactor a bloated agent file back into a thin pointer over the docs. +Create a new workflow stage, update an existing stage, review workflow quality, or refactor a bloated agent file back into a thin pointer over the docs. ## Flow ### 1. Gather requirements -1. Identify the role — the single job it owns, and its boundary. -2. Identify the docs pages that govern that role; confirm they exist. -3. Identify what the role must **not** do — boundaries prevent scope creep. +1. Identify the stage — its entry condition, job, handoff, and boundary. +2. Identify the docs pages that govern the stage; confirm they exist. +3. Identify what the stage must **not** do — boundaries prevent scope creep. ### 2. Author the description -Write the role as a page in this section, following the shape of its siblings: front matter (`title`, `description`), a one-paragraph role and boundary, when to use, a numbered flow, operating rules, and a "Where this connects" list. +Write the stage as a page in this section, following the shape of its siblings: front matter (`title`, `description`), a one-paragraph purpose and boundary, entry condition, input, numbered flow, operating rules, and a "Where this connects" list. - **Link, don't inline.** If a standard exists in the docs, link to it — never paste it in. - **Procedural, not conversational.** Numbered imperatives, no filler. @@ -29,19 +29,19 @@ Write the role as a page in this section, following the shape of its siblings: f ### 3. Keep pointers thin -A repository never carries a copy of a role. Its `AGENTS.md` — and the `CLAUDE.md` that imports it — point to these pages and add only repo-specific nuance and the genuinely tool-specific settings (permission scopes, model choice) that cannot be expressed as a pointer. When a new runtime is adopted, add a thin pointer; do not move process knowledge into it. See [Agentic Development](../Ways-of-Working/Agentic-Development.md). +A repository never carries a copy of the workflow. Its `AGENTS.md` — and the `CLAUDE.md` that imports it — point to these pages and add only repo-specific nuance and the genuinely tool-specific settings (permission scopes, model choice) that cannot be expressed as a pointer. When a new runtime is adopted, add a thin pointer; do not move process knowledge into it. See [Agentic Development](../Ways-of-Working/Agentic-Development.md). ### 4. Validate 1. Front-matter YAML parses cleanly. 2. Every link resolves, and the body duplicates no doc content. -3. The role is added to the navigation so its index row generates. +3. The stage is added to the navigation so its index row generates. ## Operating rules 1. Docs are the source of truth. If a standard is missing, propose adding it to the docs — do not embed it in an agent. -2. One agent, one job. Multiple roles mean multiple pages. -3. Update the navigation when adding or removing a role. +2. One stage, one job and handoff. Distinct stages use distinct pages. +3. Update the navigation when adding or removing a stage. ## Where this connects diff --git a/src/docs/Agents/define.md b/src/docs/Agents/define.md index 8088735..70544c0 100644 --- a/src/docs/Agents/define.md +++ b/src/docs/Agents/define.md @@ -1,13 +1,13 @@ --- -title: Define -description: Capture, route, refine, and plan work at the correct native issue altitude. +title: Define Stage +description: The workflow stage that captures, routes, refines, and plans work at the correct issue altitude. --- -# Define +# Define Stage -Take something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and route it to the correct native issue type. The output is an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. +Define is the first stage of the [Agent Workflow](index.md). It takes something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and routes it to the correct native issue type. The stage hands off an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. -## When to use +## Enter this stage when Capture a desire for change, write an issue, plan work, decompose an epic, refine a bug report, create sub-issues, structure a feature request, or turn feedback into a task. @@ -50,7 +50,7 @@ Decide how the work will happen and record the decisions. 1. Tone is impersonal. The issue description is the source of truth; comments record what changed. 2. External references are hyperlinks. 3. Do not modify a feedback issue from a non-contributor — create an internal issue and cross-link. -4. Stop when the issue meets the readiness gate for its altitude. Do not build, branch, or open pull requests — that is [Implement](implement.md). +4. Stop when the issue meets the readiness gate for its altitude. Hand a ready delivery leaf to the [Implement stage](implement.md); do not build, branch, or open pull requests here. ## Where this connects diff --git a/src/docs/Agents/implement.md b/src/docs/Agents/implement.md index b8f09c5..fad26d0 100644 --- a/src/docs/Agents/implement.md +++ b/src/docs/Agents/implement.md @@ -1,13 +1,13 @@ --- -title: Implement -description: Take one ready Task or Bug and deliver it as a review-ready pull request. +title: Implement Stage +description: The workflow stage that delivers one ready Task or Bug as a review-ready pull request. --- -# Implement +# Implement Stage -Take one ready, unblocked Task or Bug and deliver working software in a review-ready pull request. Owns the full delivery loop: branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or review others' work. +Implement is the delivery stage of the [Agent Workflow](index.md). It takes one ready, unblocked Task or Bug and produces working software in a review-ready pull request. The stage owns branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or supply the independent review. -## When to use +## Enter this stage when Implement a Task, fix a Bug, create a branch, open a pull request, respond to review feedback, or finalize a pull request. Given an Initiative, Epic, or PBI, follow native containment and blocked-by relationships to a ready delivery leaf; do not treat list order as execution order. @@ -60,7 +60,7 @@ When the change meets the [Definition of Ready for Review](../Ways-of-Working/De 2. Progress is visible — the delivery issue is updated as checklist items complete, not in bulk. 3. Draft pull request from the start; stay in the issue's scope. 4. Mark ready only when the change meets the Definition of Ready for Review — never with open checklist items. -5. No planning from scratch (that is [Define](define.md)); no reviewing others' pull requests (that is [Reviewer](reviewer.md)). +5. Return unplanned work to [Define](define.md) and hand review-ready work to [Review](reviewer.md). ## Where this connects diff --git a/src/docs/Agents/index.md b/src/docs/Agents/index.md index 219c39a..d6d592d 100644 --- a/src/docs/Agents/index.md +++ b/src/docs/Agents/index.md @@ -1,26 +1,43 @@ --- -title: Agents -description: The roles agents play across the ecosystem — authored once as documentation and pointed to from each repository. +title: Agent Workflow +description: The stages agents follow to define, implement, and review work across the ecosystem. --- -# Agents +# Agent Workflow -The roles agents play across the MSX ecosystem, authored once as documentation. Each page describes one role — its job, when to use it, and the steps it follows — grounded in the [Ways of Working](../Ways-of-Working/index.md) rather than restating them. +This is the workflow agents follow across the MSX ecosystem. Each page describes one stage: its entry condition, boundary, procedure, and handoff. The stages apply the [Ways of Working](../Ways-of-Working/index.md) without restating the standards they consume. -These descriptions are the **single source for agent behaviour**. A repository does not carry its own copy; its `AGENTS.md` and `CLAUDE.md` are thin pointers to these pages ([Agentic Development](../Ways-of-Working/Agentic-Development.md)). Humans read the same pages a new teammate would. +These stage descriptions are the **single source for agent workflow behavior**. A repository does not carry its own copy; its `AGENTS.md` and `CLAUDE.md` are thin pointers to these pages ([Agentic Development](../Ways-of-Working/Agentic-Development.md)). Humans can follow the same workflow. -The lifecycle runs **Define → Implement → Review**: capture and plan the work, build it in a pull request, then review it. Two supporting roles — Security Reviewer and Agent Author — run alongside. +## The workflow -## Contents +```mermaid +flowchart LR + D[Define] --> I[Implement] + I --> R[Review] + R -->|changes required| I + R -->|approved| Done[Merge gate] + R -->|specialized security pass| S[Security Review] + S --> R +``` + +- **Define** captures, routes, refines, and plans work until it meets the readiness gate for its issue altitude. +- **Implement** takes one ready Task or Bug through delivery and hands off a review-ready pull request. +- **Review** supplies the independent perspective and either returns actionable feedback or approves the change. +- **Security Review** is a specialized review path entered when the requested scope requires a defensive security assessment. + +[Maintain Agent Workflow](agent-author.md) is not a delivery stage. It updates these stage descriptions and the thin repository pointers when the workflow itself changes. + +## Stages and maintenance | Page | Description | | --- | --- | -| [Define](define.md) | Capture, route, refine, and plan work at the correct native issue altitude. | -| [Implement](implement.md) | Take one ready Task or Bug and deliver it as a review-ready pull request. | -| [Reviewer](reviewer.md) | Review someone else's pull request for delivery, taste, security, and undiscussed decisions. | -| [Security Reviewer](security-reviewer.md) | A structured, defensive security review that reports vulnerabilities as an actionable responsible-disclosure issue. | -| [Agent Author](agent-author.md) | Create and maintain the agent role descriptions and the per-repository pointer files that reference them. | +| [Define Stage](define.md) | The workflow stage that captures, routes, refines, and plans work at the correct issue altitude. | +| [Implement Stage](implement.md) | The workflow stage that delivers one ready Task or Bug as a review-ready pull request. | +| [Review Stage](reviewer.md) | The workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. | +| [Security Review Stage](security-reviewer.md) | A specialized workflow stage for defensive security review and responsible disclosure. | +| [Maintain Agent Workflow](agent-author.md) | Maintain the shared workflow stages and the thin repository pointers that reference them. | diff --git a/src/docs/Agents/reviewer.md b/src/docs/Agents/reviewer.md index efe1fe8..475d346 100644 --- a/src/docs/Agents/reviewer.md +++ b/src/docs/Agents/reviewer.md @@ -1,15 +1,15 @@ --- -title: Reviewer -description: Review someone else's pull request for delivery, taste, security, and undiscussed decisions. +title: Review Stage +description: The workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. --- -# Reviewer +# Review Stage -Look at a pull request as the person on the other side of the contribution. Verify the work delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. The Reviewer comments and approves; it does not change code, fix CI, or merge. +Review is the independent-assessment stage of the [Agent Workflow](index.md). It verifies that a pull request delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. This stage comments and approves; it does not change code, fix CI, or merge. -## When to use +## Enter this stage when -Review a pull request, check a change for delivery, verify acceptance criteria, or assess taste and standards. For a security-focused pass, use [Security Reviewer](security-reviewer.md). +Review a pull request, check a change for delivery, verify acceptance criteria, or assess taste and standards. For a security-focused pass, enter [Security Review](security-reviewer.md). ## Input @@ -31,7 +31,7 @@ Check each dimension per [Review Etiquette](../Ways-of-Working/Review-Etiquette. - **Delivery** — does the diff meet the acceptance criteria, without scope it did not ask for? - **Taste** — readability, naming, structure, tests that exercise behaviour. -- **Security** — input validation, no secrets in logs, SHA-pinned actions, least privilege. Escalate a deep pass to [Security Reviewer](security-reviewer.md). +- **Security** — input validation, no secrets in logs, SHA-pinned actions, least privilege. Escalate a deep pass to [Security Review](security-reviewer.md). - **Documentation** — updated where user-facing behaviour changed. - **Tests** — new behaviour has tests; bugs get regression tests. @@ -50,11 +50,11 @@ An approval co-signs the change, so approve once the blocking concerns are resol 3. One concern per comment. 4. Apply repo standards; linter rules win where a standard is silent. 5. Security is non-negotiable. -6. No code changes, no CI fixing, and no merging from the Reviewer. +6. No code changes, CI fixes, or merging in the Review stage; required changes return to [Implement](implement.md). ## Where this connects - [Review Etiquette](../Ways-of-Working/Review-Etiquette.md) — tone, severity, and how to disagree well. - [PR Format](../Ways-of-Working/PR-Format.md) — delivery-leaf closure and contextual aggregate links. - [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md) — who approves and how a change lands. -- [Security Reviewer](security-reviewer.md) — the dedicated security pass. +- [Security Review](security-reviewer.md) — the specialized security path. diff --git a/src/docs/Agents/security-reviewer.md b/src/docs/Agents/security-reviewer.md index a1e491a..6c862fd 100644 --- a/src/docs/Agents/security-reviewer.md +++ b/src/docs/Agents/security-reviewer.md @@ -1,13 +1,13 @@ --- -title: Security Reviewer -description: A structured, defensive security review that reports vulnerabilities as an actionable responsible-disclosure issue. +title: Security Review Stage +description: A specialized workflow stage for defensive security review and responsible disclosure. --- -# Security Reviewer +# Security Review Stage -A defensive security review on behalf of the code owner: identify vulnerabilities and attack vectors in source code and documentation, and produce a clear, actionable responsible-disclosure issue that remediates each finding. A defender's mindset, not an attacker's — and no working exploit code. +Security Review is a specialized path in the [Agent Workflow](index.md). On behalf of the code owner, it identifies vulnerabilities and attack vectors in source code and documentation and produces a clear, actionable responsible-disclosure issue for each finding. It uses a defender's mindset, not an attacker's, and produces no working exploit code. -## When to use +## Enter this stage when Perform a security review, audit code for vulnerabilities, threat-model a change, or review OWASP Top 10 risks — injection, secrets exposure, path traversal, privilege escalation, supply-chain risk, information disclosure. diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index c1551ed..a61bb1e 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -37,7 +37,7 @@ The `docs` repository is the canonical knowledge base. It owns: - coding standards and documentation standards; - framework and capability specs and designs; - project glossary and onboarding; -- agent role descriptions and integration guidance. +- agent workflow stages and integration guidance. Changes to `docs` happen through pull requests because this repository defines durable project intent. @@ -47,7 +47,7 @@ The `memory` repository is the durable working-memory store. It owns: - recurring gotchas and lessons learned; - active project context that should survive a single chat session; -- agent role working knowledge; +- workflow-stage working knowledge; - issue, PR, and incident notes worth reusing; - project-specific preferences that are factual rather than private user preference. @@ -260,7 +260,7 @@ Different clients load different files, but the framework keeps the same depende 1. Create or identify the organization `docs` repository. 2. Create or identify the organization `memory` repository, using the [Memory Repository Template](memory-template.md) as the starting scaffold. 3. Add `docs/index.md` and `memory/index.md` as the two root maps. -4. Add framework docs, standards, and agent role descriptions to `docs`. +4. Add framework docs, standards, and agent workflow stages to `docs`. 5. Add starter memory sections to `memory`. 6. Add thin pointer files to each product repository. 7. Add a bootstrap that keeps local docs and memory clones present. diff --git a/src/docs/Frameworks/Agentic-Development/memory-template.md b/src/docs/Frameworks/Agentic-Development/memory-template.md index ec45094..c3cc042 100644 --- a/src/docs/Frameworks/Agentic-Development/memory-template.md +++ b/src/docs/Frameworks/Agentic-Development/memory-template.md @@ -27,7 +27,7 @@ memory/ ├── knowledge/ # durable facts about the ecosystem, tools, cross-repo relationships │ ├── index.md │ └── repos/ # one file per repo worth remembering repo-specific facts about (created lazily as needed) -└── agents/ # per-agent-role working knowledge; empty stub until agent roles are formally defined +└── agents/ # per-workflow-stage knowledge; empty stub until stage-specific lessons exist └── index.md ``` @@ -43,7 +43,7 @@ top-level folder is one of those responsibilities made concrete: | --- | --- | | `gotchas/` | Recurring gotchas and lessons learned. | | `knowledge/` | Active project context that should survive a single chat session, project-specific preferences that are factual rather than private user preference, and issue/PR/incident notes worth reusing. | -| `agents/` | Agent role working knowledge. | +| `agents/` | Agent workflow-stage working knowledge. | `index.md` is the root map described in [Design's indexes section](design.md#indexes-as-the-mindmap): it links to `gotchas/index.md`, `knowledge/index.md`, and `agents/index.md` so a human or diff --git a/src/docs/Frameworks/Agentic-Development/spec.md b/src/docs/Frameworks/Agentic-Development/spec.md index 1c2eb47..35d5b02 100644 --- a/src/docs/Frameworks/Agentic-Development/spec.md +++ b/src/docs/Frameworks/Agentic-Development/spec.md @@ -12,7 +12,7 @@ An agent does useful work only when it knows which project it is serving, which Each organization owns two canonical repositories: - `docs` — the reviewed knowledge base: vision, standards, workflows, specs, designs, glossary, onboarding, and project-wide rules. -- `memory` — the durable agent working memory: lessons learned, recurring gotchas, active context, agent role knowledge, and project-specific operating notes. +- `memory` — the durable agent working memory: lessons learned, recurring gotchas, active context, workflow-stage knowledge, and project-specific operating notes. Product repositories do not copy that knowledge. They carry thin pointer files that identify the organization context and direct agents to the relevant `docs` and `memory` roots before acting. diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index 184f70d..8adf736 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -55,7 +55,7 @@ This split follows [Repository Segmentation](Repository-Segmentation.md) and [RE Agent context is delivered through three layers, in priority order — the same three layers the [Principles](Principles/AI-First-Development.md#human-agent-coexistence) describe: 1. **Documentation.** The primary source. The published docs, READMEs, and issue bodies are written for humans and read natively by agents. -2. **Central agent descriptions.** The roles agents play — Define, Implement, Reviewer, and the rest — are authored once as documentation in the [Agents](../Agents/index.md) section. They describe roles, boundaries, and procedural steps, and they reference the ways of working; they never restate a standard or convention. +2. **Central agent workflow.** The stages agents follow — Define, Implement, Review, and specialized paths — are authored once as documentation in the [Agent Workflow](../Agents/index.md) section. They describe entry conditions, boundaries, procedures, and handoffs, and they reference the ways of working; they never restate a standard or convention. 3. **Local pointer files.** Each repository carries an `AGENTS.md` — read natively by most agent runtimes — and a `CLAUDE.md` that imports it, pointing to the central descriptions and adding only repo-specific nuance and the small amount of genuinely tool-specific configuration (permission scopes, path-scoped rules) that cannot be expressed as a pointer. Any new runtime follows the same pattern, regardless of vendor: @@ -70,7 +70,7 @@ The context file and the entry points are pointers; the settings are the only ge The two non-documentation layers have different distribution models: -- **Central agent descriptions** live in the [Agents](../Agents/index.md) section of this site and are referenced by canonical URL — one definition, available to every repository and runtime with no per-repo copy to maintain. +- **The central agent workflow** lives in the [Agent Workflow](../Agents/index.md) section of this site and is referenced by canonical URL — one process, available to every repository and runtime with no per-repo copy to maintain. - **Per-repository pointer files** — `AGENTS.md`, the `CLAUDE.md` that imports it, and any path-scoped instruction files — are seeded from a template repository and kept current across existing repositories by a sync mechanism. Process knowledge is never added to a distributed config file. If an agent needs the branch strategy, it goes in [Branching and Merging](Branching-and-Merging.md) or the repo's `CONTRIBUTING.md`; if it needs a coding convention, it goes in the relevant [coding standard](../Coding-Standards/index.md). The config file only points — it never defines. diff --git a/src/docs/Ways-of-Working/Fleet-Orchestration.md b/src/docs/Ways-of-Working/Fleet-Orchestration.md index afb5557..cb9d036 100644 --- a/src/docs/Ways-of-Working/Fleet-Orchestration.md +++ b/src/docs/Ways-of-Working/Fleet-Orchestration.md @@ -150,8 +150,8 @@ flowchart TD 3. **Apply the change and run the loop.** Make the change and take the pull request through the [Contribution Workflow](Contribution-Workflow.md) — the Copilot review loop — exactly as any single-repository change. The - [Implement](../Agents/implement.md) and [Reviewer](../Agents/reviewer.md) - agent roles apply unchanged. + [Implement](../Agents/implement.md) and [Review](../Agents/reviewer.md) + workflow stages apply unchanged. 4. **Flag blockers, don't stall the fleet.** If a delivery leaf needs a human decision or an off-platform action, set `stage:blocked` with a note and move on to the next repository. diff --git a/src/docs/Ways-of-Working/Principles/AI-First-Development.md b/src/docs/Ways-of-Working/Principles/AI-First-Development.md index 5f9f10d..6dd08dd 100644 --- a/src/docs/Ways-of-Working/Principles/AI-First-Development.md +++ b/src/docs/Ways-of-Working/Principles/AI-First-Development.md @@ -32,7 +32,7 @@ Agents are trained to read documentation. That is their natural skill. By keepin Agent context is delivered through three layers, in priority order: 1. **Documentation** — the primary source. Published docs at , READMEs, and issue bodies are written for humans and naturally consumable by agents. -2. **Central agent descriptions** — the roles agents play, authored once as documentation in the [Agents](../../Agents/index.md) section. They reference the ways of working and define roles, boundaries, and procedural steps — never standards or conventions. +2. **Central agent workflow** — the stages agents follow, authored once as documentation in the [Agent Workflow](../../Agents/index.md) section. They reference the ways of working and define entry conditions, boundaries, procedures, and handoffs — never standards or conventions. 3. **Local pointer files** — each repository's `AGENTS.md` (and the `CLAUDE.md` that imports it), pointing to the central descriptions and adding only repo-specific nuance and genuinely tool-specific settings. ## Augmentation, not replacement @@ -45,7 +45,7 @@ Treat the agent ecosystem as one team mate. Many specialized roles, one cohesive ## Self-improving agents -Agents need feedback and a way to process it. Every agent definition should evolve as we learn. Capture lessons in the agent definitions and in this docs section — don't let them live only in someone's head. +Agents need feedback and a way to process it. Every workflow stage should evolve as we learn. Capture lessons in the stage descriptions and in this docs section — don't let them live only in someone's head. ## Integration and sensoring diff --git a/src/docs/index.md b/src/docs/index.md index c5259c7..fdb2981 100644 --- a/src/docs/index.md +++ b/src/docs/index.md @@ -47,7 +47,7 @@ Each area below carries its own index. Start here, read the descriptions, and fo | [Vision](Vision/index.md) | The mission and the philosophy of easy, fast, and safe. | | [Initiatives](Initiatives/index.md) | The products that make the vision real — PSModule, AzActions, TFActions, and the tools built along the way. | | [Ways of Working](Ways-of-Working/index.md) | How work happens across the ecosystem — for humans and agents alike. | -| [Agents](Agents/index.md) | The roles agents play across the ecosystem — authored once as documentation and pointed to from each repository. | +| [Agent Workflow](Agents/index.md) | The stages agents follow to define, implement, and review work across the ecosystem. | | [Coding Standards](Coding-Standards/index.md) | The shared baseline every repository inherits, and the per-language standards that build on it. | | [Capabilities](Capabilities/index.md) | The capabilities the ecosystem builds, each documented by a spec (why and what) and a design (how and what). | | [Frameworks](Frameworks/index.md) | The complete, end-to-end frameworks the ecosystem ships — opinionated automation a project adopts wholesale to go from source to shipped. | diff --git a/src/zensical.toml b/src/zensical.toml index 667f6f0..39199c6 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -77,13 +77,13 @@ nav = [ {"Continuous Practices" = "Ways-of-Working/Continuous-Practices.md"}, {"DevOps Reference" = "Ways-of-Working/DevOps-Reference.md"}, ]}, - {"Agents" = [ + {"Agent Workflow" = [ "Agents/index.md", {"Define" = "Agents/define.md"}, {"Implement" = "Agents/implement.md"}, - {"Reviewer" = "Agents/reviewer.md"}, - {"Security Reviewer" = "Agents/security-reviewer.md"}, - {"Agent Author" = "Agents/agent-author.md"}, + {"Review" = "Agents/reviewer.md"}, + {"Security Review" = "Agents/security-reviewer.md"}, + {"Maintain Workflow" = "Agents/agent-author.md"}, ]}, {"Coding Standards" = [ "Coding-Standards/index.md", From 9ae0d89c5cf4ae5aec8b3290777ce6d700438509 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 08:59:46 +0200 Subject: [PATCH 07/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Remove=20stale?= =?UTF-8?q?=20agent=20role=20framing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/AGENTS.template.md | 2 +- bootstrap/README.md | 2 +- src/docs/Ways-of-Working/Principles/AI-First-Development.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 13f7014..deab7e1 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -23,7 +23,7 @@ pwsh (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') This produces: -- `~/.msx/docs` — how work is done: ways of working, coding standards, and agent roles. The same content published at . +- `~/.msx/docs` — how work is done: ways of working, coding standards, and the agent workflow. The same content published at . - `~/.msx/memory` — what has been learned before: durable notes and prior session context. Each clone has repository-local git config only; it never modifies the global git config or the repository being worked in (git still reads them, but only repository-local config is written). diff --git a/bootstrap/README.md b/bootstrap/README.md index ad93387..af2de5d 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -9,7 +9,7 @@ The single starting point for agents: a git-isolated local clone of the MSX cent ## The model -- `~/.msx/docs` is **read context** — the ways of working, coding standards, and agent roles. Changes to it go through **pull requests**. +- `~/.msx/docs` is **read context** — the ways of working, coding standards, and agent workflow. Changes to it go through **pull requests**. - `~/.msx/memory` is **append-only context** — durable notes and session history. Changes to it are **pushed to main**. > **Prerequisite:** `MSXOrg/memory` is a private repository — the bootstrap needs access to it (and working github.com credentials) to clone or update memory. diff --git a/src/docs/Ways-of-Working/Principles/AI-First-Development.md b/src/docs/Ways-of-Working/Principles/AI-First-Development.md index 6dd08dd..0449e23 100644 --- a/src/docs/Ways-of-Working/Principles/AI-First-Development.md +++ b/src/docs/Ways-of-Working/Principles/AI-First-Development.md @@ -39,9 +39,9 @@ Agent context is delivered through three layers, in priority order: Agents amplify the team. They make us faster, more consistent, and free us from work that is mechanical. **Human in the loop** remains the default for decisions that matter. -## Persona, not swarm +## One workflow, not a swarm -Treat the agent ecosystem as one team mate. Many specialized roles, one cohesive bank of knowledge, one consistent voice. +Treat the agent ecosystem as one teammate following one shared workflow. Stages and specialized review paths divide responsibility while preserving one cohesive bank of knowledge and one consistent voice. ## Self-improving agents From a5ba29c9ccb1d9cb64c7435ea622e09d80b03ce8 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:38:00 +0200 Subject: [PATCH 08/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Route=20work=20t?= =?UTF-8?q?hrough=20workflow=20stages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Agents/agent-author.md | 10 +++---- src/docs/Agents/define.md | 8 +++--- src/docs/Agents/implement.md | 8 +++--- src/docs/Agents/index.md | 41 ++++++++-------------------- src/docs/Agents/reviewer.md | 8 +++--- src/docs/Agents/security-reviewer.md | 8 +++--- src/docs/Ways-of-Working/Workflow.md | 16 ++++++++++- src/docs/Ways-of-Working/index.md | 2 +- src/docs/index.md | 2 +- src/zensical.toml | 20 ++++++++------ 10 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/docs/Agents/agent-author.md b/src/docs/Agents/agent-author.md index dee1c2e..ea693e5 100644 --- a/src/docs/Agents/agent-author.md +++ b/src/docs/Agents/agent-author.md @@ -1,15 +1,15 @@ --- -title: Maintain Agent Workflow -description: Maintain the shared workflow stages and the thin repository pointers that reference them. +title: Maintain Workflow Guidance +description: Maintain the canonical Workflow stage procedures and the thin repository pointers that lead to them. --- -# Maintain Agent Workflow +# Maintain Workflow Guidance -Maintain the workflow stages in this section and the per-repository pointer files that reference them. Every stage description is grounded in the [Ways of Working](../Ways-of-Working/index.md), and every pointer file stays thin. This maintenance path keeps the workflow and pointers honest; it does not encode standards into either. +Maintain the canonical [Workflow](../Ways-of-Working/Workflow.md), the stage procedures in this section, and the per-repository pointer files that lead to the documentation root. Every pointer file stays thin. This maintenance path keeps the indexes, workflow, procedures, and pointers coherent; it does not encode process knowledge in configuration. ## Enter this maintenance path when -Create a new workflow stage, update an existing stage, review workflow quality, or refactor a bloated agent file back into a thin pointer over the docs. +Create a new workflow stage, update an existing stage, review workflow discoverability, or refactor a bloated agent file back into a thin pointer to the documentation root. ## Flow diff --git a/src/docs/Agents/define.md b/src/docs/Agents/define.md index 70544c0..a79fc1b 100644 --- a/src/docs/Agents/define.md +++ b/src/docs/Agents/define.md @@ -1,11 +1,11 @@ --- -title: Define Stage -description: The workflow stage that captures, routes, refines, and plans work at the correct issue altitude. +title: Define +description: Procedure for the Workflow stage that captures, routes, refines, and plans work at the correct issue altitude. --- -# Define Stage +# Define -Define is the first stage of the [Agent Workflow](index.md). It takes something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and routes it to the correct native issue type. The stage hands off an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. +Define is the first stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It takes something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and routes it to the correct native issue type. The stage hands off an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. ## Enter this stage when diff --git a/src/docs/Agents/implement.md b/src/docs/Agents/implement.md index fad26d0..aa22fb5 100644 --- a/src/docs/Agents/implement.md +++ b/src/docs/Agents/implement.md @@ -1,11 +1,11 @@ --- -title: Implement Stage -description: The workflow stage that delivers one ready Task or Bug as a review-ready pull request. +title: Implement +description: Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. --- -# Implement Stage +# Implement -Implement is the delivery stage of the [Agent Workflow](index.md). It takes one ready, unblocked Task or Bug and produces working software in a review-ready pull request. The stage owns branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or supply the independent review. +Implement is the delivery stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It takes one ready, unblocked Task or Bug and produces working software in a review-ready pull request. The stage owns branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or supply the independent review. ## Enter this stage when diff --git a/src/docs/Agents/index.md b/src/docs/Agents/index.md index d6d592d..79bcb01 100644 --- a/src/docs/Agents/index.md +++ b/src/docs/Agents/index.md @@ -1,43 +1,26 @@ --- -title: Agent Workflow -description: The stages agents follow to define, implement, and review work across the ecosystem. +title: Workflow Stages +description: Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. --- -# Agent Workflow +# Workflow Stages -This is the workflow agents follow across the MSX ecosystem. Each page describes one stage: its entry condition, boundary, procedure, and handoff. The stages apply the [Ways of Working](../Ways-of-Working/index.md) without restating the standards they consume. +The canonical [Workflow](../Ways-of-Working/Workflow.md) owns the process, stage routing, and transitions. This section contains only the procedure for each stage: its entry condition, boundary, actions, and handoff. Humans and agents follow the same pages. -These stage descriptions are the **single source for agent workflow behavior**. A repository does not carry its own copy; its `AGENTS.md` and `CLAUDE.md` are thin pointers to these pages ([Agentic Development](../Ways-of-Working/Agentic-Development.md)). Humans can follow the same workflow. +The default discovery trail is [Home](../index.md) → [Ways of Working](../Ways-of-Working/index.md) → [Workflow](../Ways-of-Working/Workflow.md) → this stage index → the resolved procedure. A task that clearly names its action may use the [Workflow routing table](../Ways-of-Working/Workflow.md#find-the-current-stage) as a shortcut. -## The workflow +No stage needs its own skill, command, named agent, or tool-specific process file. An integration may link directly to a stage for convenience, but the documentation remains the only process definition. [Maintain Workflow Guidance](agent-author.md) keeps these procedures and thin repository pointers aligned. -```mermaid -flowchart LR - D[Define] --> I[Implement] - I --> R[Review] - R -->|changes required| I - R -->|approved| Done[Merge gate] - R -->|specialized security pass| S[Security Review] - S --> R -``` - -- **Define** captures, routes, refines, and plans work until it meets the readiness gate for its issue altitude. -- **Implement** takes one ready Task or Bug through delivery and hands off a review-ready pull request. -- **Review** supplies the independent perspective and either returns actionable feedback or approves the change. -- **Security Review** is a specialized review path entered when the requested scope requires a defensive security assessment. - -[Maintain Agent Workflow](agent-author.md) is not a delivery stage. It updates these stage descriptions and the thin repository pointers when the workflow itself changes. - -## Stages and maintenance +## Procedures | Page | Description | | --- | --- | -| [Define Stage](define.md) | The workflow stage that captures, routes, refines, and plans work at the correct issue altitude. | -| [Implement Stage](implement.md) | The workflow stage that delivers one ready Task or Bug as a review-ready pull request. | -| [Review Stage](reviewer.md) | The workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. | -| [Security Review Stage](security-reviewer.md) | A specialized workflow stage for defensive security review and responsible disclosure. | -| [Maintain Agent Workflow](agent-author.md) | Maintain the shared workflow stages and the thin repository pointers that reference them. | +| [Define](define.md) | Procedure for the Workflow stage that captures, routes, refines, and plans work at the correct issue altitude. | +| [Implement](implement.md) | Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. | +| [Review](reviewer.md) | Procedure for the Workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. | +| [Security Review](security-reviewer.md) | Procedure for the specialized Workflow stage for defensive security review and responsible disclosure. | +| [Maintain Workflow Guidance](agent-author.md) | Maintain the canonical Workflow stage procedures and the thin repository pointers that lead to them. | diff --git a/src/docs/Agents/reviewer.md b/src/docs/Agents/reviewer.md index 475d346..b23917f 100644 --- a/src/docs/Agents/reviewer.md +++ b/src/docs/Agents/reviewer.md @@ -1,11 +1,11 @@ --- -title: Review Stage -description: The workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. +title: Review +description: Procedure for the Workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. --- -# Review Stage +# Review -Review is the independent-assessment stage of the [Agent Workflow](index.md). It verifies that a pull request delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. This stage comments and approves; it does not change code, fix CI, or merge. +Review is the independent-assessment stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It verifies that a pull request delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. This stage comments and approves; it does not change code, fix CI, or merge. ## Enter this stage when diff --git a/src/docs/Agents/security-reviewer.md b/src/docs/Agents/security-reviewer.md index 6c862fd..c6ac702 100644 --- a/src/docs/Agents/security-reviewer.md +++ b/src/docs/Agents/security-reviewer.md @@ -1,11 +1,11 @@ --- -title: Security Review Stage -description: A specialized workflow stage for defensive security review and responsible disclosure. +title: Security Review +description: Procedure for the specialized Workflow stage for defensive security review and responsible disclosure. --- -# Security Review Stage +# Security Review -Security Review is a specialized path in the [Agent Workflow](index.md). On behalf of the code owner, it identifies vulnerabilities and attack vectors in source code and documentation and produces a clear, actionable responsible-disclosure issue for each finding. It uses a defender's mindset, not an attacker's, and produces no working exploit code. +Security Review is a specialized path in the canonical [Workflow](../Ways-of-Working/Workflow.md). On behalf of the code owner, it identifies vulnerabilities and attack vectors in source code and documentation and produces a clear, actionable responsible-disclosure issue for each finding. It uses a defender's mindset, not an attacker's, and produces no working exploit code. ## Enter this stage when diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index 04281aa..121b9bc 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -1,6 +1,6 @@ --- title: Workflow -description: How work flows from idea to delivery and back again. +description: The canonical process from idea to delivery, including how to resolve and enter each workflow stage. --- # Workflow @@ -45,6 +45,20 @@ flowchart TD Ops -->|new release observed| Ext ``` +## Find the current stage + +Start from this page after following the documentation indexes: [Home](../index.md) → [Ways of Working](index.md) → Workflow. Infer the current stage from the work and its artifacts, then read the matching [stage procedure](../Agents/index.md). A stage is a part of this workflow, not a separate agent, skill, or instruction set. + +| Current work or prompt | Enter | Continue until | +| --- | --- | --- | +| A request, signal, or issue needs to be captured, routed, refined, or planned. Prompts such as `Make this issue ` route here. | [Define](../Agents/define.md) | The issue meets the readiness gate for its altitude. | +| A ready Task or Bug needs implementation, or a pull request needs author-side changes. Prompts such as `Implement ` or `Fix ` route here. | [Implement](../Agents/implement.md) | The pull request meets the ready-for-review gate. | +| A pull request needs an independent assessment. Prompts such as `Review this PR ` route here. | [Review](../Agents/reviewer.md) | The change is approved or actionable feedback returns it to Implement. | +| The requested assessment is explicitly security-focused. Prompts such as `Security review ` route here. | [Security Review](../Agents/security-reviewer.md) | Findings are reported through the agreed channel and the review returns to its caller. | +| The workflow pages or repository pointers themselves need to change. | [Maintain Workflow Guidance](../Agents/agent-author.md) | The canonical docs and thin pointers agree. | + +Keywords are shortcuts, not a second routing system. When a prompt is ambiguous, use the current issue, pull request, branch, and completed work to identify the stage. When work crosses a stage boundary, follow the handoff and load the next procedure rather than carrying separate process instructions through the whole task. + ## Phases ### Capture diff --git a/src/docs/Ways-of-Working/index.md b/src/docs/Ways-of-Working/index.md index 36d24e4..7d3f3c9 100644 --- a/src/docs/Ways-of-Working/index.md +++ b/src/docs/Ways-of-Working/index.md @@ -15,7 +15,7 @@ This section documents the principles, processes, and norms that every contribut | Page | Description | | --- | --- | -| [Workflow](Workflow.md) | How work flows from idea to delivery and back again. | +| [Workflow](Workflow.md) | The canonical process from idea to delivery, including how to resolve and enter each workflow stage. | | [Contribution Workflow](Contribution-Workflow.md) | How a change travels from a branch to a review-ready pull request — draft first, the Copilot review loop, then people. | | [Fleet Orchestration](Fleet-Orchestration.md) | How one change is rolled out across many repositories through Task or Bug delivery leaves tracked entirely on GitHub. | | [Documentation Model](Documentation-Model.md) | How every capability is documented — a spec for the why and a design for the how, colocated, concise, and kept evergreen for humans and agents alike. | diff --git a/src/docs/index.md b/src/docs/index.md index fdb2981..33824e4 100644 --- a/src/docs/index.md +++ b/src/docs/index.md @@ -47,7 +47,7 @@ Each area below carries its own index. Start here, read the descriptions, and fo | [Vision](Vision/index.md) | The mission and the philosophy of easy, fast, and safe. | | [Initiatives](Initiatives/index.md) | The products that make the vision real — PSModule, AzActions, TFActions, and the tools built along the way. | | [Ways of Working](Ways-of-Working/index.md) | How work happens across the ecosystem — for humans and agents alike. | -| [Agent Workflow](Agents/index.md) | The stages agents follow to define, implement, and review work across the ecosystem. | +| [Workflow Stages](Agents/index.md) | Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. | | [Coding Standards](Coding-Standards/index.md) | The shared baseline every repository inherits, and the per-language standards that build on it. | | [Capabilities](Capabilities/index.md) | The capabilities the ecosystem builds, each documented by a spec (why and what) and a design (how and what). | | [Frameworks](Frameworks/index.md) | The complete, end-to-end frameworks the ecosystem ships — opinionated automation a project adopts wholesale to go from source to shipped. | diff --git a/src/zensical.toml b/src/zensical.toml index 39199c6..760674d 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -25,7 +25,17 @@ nav = [ ]}, {"Ways of Working" = [ "Ways-of-Working/index.md", - {"Workflow" = "Ways-of-Working/Workflow.md"}, + {"Workflow" = [ + "Ways-of-Working/Workflow.md", + {"Stages" = [ + "Agents/index.md", + {"Define" = "Agents/define.md"}, + {"Implement" = "Agents/implement.md"}, + {"Review" = "Agents/reviewer.md"}, + {"Security Review" = "Agents/security-reviewer.md"}, + {"Maintain Guidance" = "Agents/agent-author.md"}, + ]}, + ]}, {"Contribution Workflow" = "Ways-of-Working/Contribution-Workflow.md"}, {"Fleet Orchestration" = "Ways-of-Working/Fleet-Orchestration.md"}, {"Documentation Model" = "Ways-of-Working/Documentation-Model.md"}, @@ -77,14 +87,6 @@ nav = [ {"Continuous Practices" = "Ways-of-Working/Continuous-Practices.md"}, {"DevOps Reference" = "Ways-of-Working/DevOps-Reference.md"}, ]}, - {"Agent Workflow" = [ - "Agents/index.md", - {"Define" = "Agents/define.md"}, - {"Implement" = "Agents/implement.md"}, - {"Review" = "Agents/reviewer.md"}, - {"Security Review" = "Agents/security-reviewer.md"}, - {"Maintain Workflow" = "Agents/agent-author.md"}, - ]}, {"Coding Standards" = [ "Coding-Standards/index.md", {"Naming" = "Coding-Standards/Naming.md"}, From 965df09621e0cd7a4c51c57bac6dd9442410f5e4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:38:11 +0200 Subject: [PATCH 09/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Discover=20workf?= =?UTF-8?q?low=20from=20root=20indexes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 35 +++++-------- bootstrap/AGENTS.template.md | 8 ++- bootstrap/README.md | 2 + .../Frameworks/Agentic-Development/design.md | 51 ++++++++++--------- .../Frameworks/Agentic-Development/index.md | 2 +- .../Frameworks/Agentic-Development/spec.md | 26 ++++++---- src/docs/Vision/index.md | 2 +- .../Ways-of-Working/Agentic-Development.md | 38 +++++++------- .../Principles/AI-First-Development.md | 6 +-- .../Principles/Engineering-Practices.md | 2 +- .../Ways-of-Working/Readme-Driven-Context.md | 4 +- 11 files changed, 90 insertions(+), 86 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a3c149d..67ac756 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,29 +1,18 @@ -# Agents +# Agent Pointer -## Main directive +This repository belongs to `github.com/MSXOrg`. -Everything is a work in progress and can be updated and improved. -If you find a problem, fix it if it's small; otherwise, register it as an issue in the respective repo. +Canonical context: -## Install the ecosystem +- Docs root: [src/docs/index.md](src/docs/index.md) +- Organization memory: `~/.msx/memory/index.md` -1. Create a folder in the home directory called `.msx`: -2. Clone the ecosystem locally: - 1. — requires PRs to be updated. - - Clone as bare and use worktrees. - - Create a worktree for every branch. Use a concise `-` worktree folder for a topic branch named `/-`. - 2. — work directly towards main. - - Simple clone, only main. +Before acting: -To install: -- Clone the repos in the users home folder under a sub-folder named `.msx`. -- Set configs locally to each of these repos using the GitHub username and email. +1. Segment the work by host, organization, repository, path, and task. +2. Start at the docs root index and follow [Ways of Working](src/docs/Ways-of-Working/index.md) to the canonical [Workflow](src/docs/Ways-of-Working/Workflow.md). +3. Infer the current stage from the task and its artifacts, then read the linked stage procedure. +4. Read [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), relevant standards, and organization memory. +5. Apply path-specific local rules only when they match the files in scope. -## Working with the ecosystem - -1. Get to know this repo first: - - [README](README.md) for what this repository is and how it builds. - - [CONTRIBUTING](CONTRIBUTING.md) for how to contribute and the review process. -2. Read `~/.msx/docs` - start with the index to get an overview of what is here. -3. Read `~/.msx/memory` - start with the index to get an overview of what is here. Use this while working - commit your memories here for work inside the PSModule organization. -4. While working with the code, do small micro commits and push on every commit. This will make it easier to review and merge your changes. +This file points to process knowledge; it does not define an agent, skill, or workflow stage. diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index deab7e1..8a24965 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -32,8 +32,12 @@ Each clone has repository-local git config only; it never modifies the global gi ## Then — read before acting -1. Read the relevant pages under `~/.msx/docs` for the task at hand. -2. Read `~/.msx/memory` for prior decisions, pitfalls, and context. +1. Start at `~/.msx/docs/src/docs/index.md`. +2. Follow the Ways of Working index to `Workflow.md`. +3. Infer the current stage from the task and its artifacts, then read the linked stage procedure. +4. Read the relevant standards, repository context, and `~/.msx/memory`. + +Clear task language may shortcut the index trail: `Review this PR ` enters Review, `Make this issue ` enters Define, and `Implement ` enters Implement. The linked documentation owns each procedure; this file does not define a separate agent or skill. ## Two write rules diff --git a/bootstrap/README.md b/bootstrap/README.md index af2de5d..d45ab25 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -16,6 +16,8 @@ The single starting point for agents: a git-isolated local clone of the MSX cent Keeping the workspace separate and git-isolated means an agent reads the same docs and memory in every repository, and its commits there use the workspace identity rather than whatever the working repository or the global config happens to be set to. +The loaded `AGENTS.md` points to the roots; discovery happens in documentation. Start at `~/.msx/docs/src/docs/index.md`, follow Ways of Working to Workflow, infer the current stage, and read the linked procedure. Clear task language can shortcut stage selection, but no skill or instruction file owns a separate copy of the process. + ## Install (once per machine) Run the bootstrap: diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index a61bb1e..9ae2756 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -37,7 +37,7 @@ The `docs` repository is the canonical knowledge base. It owns: - coding standards and documentation standards; - framework and capability specs and designs; - project glossary and onboarding; -- agent workflow stages and integration guidance. +- the canonical Workflow and its linked stage procedures. Changes to `docs` happen through pull requests because this repository defines durable project intent. @@ -108,6 +108,8 @@ Indexes are the navigation layer. An agent starts at the root index, reads descr docs/ index.md Ways-of-Working/index.md + Ways-of-Working/Workflow.md + Agents/index.md Coding-Standards/index.md Frameworks/index.md Frameworks/Agentic-Development/index.md @@ -127,7 +129,8 @@ Every index describes what sits below it. Generated indexes are preferred where flowchart TD start["Agent receives task"] --> policy["System and client policy"] policy --> user["User-global preferences"] - user --> locate["Detect host, org, and repo"] + user --> pointer["Read AGENTS.md pointer"] + pointer --> locate["Resolve host, org, docs, and memory roots"] locate --> host{"Which project scope?"} host -->|"dnb.ghe.com / AI-Platform"| aip["AI-Platform context"] @@ -135,21 +138,20 @@ flowchart TD host -->|"github.com / PSModule"| psmodule["PSModule context"] aip --> aipdocs["Read AI-Platform/docs index"] - aipdocs --> aipmemory["Read AI-Platform/memory index"] + aipdocs --> workflow["Follow indexes to Workflow"] msx --> msxdocs["Read MSXOrg/docs index"] - msxdocs --> msxmemory["Read MSXOrg/memory index"] + msxdocs --> workflow psmodule --> psdocs["Read PSModule/docs index"] - psdocs --> psmemory["Read PSModule/memory index"] + psdocs --> workflow - aipmemory --> repo["Read repository pointer files"] - msxmemory --> repo - psmemory --> repo - - repo --> path["Apply path-specific instructions"] + workflow --> stage["Infer current stage
read canonical procedure"] + stage --> memory["Read organization memory index"] + memory --> repo["Read README and repository docs"] + repo --> path["Apply path-specific local rules"] path --> task["Read issue, PR, branch, diff, diagnostics, and open files"] - task --> act["Plan and act with the selected project personality"] + task --> act["Act and follow stage handoffs"] act --> newpath{"New file path touched?"} newpath -->|"Yes"| path @@ -160,7 +162,7 @@ Resolution is deterministic. If the active repository remote is `github.com/PSMo ## Pointer files -`AGENTS.md` is the cross-runtime pointer file. It identifies the project, names the canonical docs and memory roots, and lists local nuance. +`AGENTS.md` is the cross-runtime pointer file. It identifies the project, names the canonical docs and memory root indexes, and lists local nuance. It points to the discovery trail, not to a stage-specific tool file. ```markdown # Agent Instructions @@ -175,15 +177,17 @@ Canonical project context: Before changing files: 1. Segment the work by host, organization, repository, path, and task. -2. Read this repository's README and local docs (front door first). -3. Read the relevant index in the resolved project docs repository. -4. Read relevant project memory for the resolved organization. -5. Apply path-specific instructions for the files being changed. +2. Start at the resolved project `docs/index.md`. +3. Follow the Ways of Working index to Workflow, infer the current stage, and read that stage procedure. +4. Read the relevant project standards, repository README and local docs, and organization memory. +5. Apply path-specific local rules for the files being changed. This file points; it does not define process knowledge. ``` -> **Discovery order vs. conflict precedence** — agents read local repository context first so they understand what a repository is and does before consulting cross-cutting standards. That discovery order does not invert authority: organization standards remain authoritative for cross-org practices. Local pointer files and `docs/` add repo-specific nuance and narrow exceptions; they never silently override an organization standard unless the standard explicitly permits a local exception. +The index trail is the default. A clear prompt can shortcut stage discovery: `Review this PR ` enters Review, `Make this issue ` enters Define, and `Implement ` enters Implement. These phrases are routing hints interpreted by [Workflow](../../Ways-of-Working/Workflow.md#find-the-current-stage), not commands with independent procedures. + +> **Discovery order vs. conflict precedence** — the pointer resolves organization context first, then the agent uses the stage procedure to select relevant organization and repository pages. Local pointer files and repository docs add nuance and narrow exceptions; they never silently override an organization standard unless the standard explicitly permits a local exception. `CLAUDE.md` stays a thin import: @@ -196,10 +200,10 @@ This file points; it does not define process knowledge. ```markdown Follow `AGENTS.md`. -Segment the work by host, organization, repository, path, and task before loading project standards or memory. Resolve organization docs and memory before editing. Use path-specific instruction files when their `applyTo` pattern matches a file being read, generated, reviewed, or edited. +Segment the work by host, organization, repository, path, and task. Start at the resolved organization docs root index and follow its Workflow before editing. Use path-specific instruction files only for local path rules when their `applyTo` pattern matches a file being read, generated, reviewed, or edited. ``` -Path-specific instruction files are reserved for local rules that cannot live centrally because they apply only to a repository path. +Path-specific instruction files are reserved for local rules that cannot live centrally because they apply only to a repository path. They never define workflow stages. ## Local workspace @@ -238,10 +242,10 @@ Different clients load different files, but the framework keeps the same depende | Client | Adapter | Behavior | | --- | --- | --- | -| Cross-client agents | `AGENTS.md` | Read the shared project pointer and local nuance. | +| Cross-client agents | `AGENTS.md` | Resolve the shared docs and memory roots, then traverse indexes to Workflow and the current stage. | | Claude Code | `CLAUDE.md` | Import `AGENTS.md`; add no duplicated process knowledge. | -| GitHub Copilot in VS Code | `.github/copilot-instructions.md` and `.github/instructions/*.instructions.md` | Read project pointers, then apply path-specific instructions when files match. | -| Copilot coding agent | `AGENTS.md`, `.github/copilot-instructions.md`, setup workflow | Prepare the local context before implementation and follow the same project roots. | +| GitHub Copilot in VS Code | `.github/copilot-instructions.md` and `.github/instructions/*.instructions.md` | Follow `AGENTS.md`, then apply path-specific local rules when files match. | +| Copilot coding agent | `AGENTS.md`, `.github/copilot-instructions.md`, setup workflow | Prepare the local roots, traverse the canonical indexes, and follow the resolved stage procedure. | | Copilot code review | Base-branch instructions | Review using trusted base-branch instructions rather than instructions changed by the PR under review. | ## Failure modes @@ -251,6 +255,7 @@ Different clients load different files, but the framework keeps the same depende | Repository does not identify its organization context | Infer from remote URL; ask when ambiguous. | | Docs or memory clone is missing | Bootstrap it before work; if unavailable, continue only with explicit warning. | | Pointer file duplicates central standards | Replace duplicated content with links during review. | +| A skill, command, named agent, or instruction file defines a workflow stage | Delete the duplicate procedure and link to Workflow or its stage page. | | Memory conflicts with docs | Docs win; memory is corrected or removed. | | Two organizations are open in one workspace | Select by active repository; ask before cross-project changes. | | A client ignores one pointer format | Add a thin adapter for that client that points to the same canonical roots. | @@ -260,7 +265,7 @@ Different clients load different files, but the framework keeps the same depende 1. Create or identify the organization `docs` repository. 2. Create or identify the organization `memory` repository, using the [Memory Repository Template](memory-template.md) as the starting scaffold. 3. Add `docs/index.md` and `memory/index.md` as the two root maps. -4. Add framework docs, standards, and agent workflow stages to `docs`. +4. Add the canonical Workflow and linked stage procedures to `docs`. 5. Add starter memory sections to `memory`. 6. Add thin pointer files to each product repository. 7. Add a bootstrap that keeps local docs and memory clones present. diff --git a/src/docs/Frameworks/Agentic-Development/index.md b/src/docs/Frameworks/Agentic-Development/index.md index 2a98c2b..3cc2256 100644 --- a/src/docs/Frameworks/Agentic-Development/index.md +++ b/src/docs/Frameworks/Agentic-Development/index.md @@ -13,7 +13,7 @@ A repository adopts the framework by carrying thin agent pointer files and by le | Page | Description | | --- | --- | -| [Spec](spec.md) | Requirements for the agentic development framework — org-scoped documentation, memory, and pointer files that make agents behave correctly per project. | +| [Spec](spec.md) | Requirements for index-first agentic development through org-scoped documentation, memory, and thin pointer files. | | [Design](design.md) | How the agentic development framework is built — OKF documentation, org memory, thin repo pointers, and deterministic context resolution. | | [Memory Repository Template](memory-template.md) | The concrete, copy-pasteable scaffold every organization's memory repository instantiates, and why it deliberately breaks from the Repository Standard. | diff --git a/src/docs/Frameworks/Agentic-Development/spec.md b/src/docs/Frameworks/Agentic-Development/spec.md index 35d5b02..52d1b45 100644 --- a/src/docs/Frameworks/Agentic-Development/spec.md +++ b/src/docs/Frameworks/Agentic-Development/spec.md @@ -1,6 +1,6 @@ --- title: Spec -description: Requirements for the agentic development framework — org-scoped documentation, memory, and pointer files that make agents behave correctly per project. +description: Requirements for index-first agentic development through org-scoped documentation, memory, and thin pointer files. --- # Agentic Development — Spec @@ -33,7 +33,8 @@ Applies to any organization that wants a shared project knowledge base and memor - Organization-level `docs` and `memory` repositories. - Markdown documents with YAML frontmatter, following the [Open Knowledge Format](../../Dictionary/index.md#open-knowledge-format) model. -- Thin repository pointer files such as `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, and path-scoped instruction files. +- Thin repository pointer files such as `AGENTS.md` and client adapters that import or follow it. +- Index-first discovery from the organization docs root to the canonical Workflow and its stage procedures. - Deterministic context resolution by host, organization, repository, path, and task. - Human-reviewed changes to canonical knowledge through pull requests. - Durable agent memory that can be shared by every person and agent working in the organization. @@ -52,9 +53,12 @@ Applies to any organization that wants a shared project knowledge base and memor - **Canonical memory repository.** Each adopting organization MUST have a `memory` repository that owns durable project memory and agent working knowledge. - **OKF-style documents.** Knowledge and memory documents MUST be Markdown files with YAML frontmatter, one primary concept per page, and stable paths that act as identity. - **Small pages and indexes.** Documentation and memory SHOULD prefer small pages, each folder SHOULD have an `index.md`, and indexes MUST let a human or agent navigate inward from the root. -- **Thin pointer files.** Product repositories MUST carry pointer files that identify the organization context and link to the canonical docs and memory roots. Pointer files MUST NOT duplicate standards or process knowledge. +- **Thin pointer files.** Product repositories MUST carry pointer files that identify the organization context and link to the canonical docs and memory root indexes. Pointer files MUST NOT duplicate standards, workflow stages, or process knowledge. +- **Index-first workflow discovery.** From the docs root index, a human or agent MUST be able to reach Ways of Working, the canonical Workflow, and the procedure for the current stage by following page descriptions and links. +- **Stage resolution from work.** Agents MUST infer the current stage from the prompt and current artifacts. Explicit task language MAY shortcut to the matching stage, but the shortcut MUST resolve to the canonical documentation. +- **One process source.** Skills, commands, named agents, and tool-specific instruction files MUST NOT redefine Workflow stages. A client convenience MAY link to a stage procedure and add only runtime mechanics. - **Segmentation before loading.** Local agent files MUST instruct agents to segment work by host, organization, repository, path, and task before loading project standards or memory. -- **Client adapters.** Tool-specific files such as `CLAUDE.md`, `.github/copilot-instructions.md`, and `.github/instructions/*.instructions.md` MAY add runtime-specific loading rules, but MUST point back to the same canonical docs and memory. +- **Client adapters.** Tool-specific files such as `CLAUDE.md`, `.github/copilot-instructions.md`, and `.github/instructions/*.instructions.md` MAY add runtime-specific loading or path rules, but MUST point back to the same canonical roots and MUST NOT define workflow behavior. - **Deterministic context resolution.** Agents MUST resolve context in layers: system and client policy, user preferences, organization docs, organization memory, repository pointers, path-specific instructions, then current task context. - **Local-first availability.** The docs and memory repositories SHOULD be available locally in a predictable workspace so agents can read them without relying on search or web access. - **Reviewed knowledge changes.** Changes to the `docs` repository MUST happen through pull requests. Changes to memory MAY be lighter-weight, but MUST remain versioned in git. @@ -68,6 +72,8 @@ Applies to any organization that wants a shared project knowledge base and memor - An agent working in `dnb.ghe.com/AI-Platform/` resolves `dnb.ghe.com/AI-Platform/docs` and `dnb.ghe.com/AI-Platform/memory` as the canonical project context. - A new product repository can adopt the framework by adding pointer files without copying standards or memory pages. - A human can start at `docs/index.md` or `memory/index.md` and navigate to the same context an agent uses. +- A human or agent can follow `docs/index.md` → Ways of Working → Workflow → the current stage procedure without knowing a file path in advance. +- A prompt such as `Review this PR ` reaches the Review procedure directly, while `Make this issue ` reaches Define, without a parallel process definition. - Updating a standard in `docs` changes the canonical guidance without editing every repository. - Capturing a recurring lesson in `memory` makes it available to later agents working in the same organization. @@ -77,12 +83,12 @@ The framework uses this normative resolution order: 1. **System and client policy** — non-project instructions imposed by the agent runtime. 2. **User-global preferences** — the human operator's baseline style and risk posture. -3. **Host and organization** — the project identity, derived from the current repository remote or explicit task. -4. **Organization docs** — the organization `docs` repository root index, then relevant standards, workflows, specs, and designs. -5. **Organization memory** — the organization `memory` repository root index, then relevant lessons, gotchas, and active context. -6. **Repository pointer files** — `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, and related local instructions. -7. **Path-specific instructions** — scoped rules that apply to the files being read, generated, reviewed, or edited. -8. **Current task context** — issue, pull request, prompt, branch, diff, diagnostics, terminal output, and open files. +3. **Repository pointer** — `AGENTS.md` identifies the host, organization, canonical docs root, memory root, and repository-local nuance. +4. **Organization docs** — start at `docs/index.md`, traverse to Ways of Working and Workflow, resolve the current stage, then load the relevant standards, specs, and designs. +5. **Organization memory** — start at `memory/index.md`, then load relevant lessons, gotchas, and active context. +6. **Repository context** — README, CONTRIBUTING, local docs, and narrow repository exceptions. +7. **Path-specific rules** — scoped local rules that apply to the files being read, generated, reviewed, or edited. +8. **Current task context** — issue, pull request, prompt, branch, diff, diagnostics, terminal output, and open files; use these artifacts to re-evaluate the stage after each handoff. A lower layer MAY refine a higher layer, but MUST NOT contradict it unless the higher layer explicitly allows a local exception. diff --git a/src/docs/Vision/index.md b/src/docs/Vision/index.md index f798d57..10ca19f 100644 --- a/src/docs/Vision/index.md +++ b/src/docs/Vision/index.md @@ -59,7 +59,7 @@ Vision (this site) the why — stable, evergreen └── Agents read the same docs as context before acting ``` -Each layer references the one above instead of restating it. A repository's README does not re-explain the principles — it links to them. An agent definition does not embed a style guide — it points to one. This keeps a single source of truth and lets the whole system evolve without drifting out of sync. +Each layer references the one above instead of restating it. A repository's README does not re-explain the principles — it links to them. An agent pointer does not embed a style guide or workflow stage — it leads into the documentation indexes. This keeps a single source of truth and lets the whole system evolve without drifting out of sync. ## Where it comes to life diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index 8adf736..97fa1ac 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -5,7 +5,7 @@ description: How ways of working, standards, and documentation are authored once # Agentic Development -How the ecosystem's ways of working, coding standards, and documentation are authored — and how both humans and agents consume them. The documentation defines how work is done; agent configuration *references* that documentation. It never the other way around. +How the ecosystem's ways of working, coding standards, and documentation are authored — and how both humans and agents consume them. The documentation defines how work is done; agent configuration *references* that documentation, never the other way around. ## Premise @@ -24,20 +24,22 @@ This spec rests on the [Principles](Principles/index.md). Four apply directly: ## Architecture -Agent configuration files are **pointers, not containers**. They tell the agent which human-readable files to read; they hold no process knowledge of their own. Documentation lives where it belongs — repo-specific docs in each repository's `README.md`, `CONTRIBUTING.md`, and `docs/`; cross-cutting standards in the org-level documentation site, referenced by canonical URL. +Agent configuration files are **pointers, not containers**. They identify the canonical documentation root; they do not select a persona or contain workflow stages. Documentation lives where it belongs — repo-specific docs in each repository's `README.md`, `CONTRIBUTING.md`, and `docs/`; cross-cutting standards in the org-level documentation site. -When an agent starts work in a repository, it discovers context in layers — local first, then central, then local nuance on top: +When an agent receives work, it follows the same documentation trail a human can follow: ```mermaid flowchart TD - agent["Agent starts in a repository"] --> local["1 Read local context
README.md · CONTRIBUTING.md · docs/"] - local --> index["2 Read the central indexes
Ways of Working · Coding Standards · Capabilities"] - index --> pick["3 Pick up the relevant central docs
e.g. the Terraform coding standard"] - pick --> nuance["4 Layer local overrides
repo-specific instruction files"] - nuance --> work["Start work"] + task["Agent receives work"] --> pointer["1 Read AGENTS.md
resolve project docs + memory roots"] + pointer --> root["2 Read docs/index.md"] + root --> ways["3 Follow Ways of Working index"] + ways --> workflow["4 Read Workflow"] + workflow --> stage["5 Infer the current stage
read its procedure"] + stage --> context["6 Read relevant standards,
repository context, and memory"] + context --> work["Act and follow stage handoffs"] ``` -The flow is sequential, not a decision. The agent reads local docs to understand the repository, reads the central indexes to see which shared standards exist, picks up the relevant ones, and finally checks for local overrides that add repo-specific nuance. **Local files never replace central standards — they layer specifics on top.** The docs are the stable core; every integration is a thin adapter that references them. +The indexes are the default discovery mechanism. [Workflow](Workflow.md) owns the process and routes the work to a [stage procedure](../Agents/index.md); the stage page then points to the standards and artifacts it consumes. A clear prompt such as `Review this PR ` may shortcut directly through the Workflow routing table, but it does not create a second process definition. **Local files never replace central standards — they layer specifics on top.** ## Where documentation lives @@ -52,32 +54,28 @@ This split follows [Repository Segmentation](Repository-Segmentation.md) and [RE ## How an agent runtime plugs in -Agent context is delivered through three layers, in priority order — the same three layers the [Principles](Principles/AI-First-Development.md#human-agent-coexistence) describe: - -1. **Documentation.** The primary source. The published docs, READMEs, and issue bodies are written for humans and read natively by agents. -2. **Central agent workflow.** The stages agents follow — Define, Implement, Review, and specialized paths — are authored once as documentation in the [Agent Workflow](../Agents/index.md) section. They describe entry conditions, boundaries, procedures, and handoffs, and they reference the ways of working; they never restate a standard or convention. -3. **Local pointer files.** Each repository carries an `AGENTS.md` — read natively by most agent runtimes — and a `CLAUDE.md` that imports it, pointing to the central descriptions and adding only repo-specific nuance and the small amount of genuinely tool-specific configuration (permission scopes, path-scoped rules) that cannot be expressed as a pointer. +Each repository carries an `AGENTS.md` that points to the organization documentation and memory root indexes. A `CLAUDE.md` or other client adapter may import that pointer and add only the small amount of genuinely tool-specific configuration, such as permission scopes or path matching, that cannot live in ordinary documentation. Any new runtime follows the same pattern, regardless of vendor: -- A **context file** that links to the ways-of-working docs and the repository's own context. -- **Workflow entry points** — named commands or agents — that reference those same docs and add the operational steps (branch creation, tool invocations, API calls). +- A **context pointer** that identifies the canonical docs and memory root indexes. +- Optional **keyword shortcuts** that route a clearly stated task to the matching [Workflow stage](Workflow.md#find-the-current-stage) without copying its procedure. - **Tool-specific settings** — permissions, model selection, and the like. -The context file and the entry points are pointers; the settings are the only genuinely tool-specific surface. When a new runtime is adopted, only this integration layer is added — the documentation it points to is untouched. +There is no separate process surface for Define, Implement, or Review. If a client exposes a skill, command, named agent, or other convenience, it links to the canonical stage page and adds no process knowledge. When a new runtime is adopted, only this thin integration layer is added. ## Distribution The two non-documentation layers have different distribution models: -- **The central agent workflow** lives in the [Agent Workflow](../Agents/index.md) section of this site and is referenced by canonical URL — one process, available to every repository and runtime with no per-repo copy to maintain. -- **Per-repository pointer files** — `AGENTS.md`, the `CLAUDE.md` that imports it, and any path-scoped instruction files — are seeded from a template repository and kept current across existing repositories by a sync mechanism. +- **The canonical process** lives in [Workflow](Workflow.md), which links to ordinary documentation pages for each [stage procedure](../Agents/index.md). +- **Per-repository pointer files** — `AGENTS.md`, the `CLAUDE.md` that imports it, and any path-scoped local-rule adapters — are seeded from a template repository and kept current across existing repositories by a sync mechanism. Process knowledge is never added to a distributed config file. If an agent needs the branch strategy, it goes in [Branching and Merging](Branching-and-Merging.md) or the repo's `CONTRIBUTING.md`; if it needs a coding convention, it goes in the relevant [coding standard](../Coding-Standards/index.md). The config file only points — it never defines. ## The workspace bootstrap -The **user-global** entry file is a thin **bootstrap**, not a copy of the docs. Each runtime auto-loads its own user-level file — Copilot from its user instructions, Claude Code from `~/.claude/CLAUDE.md` (which imports the same instructions) — and its first instruction is to make the central workspace present locally, then read from it. This is distinct from the per-repository `AGENTS.md` and `CLAUDE.md`, which remain thin pointers to the central descriptions. +The **user-global** entry file is a thin **bootstrap**, not a copy of the docs. Each runtime auto-loads its own user-level file — Copilot from its user instructions, Claude Code from `~/.claude/CLAUDE.md` (which imports the same instructions) — and its first instruction is to make the central workspace present locally, then start at the root indexes. This is distinct from the per-repository `AGENTS.md` and `CLAUDE.md`, which remain thin pointers to the same roots. The workspace is a git-isolated clone of the central repositories under `~/.msx`: diff --git a/src/docs/Ways-of-Working/Principles/AI-First-Development.md b/src/docs/Ways-of-Working/Principles/AI-First-Development.md index 0449e23..d5a89b3 100644 --- a/src/docs/Ways-of-Working/Principles/AI-First-Development.md +++ b/src/docs/Ways-of-Working/Principles/AI-First-Development.md @@ -32,8 +32,8 @@ Agents are trained to read documentation. That is their natural skill. By keepin Agent context is delivered through three layers, in priority order: 1. **Documentation** — the primary source. Published docs at , READMEs, and issue bodies are written for humans and naturally consumable by agents. -2. **Central agent workflow** — the stages agents follow, authored once as documentation in the [Agent Workflow](../../Agents/index.md) section. They reference the ways of working and define entry conditions, boundaries, procedures, and handoffs — never standards or conventions. -3. **Local pointer files** — each repository's `AGENTS.md` (and the `CLAUDE.md` that imports it), pointing to the central descriptions and adding only repo-specific nuance and genuinely tool-specific settings. +2. **Canonical workflow** — [Workflow](../Workflow.md) owns the process and links to ordinary documentation for each [stage procedure](../../Agents/index.md). Indexes provide the default discovery path; clear task language may shortcut stage selection without creating separate instructions. +3. **Local pointer files** — each repository's `AGENTS.md` (and the `CLAUDE.md` that imports it), pointing to the canonical docs and memory root indexes and adding only repo-specific nuance and genuinely tool-specific settings. ## Augmentation, not replacement @@ -78,7 +78,7 @@ See [README-Driven Context](../Readme-Driven-Context.md). The work of keeping context **right, evergreen, and declarative** runs alongside software delivery: - **Software delivery** produces code, tests, and releases using source control, CI/CD, and DevOps practices. -- **Context maintenance** produces issues, decisions, READMEs, agent definitions, and documentation — and treats them as products that must be kept current. +- **Context maintenance** produces issues, decisions, READMEs, pointer files, and documentation — and treats them as products that must be kept current. Both run continuously. Each iteration of software delivery produces context that needs maintenance; each iteration of context maintenance unblocks the next round of software work. diff --git a/src/docs/Ways-of-Working/Principles/Engineering-Practices.md b/src/docs/Ways-of-Working/Principles/Engineering-Practices.md index 75603a9..618d2d5 100644 --- a/src/docs/Ways-of-Working/Principles/Engineering-Practices.md +++ b/src/docs/Ways-of-Working/Principles/Engineering-Practices.md @@ -17,7 +17,7 @@ If something is only in your head: ## Everything as code -Everything — workflows, configuration, infrastructure, processes, agent definitions, even the docs you are reading — lives in source control. This is the bedrock of human-agent interaction. An agent can read code, change code, propose code. It cannot read what's only in someone's memory. +Everything — workflows, configuration, infrastructure, processes, pointer files, even the docs you are reading — lives in source control. This is the bedrock of human-agent interaction. An agent can read code, change code, propose code. It cannot read what's only in someone's memory. ## Code in code files diff --git a/src/docs/Ways-of-Working/Readme-Driven-Context.md b/src/docs/Ways-of-Working/Readme-Driven-Context.md index 837f44d..bd38f9a 100644 --- a/src/docs/Ways-of-Working/Readme-Driven-Context.md +++ b/src/docs/Ways-of-Working/Readme-Driven-Context.md @@ -53,9 +53,9 @@ The README is updated **in the same pull request** that introduces the change. D - CI/CD changes that do not affect usage. - Dependency updates that do not change behavior. -## Agent workflow +## Using repository context -When an agent works on a repository, the README sequence is: +When a human or agent works on a repository, the README sequence is: 1. **Read the README first** — before reading source code, understand the project context. 2. **Use the README as a guide** — let documented scope and architecture inform where and how to make changes. From 9d5eb5fb70a8e011183b21d00d68097023e1371a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:42:05 +0200 Subject: [PATCH 10/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Align=20workflow?= =?UTF-8?q?=20maintenance=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/zensical.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zensical.toml b/src/zensical.toml index 760674d..e3b8595 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -33,7 +33,7 @@ nav = [ {"Implement" = "Agents/implement.md"}, {"Review" = "Agents/reviewer.md"}, {"Security Review" = "Agents/security-reviewer.md"}, - {"Maintain Guidance" = "Agents/agent-author.md"}, + {"Maintain Workflow Guidance" = "Agents/agent-author.md"}, ]}, ]}, {"Contribution Workflow" = "Ways-of-Working/Contribution-Workflow.md"}, From be0e17367727fad1d8291aace6ca4a48db79b1f3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:46:44 +0200 Subject: [PATCH 11/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Use=20type-neutr?= =?UTF-8?q?al=20delivery=20plan=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Agents/implement.md | 8 ++++---- src/docs/Ways-of-Working/PR-Format.md | 2 +- src/docs/Ways-of-Working/Workflow.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/docs/Agents/implement.md b/src/docs/Agents/implement.md index aa22fb5..429539c 100644 --- a/src/docs/Agents/implement.md +++ b/src/docs/Agents/implement.md @@ -33,11 +33,11 @@ Use [git worktrees](../Ways-of-Working/Git-Worktrees.md) for every repository-de ### 3. Build -For each checklist item in the delivery plan: +For each step in the delivery plan: 1. Implement the change and self-review the staged diff. 2. Commit per [Commit Conventions](../Ways-of-Working/Commit-Conventions.md) — one logical change per commit. -3. Update the issue as each checklist item completes — do not batch. +3. Update the issue as each plan step completes — do not batch. 4. Push regularly so CI runs against current work. When the plan is wrong, stop and document the conflict in a comment, then update the plan before resuming. Out-of-scope problems go to [Define](define.md). @@ -57,9 +57,9 @@ When the change meets the [Definition of Ready for Review](../Ways-of-Working/De ## Operating rules 1. Micro-commits, one logical change each, with descriptive messages. -2. Progress is visible — the delivery issue is updated as checklist items complete, not in bulk. +2. Progress is visible — the delivery issue is updated as plan steps complete, not in bulk. 3. Draft pull request from the start; stay in the issue's scope. -4. Mark ready only when the change meets the Definition of Ready for Review — never with open checklist items. +4. Mark ready only when the change meets the Definition of Ready for Review — never with open plan steps. 5. Return unplanned work to [Define](define.md) and hand review-ready work to [Review](reviewer.md). ## Where this connects diff --git a/src/docs/Ways-of-Working/PR-Format.md b/src/docs/Ways-of-Working/PR-Format.md index 4b76aa3..1127c1b 100644 --- a/src/docs/Ways-of-Working/PR-Format.md +++ b/src/docs/Ways-of-Working/PR-Format.md @@ -119,7 +119,7 @@ The **Technical details** block is for reviewers and maintainers. Include intern - Which internal functions, classes, or files were changed. - Implementation approach and design decisions. - Backward compatibility notes for developers. -- **Implementation plan progress** — cross-reference the closing Task or Bug's plan. Which checklist items does this PR complete? Which were moved to follow-up delivery issues? +- **Implementation plan progress** — cross-reference the closing Task or Bug's plan. Which plan steps does this PR complete? Which were moved to follow-up delivery issues? The **Relevant issues (or links)** block is required and uses fully qualified references (`Owner/Repo#N`) so links work across repositories. diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index 121b9bc..cbe7fac 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -108,7 +108,7 @@ Execute one ready, unblocked Task or Bug. For repository delivery: 1. **Branch** — create a branch (and [worktree](Git-Worktrees.md)) for the delivery leaf. 2. **Draft PR** — push early and open a draft pull request that closes exactly that Task or Bug. This makes progress visible and attaches CI from the start. -3. **Implement** — work through the checklist. One logical change per commit. Update the issue as each checklist item completes. +3. **Implement** — work through the implementation plan. One logical change per commit. Update the issue as each plan step completes. 4. **Test locally** — don't push known failures to CI. Push work as far inward as it can go. 5. **Self-review with automation** — run the [Copilot review loop](Contribution-Workflow.md#the-copilot-review-loop) until it reports a clean round, fixing in-scope feedback and filing follow-up issues for the rest. 6. **Ready and auto-merge** — when the change meets the [Definition of Ready for Review](Definition-of-Ready-and-Done.md#definition-of-ready-for-review), finalize the pull request per [PR Format](PR-Format.md), mark it ready, and enable auto-merge. From 85c4ce6480d013893e5d0363a8c7e56ee7858b22 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:50:49 +0200 Subject: [PATCH 12/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Label=20the=20st?= =?UTF-8?q?age=20index=20precisely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Ways-of-Working/Workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index cbe7fac..6c39438 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -47,7 +47,7 @@ flowchart TD ## Find the current stage -Start from this page after following the documentation indexes: [Home](../index.md) → [Ways of Working](index.md) → Workflow. Infer the current stage from the work and its artifacts, then read the matching [stage procedure](../Agents/index.md). A stage is a part of this workflow, not a separate agent, skill, or instruction set. +Start from this page after following the documentation indexes: [Home](../index.md) → [Ways of Working](index.md) → Workflow. Infer the current stage from the work and its artifacts, then use the [stage procedure index](../Agents/index.md) or the direct links below. A stage is a part of this workflow, not a separate agent, skill, or instruction set. | Current work or prompt | Enter | Continue until | | --- | --- | --- | From 59770e504607325ec9de0ed5cf717b4e93915ecf Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 09:55:35 +0200 Subject: [PATCH 13/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Flatten=20workfl?= =?UTF-8?q?ow=20stage=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/zensical.toml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/zensical.toml b/src/zensical.toml index e3b8595..3ef3c1f 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -27,14 +27,12 @@ nav = [ "Ways-of-Working/index.md", {"Workflow" = [ "Ways-of-Working/Workflow.md", - {"Stages" = [ - "Agents/index.md", - {"Define" = "Agents/define.md"}, - {"Implement" = "Agents/implement.md"}, - {"Review" = "Agents/reviewer.md"}, - {"Security Review" = "Agents/security-reviewer.md"}, - {"Maintain Workflow Guidance" = "Agents/agent-author.md"}, - ]}, + "Agents/index.md", + {"Define" = "Agents/define.md"}, + {"Implement" = "Agents/implement.md"}, + {"Review" = "Agents/reviewer.md"}, + {"Security Review" = "Agents/security-reviewer.md"}, + {"Maintain Workflow Guidance" = "Agents/agent-author.md"}, ]}, {"Contribution Workflow" = "Ways-of-Working/Contribution-Workflow.md"}, {"Fleet Orchestration" = "Ways-of-Working/Fleet-Orchestration.md"}, From d75e44accfc329d77de91516ee5a4b2f96b7bc3f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 10:32:00 +0200 Subject: [PATCH 14/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Move=20procedure?= =?UTF-8?q?s=20under=20ways=20of=20working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Agents/implement.md | 70 ------------------- src/docs/Agents/index.md | 26 ------- .../Frameworks/Agentic-Development/design.md | 2 +- .../Ways-of-Working/Agentic-Development.md | 4 +- .../Ways-of-Working/Fleet-Orchestration.md | 2 +- src/docs/Ways-of-Working/PR-Format.md | 2 +- .../Principles/AI-First-Development.md | 2 +- .../Workflow-Stages/Define.md} | 18 ++--- .../Workflow-Stages/Implement.md | 70 +++++++++++++++++++ .../Workflow-Stages/Maintain-Guidance.md} | 8 +-- .../Workflow-Stages/Review.md} | 24 +++---- .../Workflow-Stages/Security-Review.md} | 6 +- .../Ways-of-Working/Workflow-Stages/index.md | 26 +++++++ src/docs/Ways-of-Working/Workflow.md | 12 ++-- src/docs/Ways-of-Working/index.md | 1 + src/docs/index.md | 1 - src/zensical.toml | 12 ++-- 17 files changed, 143 insertions(+), 143 deletions(-) delete mode 100644 src/docs/Agents/implement.md delete mode 100644 src/docs/Agents/index.md rename src/docs/{Agents/define.md => Ways-of-Working/Workflow-Stages/Define.md} (60%) create mode 100644 src/docs/Ways-of-Working/Workflow-Stages/Implement.md rename src/docs/{Agents/agent-author.md => Ways-of-Working/Workflow-Stages/Maintain-Guidance.md} (77%) rename src/docs/{Agents/reviewer.md => Ways-of-Working/Workflow-Stages/Review.md} (62%) rename src/docs/{Agents/security-reviewer.md => Ways-of-Working/Workflow-Stages/Security-Review.md} (80%) create mode 100644 src/docs/Ways-of-Working/Workflow-Stages/index.md diff --git a/src/docs/Agents/implement.md b/src/docs/Agents/implement.md deleted file mode 100644 index 429539c..0000000 --- a/src/docs/Agents/implement.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: Implement -description: Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. ---- - -# Implement - -Implement is the delivery stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It takes one ready, unblocked Task or Bug and produces working software in a review-ready pull request. The stage owns branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or supply the independent review. - -## Enter this stage when - -Implement a Task, fix a Bug, create a branch, open a pull request, respond to review feedback, or finalize a pull request. Given an Initiative, Epic, or PBI, follow native containment and blocked-by relationships to a ready delivery leaf; do not treat list order as execution order. - -## Input - -A ready repository-delivery Task or Bug number or URL. An operational Task follows its canonical [operational delivery path](../Ways-of-Working/Issues/Types/Task.md#operational-delivery) instead of this pull-request flow. - -## Flow - -### 1. Orient - -1. Read the delivery issue fully, including its type-specific body, native parent, and dependency edges. -2. Read the repository README first per [README-Driven Context](../Ways-of-Working/Readme-Driven-Context.md). -3. Identify the stack and load the relevant [Coding Standards](../Coding-Standards/index.md). Repo-local linter config wins where it disagrees with a published standard. - -### 2. Branch and draft pull request - -Use [git worktrees](../Ways-of-Working/Git-Worktrees.md) for every repository-delivery Task or Bug. - -1. Create a worktree from the default branch per [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md). -2. Push an initial commit and immediately open a **draft** pull request so CI attaches from the first push. -3. Close exactly that Task or Bug in the pull request. Reference a parent PBI or Epic only as non-closing context, then assign the pull request. - -### 3. Build - -For each step in the delivery plan: - -1. Implement the change and self-review the staged diff. -2. Commit per [Commit Conventions](../Ways-of-Working/Commit-Conventions.md) — one logical change per commit. -3. Update the issue as each plan step completes — do not batch. -4. Push regularly so CI runs against current work. - -When the plan is wrong, stop and document the conflict in a comment, then update the plan before resuming. Out-of-scope problems go to [Define](define.md). - -### 4. Self-review and respond - -1. Run the [Copilot review loop](../Ways-of-Working/Contribution-Workflow.md#the-copilot-review-loop) until it reports a clean round. -2. Triage each thread and CI failure per [Review Etiquette](../Ways-of-Working/Review-Etiquette.md): fix in scope and propagate the same fix elsewhere; file a follow-up for out-of-scope; reply, then resolve. - -### 5. Finalize and hand off - -When the change meets the [Definition of Ready for Review](../Ways-of-Working/Definition-of-Ready-and-Done.md): - -1. Finalize the title, release-note description, and label per [PR Format](../Ways-of-Working/PR-Format.md). -2. Mark the pull request ready and enable auto-merge per [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md). - -## Operating rules - -1. Micro-commits, one logical change each, with descriptive messages. -2. Progress is visible — the delivery issue is updated as plan steps complete, not in bulk. -3. Draft pull request from the start; stay in the issue's scope. -4. Mark ready only when the change meets the Definition of Ready for Review — never with open plan steps. -5. Return unplanned work to [Define](define.md) and hand review-ready work to [Review](reviewer.md). - -## Where this connects - -- [Contribution Workflow](../Ways-of-Working/Contribution-Workflow.md) — the draft-first loop this runs. -- [Issue Lifecycle](../Ways-of-Working/Issues/Process/Lifecycle.md) and [Issue Relationships](../Ways-of-Working/Issues/Process/Relationships.md) — delivery-leaf eligibility and blockers. -- [Definition of Ready and Done](../Ways-of-Working/Definition-of-Ready-and-Done.md) — the gate this hands off at. -- [PR Format](../Ways-of-Working/PR-Format.md) and [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md) — packaging and landing. diff --git a/src/docs/Agents/index.md b/src/docs/Agents/index.md deleted file mode 100644 index 79bcb01..0000000 --- a/src/docs/Agents/index.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Workflow Stages -description: Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. ---- - -# Workflow Stages - -The canonical [Workflow](../Ways-of-Working/Workflow.md) owns the process, stage routing, and transitions. This section contains only the procedure for each stage: its entry condition, boundary, actions, and handoff. Humans and agents follow the same pages. - -The default discovery trail is [Home](../index.md) → [Ways of Working](../Ways-of-Working/index.md) → [Workflow](../Ways-of-Working/Workflow.md) → this stage index → the resolved procedure. A task that clearly names its action may use the [Workflow routing table](../Ways-of-Working/Workflow.md#find-the-current-stage) as a shortcut. - -No stage needs its own skill, command, named agent, or tool-specific process file. An integration may link directly to a stage for convenience, but the documentation remains the only process definition. [Maintain Workflow Guidance](agent-author.md) keeps these procedures and thin repository pointers aligned. - -## Procedures - - - -| Page | Description | -| --- | --- | -| [Define](define.md) | Procedure for the Workflow stage that captures, routes, refines, and plans work at the correct issue altitude. | -| [Implement](implement.md) | Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. | -| [Review](reviewer.md) | Procedure for the Workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. | -| [Security Review](security-reviewer.md) | Procedure for the specialized Workflow stage for defensive security review and responsible disclosure. | -| [Maintain Workflow Guidance](agent-author.md) | Maintain the canonical Workflow stage procedures and the thin repository pointers that lead to them. | - - diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index 9ae2756..927f5fc 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -109,7 +109,7 @@ docs/ index.md Ways-of-Working/index.md Ways-of-Working/Workflow.md - Agents/index.md + Ways-of-Working/Workflow-Stages/index.md Coding-Standards/index.md Frameworks/index.md Frameworks/Agentic-Development/index.md diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index 97fa1ac..0071f52 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -39,7 +39,7 @@ flowchart TD context --> work["Act and follow stage handoffs"] ``` -The indexes are the default discovery mechanism. [Workflow](Workflow.md) owns the process and routes the work to a [stage procedure](../Agents/index.md); the stage page then points to the standards and artifacts it consumes. A clear prompt such as `Review this PR ` may shortcut directly through the Workflow routing table, but it does not create a second process definition. **Local files never replace central standards — they layer specifics on top.** +The indexes are the default discovery mechanism. [Workflow](Workflow.md) owns the process and routes the work to a [stage procedure](Workflow-Stages/index.md); the stage page then points to the standards and artifacts it consumes. A clear prompt such as `Review this PR ` may shortcut directly through the Workflow routing table, but it does not create a second process definition. **Local files never replace central standards — they layer specifics on top.** ## Where documentation lives @@ -68,7 +68,7 @@ There is no separate process surface for Define, Implement, or Review. If a clie The two non-documentation layers have different distribution models: -- **The canonical process** lives in [Workflow](Workflow.md), which links to ordinary documentation pages for each [stage procedure](../Agents/index.md). +- **The canonical process** lives in [Workflow](Workflow.md), which links to ordinary documentation pages for each [stage procedure](Workflow-Stages/index.md). - **Per-repository pointer files** — `AGENTS.md`, the `CLAUDE.md` that imports it, and any path-scoped local-rule adapters — are seeded from a template repository and kept current across existing repositories by a sync mechanism. Process knowledge is never added to a distributed config file. If an agent needs the branch strategy, it goes in [Branching and Merging](Branching-and-Merging.md) or the repo's `CONTRIBUTING.md`; if it needs a coding convention, it goes in the relevant [coding standard](../Coding-Standards/index.md). The config file only points — it never defines. diff --git a/src/docs/Ways-of-Working/Fleet-Orchestration.md b/src/docs/Ways-of-Working/Fleet-Orchestration.md index cb9d036..c6c0f37 100644 --- a/src/docs/Ways-of-Working/Fleet-Orchestration.md +++ b/src/docs/Ways-of-Working/Fleet-Orchestration.md @@ -150,7 +150,7 @@ flowchart TD 3. **Apply the change and run the loop.** Make the change and take the pull request through the [Contribution Workflow](Contribution-Workflow.md) — the Copilot review loop — exactly as any single-repository change. The - [Implement](../Agents/implement.md) and [Review](../Agents/reviewer.md) + [Implement](Workflow-Stages/Implement.md) and [Review](Workflow-Stages/Review.md) workflow stages apply unchanged. 4. **Flag blockers, don't stall the fleet.** If a delivery leaf needs a human decision or an off-platform action, set `stage:blocked` with a note and move on to the next diff --git a/src/docs/Ways-of-Working/PR-Format.md b/src/docs/Ways-of-Working/PR-Format.md index 1127c1b..f30148f 100644 --- a/src/docs/Ways-of-Working/PR-Format.md +++ b/src/docs/Ways-of-Working/PR-Format.md @@ -125,7 +125,7 @@ The **Relevant issues (or links)** block is required and uses fully qualified re Use one bullet per linked issue. Exactly one bullet uses a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue) (`Fixes`, `Closes`, or `Resolves`), and it closes the one Task or Bug delivered by the pull request. A parent PBI or Epic may appear as context without a closing keyword; never close an aggregate through a delivery pull request. Other supporting links are also non-closing. -If there is not exactly one closing Task or Bug: **stop**. Route back to [Define](../Agents/define.md) to create or correctly route the delivery leaf. The [Issue Hierarchy](Issues/Types/Hierarchy.md) and [Issue Lifecycle](Issues/Process/Lifecycle.md) own type and closure semantics. +If there is not exactly one closing Task or Bug: **stop**. Route back to [Define](Workflow-Stages/Define.md) to create or correctly route the delivery leaf. The [Issue Hierarchy](Issues/Types/Hierarchy.md) and [Issue Lifecycle](Issues/Process/Lifecycle.md) own type and closure semantics. ## Formatting diff --git a/src/docs/Ways-of-Working/Principles/AI-First-Development.md b/src/docs/Ways-of-Working/Principles/AI-First-Development.md index d5a89b3..d8414d2 100644 --- a/src/docs/Ways-of-Working/Principles/AI-First-Development.md +++ b/src/docs/Ways-of-Working/Principles/AI-First-Development.md @@ -32,7 +32,7 @@ Agents are trained to read documentation. That is their natural skill. By keepin Agent context is delivered through three layers, in priority order: 1. **Documentation** — the primary source. Published docs at , READMEs, and issue bodies are written for humans and naturally consumable by agents. -2. **Canonical workflow** — [Workflow](../Workflow.md) owns the process and links to ordinary documentation for each [stage procedure](../../Agents/index.md). Indexes provide the default discovery path; clear task language may shortcut stage selection without creating separate instructions. +2. **Canonical workflow** — [Workflow](../Workflow.md) owns the process and links to ordinary documentation for each [stage procedure](../Workflow-Stages/index.md). Indexes provide the default discovery path; clear task language may shortcut stage selection without creating separate instructions. 3. **Local pointer files** — each repository's `AGENTS.md` (and the `CLAUDE.md` that imports it), pointing to the canonical docs and memory root indexes and adding only repo-specific nuance and genuinely tool-specific settings. ## Augmentation, not replacement diff --git a/src/docs/Agents/define.md b/src/docs/Ways-of-Working/Workflow-Stages/Define.md similarity index 60% rename from src/docs/Agents/define.md rename to src/docs/Ways-of-Working/Workflow-Stages/Define.md index a79fc1b..446e3ee 100644 --- a/src/docs/Agents/define.md +++ b/src/docs/Ways-of-Working/Workflow-Stages/Define.md @@ -5,7 +5,7 @@ description: Procedure for the Workflow stage that captures, routes, refines, an # Define -Define is the first stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It takes something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and routes it to the correct native issue type. The stage hands off an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. +Define is the first stage of the canonical [Workflow](../Workflow.md). It takes something someone wants — a feature, a bug, an improvement, external feedback, or a production signal — and routes it to the correct native issue type. The stage hands off an Epic or PBI ready to coordinate and decompose, or a Task or Bug ready for delivery. Define plans work; it does not build it. ## Enter this stage when @@ -22,12 +22,12 @@ A description of a desired change, a feedback issue from a non-contributor (trea Turn the input into an issue with a provisional native type and enough local context to refine it. 1. Search for duplicates first — propose consolidation rather than creating a new issue. -2. Follow the universal [Issue Format](../Ways-of-Working/Issues/Process/Format.md) and the selected type page for its body. +2. Follow the universal [Issue Format](../Issues/Process/Format.md) and the selected type page for its body. 3. Acceptance criteria must be user-observable and testable. ### 2. Refine -Ground the issue so anyone reading it agrees on what "done" means, up to the [Definition of Ready](../Ways-of-Working/Definition-of-Ready-and-Done.md). +Ground the issue so anyone reading it agrees on what "done" means, up to the [Definition of Ready](../Definition-of-Ready-and-Done.md). 1. Pain before solution — push back on implementation-framed requests. 2. Make assumptions explicit. @@ -38,11 +38,11 @@ Ground the issue so anyone reading it agrees on what "done" means, up to the [De Decide how the work will happen and record the decisions. -1. Route the work through the [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md); do not infer altitude from effort, priority, or a legacy label. +1. Route the work through the [Issue Hierarchy](../Issues/Types/Hierarchy.md); do not infer altitude from effort, priority, or a legacy label. 2. For an Epic or PBI, establish native containment and dependency relationships and refine the first required children to their own readiness gates. 3. For a Task or Bug, produce one executable delivery plan sized for one pull request. Use the audited operational Task path only when no repository artifact exists. 4. Find the minimum viable path — spike, then proof of concept, then minimum viable product, then improve. -5. Record decisions at the issue's [planning altitude](../Ways-of-Working/Issues/Process/Planning.md), with rationale and alternatives where they matter. +5. Record decisions at the issue's [planning altitude](../Issues/Process/Planning.md), with rationale and alternatives where they matter. 6. Resolve open questions before finishing; defer anything that does not block this slice to a follow-up issue. ## Operating rules @@ -50,10 +50,10 @@ Decide how the work will happen and record the decisions. 1. Tone is impersonal. The issue description is the source of truth; comments record what changed. 2. External references are hyperlinks. 3. Do not modify a feedback issue from a non-contributor — create an internal issue and cross-link. -4. Stop when the issue meets the readiness gate for its altitude. Hand a ready delivery leaf to the [Implement stage](implement.md); do not build, branch, or open pull requests here. +4. Stop when the issue meets the readiness gate for its altitude. Hand a ready delivery leaf to the [Implement stage](Implement.md); do not build, branch, or open pull requests here. ## Where this connects -- [Workflow](../Ways-of-Working/Workflow.md) — the loop this opens. -- [Issue Format](../Ways-of-Working/Issues/Process/Format.md), [Issue Planning](../Ways-of-Working/Issues/Process/Planning.md), [Issue Relationships](../Ways-of-Working/Issues/Process/Relationships.md), and [Issue Hierarchy](../Ways-of-Working/Issues/Types/Hierarchy.md) — canonical issue ownership. -- [Definition of Ready and Done](../Ways-of-Working/Definition-of-Ready-and-Done.md) — the readiness bar this aims for. +- [Workflow](../Workflow.md) — the loop this opens. +- [Issue Format](../Issues/Process/Format.md), [Issue Planning](../Issues/Process/Planning.md), [Issue Relationships](../Issues/Process/Relationships.md), and [Issue Hierarchy](../Issues/Types/Hierarchy.md) — canonical issue ownership. +- [Definition of Ready and Done](../Definition-of-Ready-and-Done.md) — the readiness bar this aims for. diff --git a/src/docs/Ways-of-Working/Workflow-Stages/Implement.md b/src/docs/Ways-of-Working/Workflow-Stages/Implement.md new file mode 100644 index 0000000..6ad0950 --- /dev/null +++ b/src/docs/Ways-of-Working/Workflow-Stages/Implement.md @@ -0,0 +1,70 @@ +--- +title: Implement +description: Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. +--- + +# Implement + +Implement is the delivery stage of the canonical [Workflow](../Workflow.md). It takes one ready, unblocked Task or Bug and produces working software in a review-ready pull request. The stage owns branching, coding, committing, opening the pull request, tracking progress, running the automated review loop, responding to feedback, and finalizing the release note. Implement builds delivery leaves; it does not implement an Epic or PBI aggregate, plan from scratch, or supply the independent review. + +## Enter this stage when + +Implement a Task, fix a Bug, create a branch, open a pull request, respond to review feedback, or finalize a pull request. Given an Initiative, Epic, or PBI, follow native containment and blocked-by relationships to a ready delivery leaf; do not treat list order as execution order. + +## Input + +A ready repository-delivery Task or Bug number or URL. An operational Task follows its canonical [operational delivery path](../Issues/Types/Task.md#operational-delivery) instead of this pull-request flow. + +## Flow + +### 1. Orient + +1. Read the delivery issue fully, including its type-specific body, native parent, and dependency edges. +2. Read the repository README first per [README-Driven Context](../Readme-Driven-Context.md). +3. Identify the stack and load the relevant [Coding Standards](../../Coding-Standards/index.md). Repo-local linter config wins where it disagrees with a published standard. + +### 2. Branch and draft pull request + +Use [git worktrees](../Git-Worktrees.md) for every repository-delivery Task or Bug. + +1. Create a worktree from the default branch per [Branching and Merging](../Branching-and-Merging.md). +2. Push an initial commit and immediately open a **draft** pull request so CI attaches from the first push. +3. Close exactly that Task or Bug in the pull request. Reference a parent PBI or Epic only as non-closing context, then assign the pull request. + +### 3. Build + +For each step in the delivery plan: + +1. Implement the change and self-review the staged diff. +2. Commit per [Commit Conventions](../Commit-Conventions.md) — one logical change per commit. +3. Update the issue as each plan step completes — do not batch. +4. Push regularly so CI runs against current work. + +When the plan is wrong, stop and document the conflict in a comment, then update the plan before resuming. Out-of-scope problems go to [Define](Define.md). + +### 4. Self-review and respond + +1. Run the [Copilot review loop](../Contribution-Workflow.md#the-copilot-review-loop) until it reports a clean round. +2. Triage each thread and CI failure per [Review Etiquette](../Review-Etiquette.md): fix in scope and propagate the same fix elsewhere; file a follow-up for out-of-scope; reply, then resolve. + +### 5. Finalize and hand off + +When the change meets the [Definition of Ready for Review](../Definition-of-Ready-and-Done.md): + +1. Finalize the title, release-note description, and label per [PR Format](../PR-Format.md). +2. Mark the pull request ready and enable auto-merge per [Branching and Merging](../Branching-and-Merging.md). + +## Operating rules + +1. Micro-commits, one logical change each, with descriptive messages. +2. Progress is visible — the delivery issue is updated as plan steps complete, not in bulk. +3. Draft pull request from the start; stay in the issue's scope. +4. Mark ready only when the change meets the Definition of Ready for Review — never with open plan steps. +5. Return unplanned work to [Define](Define.md) and hand review-ready work to [Review](Review.md). + +## Where this connects + +- [Contribution Workflow](../Contribution-Workflow.md) — the draft-first loop this runs. +- [Issue Lifecycle](../Issues/Process/Lifecycle.md) and [Issue Relationships](../Issues/Process/Relationships.md) — delivery-leaf eligibility and blockers. +- [Definition of Ready and Done](../Definition-of-Ready-and-Done.md) — the gate this hands off at. +- [PR Format](../PR-Format.md) and [Branching and Merging](../Branching-and-Merging.md) — packaging and landing. diff --git a/src/docs/Agents/agent-author.md b/src/docs/Ways-of-Working/Workflow-Stages/Maintain-Guidance.md similarity index 77% rename from src/docs/Agents/agent-author.md rename to src/docs/Ways-of-Working/Workflow-Stages/Maintain-Guidance.md index ea693e5..17b5a34 100644 --- a/src/docs/Agents/agent-author.md +++ b/src/docs/Ways-of-Working/Workflow-Stages/Maintain-Guidance.md @@ -5,7 +5,7 @@ description: Maintain the canonical Workflow stage procedures and the thin repos # Maintain Workflow Guidance -Maintain the canonical [Workflow](../Ways-of-Working/Workflow.md), the stage procedures in this section, and the per-repository pointer files that lead to the documentation root. Every pointer file stays thin. This maintenance path keeps the indexes, workflow, procedures, and pointers coherent; it does not encode process knowledge in configuration. +Maintain the canonical [Workflow](../Workflow.md), the stage procedures in this section, and the per-repository pointer files that lead to the documentation root. Every pointer file stays thin. This maintenance path keeps the indexes, workflow, procedures, and pointers coherent; it does not encode process knowledge in configuration. ## Enter this maintenance path when @@ -29,7 +29,7 @@ Write the stage as a page in this section, following the shape of its siblings: ### 3. Keep pointers thin -A repository never carries a copy of the workflow. Its `AGENTS.md` — and the `CLAUDE.md` that imports it — point to these pages and add only repo-specific nuance and the genuinely tool-specific settings (permission scopes, model choice) that cannot be expressed as a pointer. When a new runtime is adopted, add a thin pointer; do not move process knowledge into it. See [Agentic Development](../Ways-of-Working/Agentic-Development.md). +A repository never carries a copy of the workflow. Its `AGENTS.md` — and the `CLAUDE.md` that imports it — point to these pages and add only repo-specific nuance and the genuinely tool-specific settings (permission scopes, model choice) that cannot be expressed as a pointer. When a new runtime is adopted, add a thin pointer; do not move process knowledge into it. See [Agentic Development](../Agentic-Development.md). ### 4. Validate @@ -45,5 +45,5 @@ A repository never carries a copy of the workflow. Its `AGENTS.md` — and the ` ## Where this connects -- [Agentic Development](../Ways-of-Working/Agentic-Development.md) — the pointer model this maintains. -- [Documentation Model](../Ways-of-Working/Documentation-Model.md) — how these pages stay evergreen. +- [Agentic Development](../Agentic-Development.md) — the pointer model this maintains. +- [Documentation Model](../Documentation-Model.md) — how these pages stay evergreen. diff --git a/src/docs/Agents/reviewer.md b/src/docs/Ways-of-Working/Workflow-Stages/Review.md similarity index 62% rename from src/docs/Agents/reviewer.md rename to src/docs/Ways-of-Working/Workflow-Stages/Review.md index b23917f..80a6e8e 100644 --- a/src/docs/Agents/reviewer.md +++ b/src/docs/Ways-of-Working/Workflow-Stages/Review.md @@ -5,11 +5,11 @@ description: Procedure for the Workflow stage that independently reviews a pull # Review -Review is the independent-assessment stage of the canonical [Workflow](../Ways-of-Working/Workflow.md). It verifies that a pull request delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. This stage comments and approves; it does not change code, fix CI, or merge. +Review is the independent-assessment stage of the canonical [Workflow](../Workflow.md). It verifies that a pull request delivers its closing Task or Bug, applies good taste, respects security, and does not quietly introduce decisions that were not discussed. This stage comments and approves; it does not change code, fix CI, or merge. ## Enter this stage when -Review a pull request, check a change for delivery, verify acceptance criteria, or assess taste and standards. For a security-focused pass, enter [Security Review](security-reviewer.md). +Review a pull request, check a change for delivery, verify acceptance criteria, or assess taste and standards. For a security-focused pass, enter [Security Review](Security-Review.md). ## Input @@ -23,25 +23,25 @@ Confirm the pull request closes exactly one Task or Bug, then review against tha ### 2. Read the README -Some "missing" things are by design per the documented scope ([README-Driven Context](../Ways-of-Working/Readme-Driven-Context.md)). +Some "missing" things are by design per the documented scope ([README-Driven Context](../Readme-Driven-Context.md)). ### 3. Assess the diff -Check each dimension per [Review Etiquette](../Ways-of-Working/Review-Etiquette.md): +Check each dimension per [Review Etiquette](../Review-Etiquette.md): - **Delivery** — does the diff meet the acceptance criteria, without scope it did not ask for? - **Taste** — readability, naming, structure, tests that exercise behaviour. -- **Security** — input validation, no secrets in logs, SHA-pinned actions, least privilege. Escalate a deep pass to [Security Review](security-reviewer.md). +- **Security** — input validation, no secrets in logs, SHA-pinned actions, least privilege. Escalate a deep pass to [Security Review](Security-Review.md). - **Documentation** — updated where user-facing behaviour changed. - **Tests** — new behaviour has tests; bugs get regression tests. ### 4. Post the review -Use severity prefixes per [Review Etiquette](../Ways-of-Working/Review-Etiquette.md): `Blocking:`, `Question:`, `Suggestion:`, `Nit:`. Out-of-scope suggestions become new issues, not blocking comments. +Use severity prefixes per [Review Etiquette](../Review-Etiquette.md): `Blocking:`, `Question:`, `Suggestion:`, `Nit:`. Out-of-scope suggestions become new issues, not blocking comments. ### 5. Approve -An approval co-signs the change, so approve once the blocking concerns are resolved. The approving identity must not be the author and must not be the built-in Actions identity — approvals come from a separate reviewer identity per [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md). With auto-merge enabled, the approval is what lands the change. +An approval co-signs the change, so approve once the blocking concerns are resolved. The approving identity must not be the author and must not be the built-in Actions identity — approvals come from a separate reviewer identity per [Branching and Merging](../Branching-and-Merging.md). With auto-merge enabled, the approval is what lands the change. ## Operating rules @@ -50,11 +50,11 @@ An approval co-signs the change, so approve once the blocking concerns are resol 3. One concern per comment. 4. Apply repo standards; linter rules win where a standard is silent. 5. Security is non-negotiable. -6. No code changes, CI fixes, or merging in the Review stage; required changes return to [Implement](implement.md). +6. No code changes, CI fixes, or merging in the Review stage; required changes return to [Implement](Implement.md). ## Where this connects -- [Review Etiquette](../Ways-of-Working/Review-Etiquette.md) — tone, severity, and how to disagree well. -- [PR Format](../Ways-of-Working/PR-Format.md) — delivery-leaf closure and contextual aggregate links. -- [Branching and Merging](../Ways-of-Working/Branching-and-Merging.md) — who approves and how a change lands. -- [Security Review](security-reviewer.md) — the specialized security path. +- [Review Etiquette](../Review-Etiquette.md) — tone, severity, and how to disagree well. +- [PR Format](../PR-Format.md) — delivery-leaf closure and contextual aggregate links. +- [Branching and Merging](../Branching-and-Merging.md) — who approves and how a change lands. +- [Security Review](Security-Review.md) — the specialized security path. diff --git a/src/docs/Agents/security-reviewer.md b/src/docs/Ways-of-Working/Workflow-Stages/Security-Review.md similarity index 80% rename from src/docs/Agents/security-reviewer.md rename to src/docs/Ways-of-Working/Workflow-Stages/Security-Review.md index c6ac702..c62afe2 100644 --- a/src/docs/Agents/security-reviewer.md +++ b/src/docs/Ways-of-Working/Workflow-Stages/Security-Review.md @@ -5,7 +5,7 @@ description: Procedure for the specialized Workflow stage for defensive security # Security Review -Security Review is a specialized path in the canonical [Workflow](../Ways-of-Working/Workflow.md). On behalf of the code owner, it identifies vulnerabilities and attack vectors in source code and documentation and produces a clear, actionable responsible-disclosure issue for each finding. It uses a defender's mindset, not an attacker's, and produces no working exploit code. +Security Review is a specialized path in the canonical [Workflow](../Workflow.md). On behalf of the code owner, it identifies vulnerabilities and attack vectors in source code and documentation and produces a clear, actionable responsible-disclosure issue for each finding. It uses a defender's mindset, not an attacker's, and produces no working exploit code. ## Enter this stage when @@ -43,5 +43,5 @@ After the review, open a security issue on the target repository titled `[Securi ## Where this connects -- [Review Etiquette](../Ways-of-Working/Review-Etiquette.md) — how findings are communicated. -- [Security](../Coding-Standards/Security.md) and [GitHub Actions](../Coding-Standards/GitHub-Actions.md) — the standards a finding cites. +- [Review Etiquette](../Review-Etiquette.md) — how findings are communicated. +- [Security](../../Coding-Standards/Security.md) and [GitHub Actions](../../Coding-Standards/GitHub-Actions.md) — the standards a finding cites. diff --git a/src/docs/Ways-of-Working/Workflow-Stages/index.md b/src/docs/Ways-of-Working/Workflow-Stages/index.md new file mode 100644 index 0000000..9615069 --- /dev/null +++ b/src/docs/Ways-of-Working/Workflow-Stages/index.md @@ -0,0 +1,26 @@ +--- +title: Workflow Stages +description: Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. +--- + +# Workflow Stages + +The canonical [Workflow](../Workflow.md) owns the process, stage routing, and transitions. This section contains only the procedure for each stage: its entry condition, boundary, actions, and handoff. Humans and agents follow the same pages. + +The default discovery trail is [Home](../../index.md) → [Ways of Working](../index.md) → [Workflow](../Workflow.md) → this stage index → the resolved procedure. A task that clearly names its action may use the [Workflow routing table](../Workflow.md#find-the-current-stage) as a shortcut. + +No stage needs its own skill, command, named agent, or tool-specific process file. An integration may link directly to a stage for convenience, but the documentation remains the only process definition. [Maintain Workflow Guidance](Maintain-Guidance.md) keeps these procedures and thin repository pointers aligned. + +## Procedures + + + +| Page | Description | +| --- | --- | +| [Define](Define.md) | Procedure for the Workflow stage that captures, routes, refines, and plans work at the correct issue altitude. | +| [Implement](Implement.md) | Procedure for the Workflow stage that delivers one ready Task or Bug as a review-ready pull request. | +| [Review](Review.md) | Procedure for the Workflow stage that independently reviews a pull request for delivery, taste, security, and decisions. | +| [Security Review](Security-Review.md) | Procedure for the specialized Workflow stage for defensive security review and responsible disclosure. | +| [Maintain Workflow Guidance](Maintain-Guidance.md) | Maintain the canonical Workflow stage procedures and the thin repository pointers that lead to them. | + + diff --git a/src/docs/Ways-of-Working/Workflow.md b/src/docs/Ways-of-Working/Workflow.md index 6c39438..d80a38e 100644 --- a/src/docs/Ways-of-Working/Workflow.md +++ b/src/docs/Ways-of-Working/Workflow.md @@ -47,15 +47,15 @@ flowchart TD ## Find the current stage -Start from this page after following the documentation indexes: [Home](../index.md) → [Ways of Working](index.md) → Workflow. Infer the current stage from the work and its artifacts, then use the [stage procedure index](../Agents/index.md) or the direct links below. A stage is a part of this workflow, not a separate agent, skill, or instruction set. +Start from this page after following the documentation indexes: [Home](../index.md) → [Ways of Working](index.md) → Workflow. Infer the current stage from the work and its artifacts, then use the [stage procedure index](Workflow-Stages/index.md) or the direct links below. A stage is a part of this workflow, not a separate agent, skill, or instruction set. | Current work or prompt | Enter | Continue until | | --- | --- | --- | -| A request, signal, or issue needs to be captured, routed, refined, or planned. Prompts such as `Make this issue ` route here. | [Define](../Agents/define.md) | The issue meets the readiness gate for its altitude. | -| A ready Task or Bug needs implementation, or a pull request needs author-side changes. Prompts such as `Implement ` or `Fix ` route here. | [Implement](../Agents/implement.md) | The pull request meets the ready-for-review gate. | -| A pull request needs an independent assessment. Prompts such as `Review this PR ` route here. | [Review](../Agents/reviewer.md) | The change is approved or actionable feedback returns it to Implement. | -| The requested assessment is explicitly security-focused. Prompts such as `Security review ` route here. | [Security Review](../Agents/security-reviewer.md) | Findings are reported through the agreed channel and the review returns to its caller. | -| The workflow pages or repository pointers themselves need to change. | [Maintain Workflow Guidance](../Agents/agent-author.md) | The canonical docs and thin pointers agree. | +| A request, signal, or issue needs to be captured, routed, refined, or planned. Prompts such as `Make this issue ` route here. | [Define](Workflow-Stages/Define.md) | The issue meets the readiness gate for its altitude. | +| A ready Task or Bug needs implementation, or a pull request needs author-side changes. Prompts such as `Implement ` or `Fix ` route here. | [Implement](Workflow-Stages/Implement.md) | The pull request meets the ready-for-review gate. | +| A pull request needs an independent assessment. Prompts such as `Review this PR ` route here. | [Review](Workflow-Stages/Review.md) | The change is approved or actionable feedback returns it to Implement. | +| The requested assessment is explicitly security-focused. Prompts such as `Security review ` route here. | [Security Review](Workflow-Stages/Security-Review.md) | Findings are reported through the agreed channel and the review returns to its caller. | +| The workflow pages or repository pointers themselves need to change. | [Maintain Workflow Guidance](Workflow-Stages/Maintain-Guidance.md) | The canonical docs and thin pointers agree. | Keywords are shortcuts, not a second routing system. When a prompt is ambiguous, use the current issue, pull request, branch, and completed work to identify the stage. When work crosses a stage boundary, follow the handoff and load the next procedure rather than carrying separate process instructions through the whole task. diff --git a/src/docs/Ways-of-Working/index.md b/src/docs/Ways-of-Working/index.md index 7d3f3c9..2165221 100644 --- a/src/docs/Ways-of-Working/index.md +++ b/src/docs/Ways-of-Working/index.md @@ -16,6 +16,7 @@ This section documents the principles, processes, and norms that every contribut | Page | Description | | --- | --- | | [Workflow](Workflow.md) | The canonical process from idea to delivery, including how to resolve and enter each workflow stage. | +| [Workflow Stages](Workflow-Stages/index.md) | Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. | | [Contribution Workflow](Contribution-Workflow.md) | How a change travels from a branch to a review-ready pull request — draft first, the Copilot review loop, then people. | | [Fleet Orchestration](Fleet-Orchestration.md) | How one change is rolled out across many repositories through Task or Bug delivery leaves tracked entirely on GitHub. | | [Documentation Model](Documentation-Model.md) | How every capability is documented — a spec for the why and a design for the how, colocated, concise, and kept evergreen for humans and agents alike. | diff --git a/src/docs/index.md b/src/docs/index.md index 33824e4..04266a1 100644 --- a/src/docs/index.md +++ b/src/docs/index.md @@ -47,7 +47,6 @@ Each area below carries its own index. Start here, read the descriptions, and fo | [Vision](Vision/index.md) | The mission and the philosophy of easy, fast, and safe. | | [Initiatives](Initiatives/index.md) | The products that make the vision real — PSModule, AzActions, TFActions, and the tools built along the way. | | [Ways of Working](Ways-of-Working/index.md) | How work happens across the ecosystem — for humans and agents alike. | -| [Workflow Stages](Agents/index.md) | Procedures for each stage of the canonical Workflow, reached through indexes or direct task-language shortcuts. | | [Coding Standards](Coding-Standards/index.md) | The shared baseline every repository inherits, and the per-language standards that build on it. | | [Capabilities](Capabilities/index.md) | The capabilities the ecosystem builds, each documented by a spec (why and what) and a design (how and what). | | [Frameworks](Frameworks/index.md) | The complete, end-to-end frameworks the ecosystem ships — opinionated automation a project adopts wholesale to go from source to shipped. | diff --git a/src/zensical.toml b/src/zensical.toml index 3ef3c1f..755e0b1 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -27,12 +27,12 @@ nav = [ "Ways-of-Working/index.md", {"Workflow" = [ "Ways-of-Working/Workflow.md", - "Agents/index.md", - {"Define" = "Agents/define.md"}, - {"Implement" = "Agents/implement.md"}, - {"Review" = "Agents/reviewer.md"}, - {"Security Review" = "Agents/security-reviewer.md"}, - {"Maintain Workflow Guidance" = "Agents/agent-author.md"}, + "Ways-of-Working/Workflow-Stages/index.md", + {"Define" = "Ways-of-Working/Workflow-Stages/Define.md"}, + {"Implement" = "Ways-of-Working/Workflow-Stages/Implement.md"}, + {"Review" = "Ways-of-Working/Workflow-Stages/Review.md"}, + {"Security Review" = "Ways-of-Working/Workflow-Stages/Security-Review.md"}, + {"Maintain Workflow Guidance" = "Ways-of-Working/Workflow-Stages/Maintain-Guidance.md"}, ]}, {"Contribution Workflow" = "Ways-of-Working/Contribution-Workflow.md"}, {"Fleet Orchestration" = "Ways-of-Working/Fleet-Orchestration.md"}, From 0c62e6e862dbf5d93311424d437b4ffe26643513 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:03:41 +0200 Subject: [PATCH 15/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Req?= =?UTF-8?q?uire=20fresh=20context=20repositories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 9 +-- bootstrap/AGENTS.template.md | 31 ++++++++- bootstrap/Initialize-MsxWorkspace.ps1 | 67 ++++++++++++++++--- bootstrap/README.md | 33 ++++++++- .../Frameworks/Agentic-Development/design.md | 2 +- .../Frameworks/Agentic-Development/spec.md | 1 + .../Ways-of-Working/Agentic-Development.md | 2 +- 7 files changed, 129 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 67ac756..106213e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,9 +10,10 @@ Canonical context: Before acting: 1. Segment the work by host, organization, repository, path, and task. -2. Start at the docs root index and follow [Ways of Working](src/docs/Ways-of-Working/index.md) to the canonical [Workflow](src/docs/Ways-of-Working/Workflow.md). -3. Infer the current stage from the task and its artifacts, then read the linked stage procedure. -4. Read [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), relevant standards, and organization memory. -5. Apply path-specific local rules only when they match the files in scope. +2. Synchronize every canonical context repository to its remote default branch; stop if any context may be stale. +3. Start at the docs root index and follow [Ways of Working](src/docs/Ways-of-Working/index.md) to the canonical [Workflow](src/docs/Ways-of-Working/Workflow.md). +4. Infer the current stage from the task and its artifacts, then read the linked stage procedure. +5. Read [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), relevant standards, and organization memory. +6. Apply path-specific local rules only when they match the files in scope. This file points to process knowledge; it does not define an agent, skill, or workflow stage. diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 8a24965..abd797c 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -4,7 +4,7 @@ The single starting point for any agent, in any repository. Before doing anythin ## First — bootstrap the workspace -The workspace is a git-isolated clone of the central repositories under `~/.msx`. Set it up (idempotent — clones what is missing, attempts to fast-forward the rest): +The workspace is a git-isolated clone of the central repositories under `~/.msx`. Set it up before reading context. Existing context repositories must be clean, on their default branch, and exactly synchronized with the remote: ```powershell $docs = Join-Path $HOME '.msx/docs' @@ -17,8 +17,37 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ($LASTEXITCODE -ne 0) { throw "git clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and github.com credentials, then re-run." } +} else { + $refspec = '+refs/heads/*:refs/remotes/origin/*' + if ($refspec -notin @(git -C $docs config --get-all remote.origin.fetch)) { + git -C $docs config --add remote.origin.fetch $refspec + if ($LASTEXITCODE -ne 0) { + throw "Could not configure remote tracking branches for MSXOrg/docs (exit $LASTEXITCODE)." + } + } + git -C $docs fetch origin --prune --quiet + if ($LASTEXITCODE -ne 0) { + throw "git fetch of MSXOrg/docs failed (exit $LASTEXITCODE). Do not use stale context." + } + $branch = (git -C $docs branch --show-current | Out-String).Trim() + if ($branch -ne 'main') { + throw "$docs is on '$branch', not 'main'. Switch branches before using this context." + } + if (@(git -C $docs status --porcelain).Count -gt 0) { + throw "$docs has uncommitted changes. Resolve them before using this context." + } + git -C $docs merge --ff-only --quiet origin/main + if ($LASTEXITCODE -ne 0) { + throw "MSXOrg/docs cannot fast-forward to origin/main. Do not use stale context." + } + if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse origin/main)) { + throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." + } } pwsh (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') +if ($LASTEXITCODE -ne 0) { + throw "MSX workspace synchronization failed. Do not read context until every repository is current." +} ``` This produces: diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index ad644d8..a016e31 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -22,13 +22,14 @@ - Memory (MSXOrg/memory) is append-only context; notes are committed and pushed to main directly, without a pull request. - The script is idempotent: it clones what is missing and attempts to - fast-forward what is already present, leaving a repository unchanged (with a - warning) when it cannot fast-forward. + The script is idempotent: it clones what is missing and synchronizes every + existing context repository to the exact remote default-branch head. It stops + before context is read when a repository is dirty, on another branch, locally + ahead, diverged, or unavailable. .EXAMPLE ./Initialize-MsxWorkspace.ps1 - Clones missing repositories and attempts to fast-forward existing ones under ~/.msx. + Clones missing repositories and exactly synchronizes existing ones under ~/.msx. .EXAMPLE ./Initialize-MsxWorkspace.ps1 -Root /work/.msx -Verbose @@ -74,15 +75,65 @@ if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { $results = foreach ($repo in $repositories) { $path = Join-Path $Root $repo.Name if (Test-Path (Join-Path $path '.git')) { - if ($PSCmdlet.ShouldProcess($path, 'Fetch and fast-forward')) { + if ($PSCmdlet.ShouldProcess($path, 'Fetch and synchronize default branch')) { Write-Verbose "Updating $path" - git -C $path fetch origin --quiet + $allBranchesRefspec = '+refs/heads/*:refs/remotes/origin/*' + $fetchRefspecs = @(git -C $path config --get-all remote.origin.fetch) + if ($LASTEXITCODE -notin @(0, 1)) { + throw "git config remote.origin.fetch failed for '$path' (exit $LASTEXITCODE)." + } + if ($allBranchesRefspec -notin $fetchRefspecs) { + git -C $path config --add remote.origin.fetch $allBranchesRefspec + if ($LASTEXITCODE -ne 0) { + throw "Could not configure remote tracking branches for '$path' (exit $LASTEXITCODE)." + } + } + + git -C $path fetch origin --prune --quiet if ($LASTEXITCODE -ne 0) { throw "git fetch failed for '$path' (exit $LASTEXITCODE). Check network access and credentials for $($repo.Url)." } - git -C $path pull --ff-only --quiet + + git -C $path remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Cannot detect the remote default branch for '$path'. Check origin before using this context." + } + $defaultRef = (git -C $path symbolic-ref --quiet --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Cannot resolve the remote default branch for '$path'. Repair origin/HEAD before using this context." + } + $defaultBranch = $defaultRef -replace '^origin/', '' + $currentBranch = (git -C $path branch --show-current | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "git branch --show-current failed for '$path' (exit $LASTEXITCODE)." + } + if ($currentBranch -ne $defaultBranch) { + throw "'$path' is on '$currentBranch', not the default branch '$defaultBranch'. Switch branches before using this context." + } + + $status = @(git -C $path status --porcelain) + if ($LASTEXITCODE -ne 0) { + throw "git status failed for '$path' (exit $LASTEXITCODE)." + } + if ($status.Count -gt 0) { + throw "'$path' has uncommitted changes. Commit, push, or remove them before using this context." + } + + git -C $path merge --ff-only --quiet $defaultRef if ($LASTEXITCODE -ne 0) { - Write-Warning "Could not fast-forward '$path' (local changes or diverged history). Left as-is." + throw "Could not fast-forward '$path' to '$defaultRef'. Resolve its diverged history before using this context." + } + + $localHead = (git -C $path rev-parse HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "git rev-parse HEAD failed for '$path' (exit $LASTEXITCODE)." + } + $remoteHead = (git -C $path rev-parse $defaultRef | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "git rev-parse '$defaultRef' failed for '$path' (exit $LASTEXITCODE)." + } + if ($localHead -ne $remoteHead) { + throw "'$path' is not exactly synchronized with '$defaultRef'. Push or reconcile local commits before using this context." } } } else { diff --git a/bootstrap/README.md b/bootstrap/README.md index d45ab25..9e9f0d6 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -4,7 +4,7 @@ The single starting point for agents: a git-isolated local clone of the MSX cent ## Contents -- `Initialize-MsxWorkspace.ps1` — idempotent setup. Clones `MSXOrg/docs` and `MSXOrg/memory` under `~/.msx`, attempts to fast-forward them if present, and writes a repository-local git identity so the workspace never modifies the global git config. +- `Initialize-MsxWorkspace.ps1` — idempotent setup. Clones `MSXOrg/docs` and `MSXOrg/memory` under `~/.msx`, requires existing clones to exactly match their remote default branches, and writes a repository-local git identity so the workspace never modifies the global git config. - `AGENTS.template.md` — the user-global entry instruction. It bootstraps the workspace, then points the agent at the docs and memory. Install it once per machine (below). ## The model @@ -14,6 +14,8 @@ The single starting point for agents: a git-isolated local clone of the MSX cent > **Prerequisite:** `MSXOrg/memory` is a private repository — the bootstrap needs access to it (and working github.com credentials) to clone or update memory. +Before either repository is used, bootstrap fetches it and requires a clean checkout on the remote default branch at the exact remote head. A dirty, locally ahead, diverged, wrong-branch, or unreachable context repository stops bootstrap; stale context is never treated as a successful fallback. + Keeping the workspace separate and git-isolated means an agent reads the same docs and memory in every repository, and its commits there use the workspace identity rather than whatever the working repository or the global config happens to be set to. The loaded `AGENTS.md` points to the roots; discovery happens in documentation. Start at `~/.msx/docs/src/docs/index.md`, follow Ways of Working to Workflow, infer the current stage, and read the linked procedure. Clear task language can shortcut stage selection, but no skill or instruction file owns a separate copy of the process. @@ -33,8 +35,37 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ($LASTEXITCODE -ne 0) { throw "git clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and github.com credentials, then re-run." } +} else { + $refspec = '+refs/heads/*:refs/remotes/origin/*' + if ($refspec -notin @(git -C $docs config --get-all remote.origin.fetch)) { + git -C $docs config --add remote.origin.fetch $refspec + if ($LASTEXITCODE -ne 0) { + throw "Could not configure remote tracking branches for MSXOrg/docs (exit $LASTEXITCODE)." + } + } + git -C $docs fetch origin --prune --quiet + if ($LASTEXITCODE -ne 0) { + throw "git fetch of MSXOrg/docs failed (exit $LASTEXITCODE). Do not use stale context." + } + $branch = (git -C $docs branch --show-current | Out-String).Trim() + if ($branch -ne 'main') { + throw "$docs is on '$branch', not 'main'. Switch branches before using this context." + } + if (@(git -C $docs status --porcelain).Count -gt 0) { + throw "$docs has uncommitted changes. Resolve them before using this context." + } + git -C $docs merge --ff-only --quiet origin/main + if ($LASTEXITCODE -ne 0) { + throw "MSXOrg/docs cannot fast-forward to origin/main. Do not use stale context." + } + if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse origin/main)) { + throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." + } } pwsh (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') +if ($LASTEXITCODE -ne 0) { + throw "MSX workspace synchronization failed. Do not read context until every repository is current." +} ``` Wire it into the tools so it runs as the first instruction: diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index 927f5fc..e00c2d2 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -222,7 +222,7 @@ A local bootstrap makes central context predictable: memory/ ``` -The bootstrap clones missing repositories and fast-forwards existing clones when possible. It writes repository-local git configuration only. If a context repository cannot update, the agent uses the local copy and reports that it may be stale. +The bootstrap clones missing repositories and fetches every existing context repository before use. Each clone must be clean, checked out on the remote default branch, and exactly equal to the fetched remote head. A dirty, locally ahead, diverged, wrong-branch, or unreachable clone stops context resolution; the agent does not use a possibly stale local copy. Bootstrap writes repository-local git configuration only. ## Memory writing rules diff --git a/src/docs/Frameworks/Agentic-Development/spec.md b/src/docs/Frameworks/Agentic-Development/spec.md index 52d1b45..4aac8e5 100644 --- a/src/docs/Frameworks/Agentic-Development/spec.md +++ b/src/docs/Frameworks/Agentic-Development/spec.md @@ -61,6 +61,7 @@ Applies to any organization that wants a shared project knowledge base and memor - **Client adapters.** Tool-specific files such as `CLAUDE.md`, `.github/copilot-instructions.md`, and `.github/instructions/*.instructions.md` MAY add runtime-specific loading or path rules, but MUST point back to the same canonical roots and MUST NOT define workflow behavior. - **Deterministic context resolution.** Agents MUST resolve context in layers: system and client policy, user preferences, organization docs, organization memory, repository pointers, path-specific instructions, then current task context. - **Local-first availability.** The docs and memory repositories SHOULD be available locally in a predictable workspace so agents can read them without relying on search or web access. +- **Fresh context before use.** Every canonical context repository MUST be fetched and exactly synchronized with its remote default branch before its contents are read. Dirty, locally ahead, diverged, wrong-branch, or unreachable repositories MUST stop context resolution rather than fall back to stale content. - **Reviewed knowledge changes.** Changes to the `docs` repository MUST happen through pull requests. Changes to memory MAY be lighter-weight, but MUST remain versioned in git. - **No cross-project bleed.** An agent working in one organization MUST NOT apply another organization's standards or memory unless the current task explicitly asks for cross-organization work. - **Traceable memory.** Memory entries SHOULD identify the context they came from and SHOULD be short, factual, and linked to the relevant issue, pull request, document, or repository when one exists. diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index 0071f52..12e662f 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -82,7 +82,7 @@ The workspace is a git-isolated clone of the central repositories under `~/.msx` - `~/.msx/docs` — this documentation, read as local files. Changes to it go through pull requests. - `~/.msx/memory` — durable notes and prior session context. Changes to it are pushed to main. -Each clone carries repository-local git config only, so the workspace never modifies the global git config or the repository the agent is working in — git still reads them, but only repository-local config is written. The setup is one idempotent script — [`bootstrap/Initialize-MsxWorkspace.ps1`](https://github.com/MSXOrg/docs/blob/main/bootstrap/Initialize-MsxWorkspace.ps1) — that clones what is missing and attempts to fast-forward the rest, leaving a repository as-is if it cannot. This keeps "start at the same point" literal: every agent, in every repository, begins from the same local docs and memory. +Each clone carries repository-local git config only, so the workspace never modifies the global git config or the repository the agent is working in — git still reads them, but only repository-local config is written. Before context is read, [`bootstrap/Initialize-MsxWorkspace.ps1`](https://github.com/MSXOrg/docs/blob/main/bootstrap/Initialize-MsxWorkspace.ps1) clones missing repositories and requires every existing context repository to be clean, on its remote default branch, and exactly synchronized with the remote head. Any update failure stops context resolution rather than allowing stale guidance or memory. The workspace makes the *central* context present locally; the same local-first stance shapes how each working repository is laid out. Repositories are cloned as [git worktrees](Git-Worktrees.md) — one working directory per branch — so a person and an agent, or several agents, can work on multiple issues in the same repository at once without stashing or switching branches. From 14021219e7cb15f5d6d97371ee685600951923e0 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:13:24 +0200 Subject: [PATCH 16/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Gate=20discovery?= =?UTF-8?q?=20on=20fresh=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Frameworks/Agentic-Development/design.md | 38 +++++++++---------- .../Frameworks/Agentic-Development/index.md | 2 +- .../Frameworks/Agentic-Development/spec.md | 20 +++++----- .../Ways-of-Working/Agentic-Development.md | 13 ++++--- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index e00c2d2..806b9cf 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -134,17 +134,14 @@ flowchart TD locate --> host{"Which project scope?"} host -->|"dnb.ghe.com / AI-Platform"| aip["AI-Platform context"] - host -->|"github.com / MSXOrg"| msx["MSXOrg context"] - host -->|"github.com / PSModule"| psmodule["PSModule context"] + host -->|"github.com/MSXOrg"| msx["MSXOrg context"] + host -->|"github.com/PSModule"| psmodule["PSModule context"] - aip --> aipdocs["Read AI-Platform/docs index"] - aipdocs --> workflow["Follow indexes to Workflow"] - - msx --> msxdocs["Read MSXOrg/docs index"] - msxdocs --> workflow - - psmodule --> psdocs["Read PSModule/docs index"] - psdocs --> workflow + aip --> refresh["Refresh selected docs + memory
stop unless exactly synchronized"] + msx --> refresh + psmodule --> refresh + refresh --> docs["Read selected docs index"] + docs --> workflow["Follow indexes to Workflow"] workflow --> stage["Infer current stage
read canonical procedure"] stage --> memory["Read organization memory index"] @@ -177,10 +174,11 @@ Canonical project context: Before changing files: 1. Segment the work by host, organization, repository, path, and task. -2. Start at the resolved project `docs/index.md`. -3. Follow the Ways of Working index to Workflow, infer the current stage, and read that stage procedure. -4. Read the relevant project standards, repository README and local docs, and organization memory. -5. Apply path-specific local rules for the files being changed. +2. Refresh every canonical context repository and stop unless each exactly matches its remote default branch. +3. Start at the resolved project `docs/index.md`. +4. Follow the Ways of Working index to Workflow, infer the current stage, and read that stage procedure. +5. Read the relevant project standards, repository README and local docs, and organization memory. +6. Apply path-specific local rules for the files being changed. This file points; it does not define process knowledge. ``` @@ -200,7 +198,7 @@ The index trail is the default. A clear prompt can shortcut stage discovery: `Re ```markdown Follow `AGENTS.md`. -Segment the work by host, organization, repository, path, and task. Start at the resolved organization docs root index and follow its Workflow before editing. Use path-specific instruction files only for local path rules when their `applyTo` pattern matches a file being read, generated, reviewed, or edited. +Segment the work by host, organization, repository, path, and task. Refresh the resolved canonical context repositories and stop on any synchronization failure. Only then start at the organization docs root index and follow its Workflow. Use path-specific instruction files only for local path rules when their `applyTo` pattern matches a file being read, generated, reviewed, or edited. ``` Path-specific instruction files are reserved for local rules that cannot live centrally because they apply only to a repository path. They never define workflow stages. @@ -242,10 +240,10 @@ Different clients load different files, but the framework keeps the same depende | Client | Adapter | Behavior | | --- | --- | --- | -| Cross-client agents | `AGENTS.md` | Resolve the shared docs and memory roots, then traverse indexes to Workflow and the current stage. | +| Cross-client agents | `AGENTS.md` | Resolve and synchronize the shared docs and memory roots, then traverse indexes to Workflow and the current stage. | | Claude Code | `CLAUDE.md` | Import `AGENTS.md`; add no duplicated process knowledge. | -| GitHub Copilot in VS Code | `.github/copilot-instructions.md` and `.github/instructions/*.instructions.md` | Follow `AGENTS.md`, then apply path-specific local rules when files match. | -| Copilot coding agent | `AGENTS.md`, `.github/copilot-instructions.md`, setup workflow | Prepare the local roots, traverse the canonical indexes, and follow the resolved stage procedure. | +| GitHub Copilot in VS Code | `.github/copilot-instructions.md` and `.github/instructions/*.instructions.md` | Follow `AGENTS.md`, including its freshness gate, then apply path-specific local rules when files match. | +| Copilot coding agent | `AGENTS.md`, `.github/copilot-instructions.md`, setup workflow | Synchronize the local roots, traverse the canonical indexes, and follow the resolved stage procedure. | | Copilot code review | Base-branch instructions | Review using trusted base-branch instructions rather than instructions changed by the PR under review. | ## Failure modes @@ -253,7 +251,7 @@ Different clients load different files, but the framework keeps the same depende | Failure | Design response | | --- | --- | | Repository does not identify its organization context | Infer from remote URL; ask when ambiguous. | -| Docs or memory clone is missing | Bootstrap it before work; if unavailable, continue only with explicit warning. | +| A docs or memory clone is missing or cannot synchronize | Bootstrap or repair it, then retry. Stop context resolution until every canonical context repository passes the freshness gate. | | Pointer file duplicates central standards | Replace duplicated content with links during review. | | A skill, command, named agent, or instruction file defines a workflow stage | Delete the duplicate procedure and link to Workflow or its stage page. | | Memory conflicts with docs | Docs win; memory is corrected or removed. | @@ -268,7 +266,7 @@ Different clients load different files, but the framework keeps the same depende 4. Add the canonical Workflow and linked stage procedures to `docs`. 5. Add starter memory sections to `memory`. 6. Add thin pointer files to each product repository. -7. Add a bootstrap that keeps local docs and memory clones present. +7. Add a bootstrap that keeps local docs and memory clones present and exactly synchronized before use. 8. Review new work for pointer discipline: facts live once, links point to them. ## Where this connects diff --git a/src/docs/Frameworks/Agentic-Development/index.md b/src/docs/Frameworks/Agentic-Development/index.md index 3cc2256..20849ac 100644 --- a/src/docs/Frameworks/Agentic-Development/index.md +++ b/src/docs/Frameworks/Agentic-Development/index.md @@ -13,7 +13,7 @@ A repository adopts the framework by carrying thin agent pointer files and by le | Page | Description | | --- | --- | -| [Spec](spec.md) | Requirements for index-first agentic development through org-scoped documentation, memory, and thin pointer files. | +| [Spec](spec.md) | Requirements for refresh-first, index-first agentic development through canonical documentation, memory, and thin pointers. | | [Design](design.md) | How the agentic development framework is built — OKF documentation, org memory, thin repo pointers, and deterministic context resolution. | | [Memory Repository Template](memory-template.md) | The concrete, copy-pasteable scaffold every organization's memory repository instantiates, and why it deliberately breaks from the Repository Standard. | diff --git a/src/docs/Frameworks/Agentic-Development/spec.md b/src/docs/Frameworks/Agentic-Development/spec.md index 4aac8e5..eb06e0b 100644 --- a/src/docs/Frameworks/Agentic-Development/spec.md +++ b/src/docs/Frameworks/Agentic-Development/spec.md @@ -1,6 +1,6 @@ --- title: Spec -description: Requirements for index-first agentic development through org-scoped documentation, memory, and thin pointer files. +description: Requirements for refresh-first, index-first agentic development through canonical documentation, memory, and thin pointers. --- # Agentic Development — Spec @@ -34,7 +34,7 @@ Applies to any organization that wants a shared project knowledge base and memor - Organization-level `docs` and `memory` repositories. - Markdown documents with YAML frontmatter, following the [Open Knowledge Format](../../Dictionary/index.md#open-knowledge-format) model. - Thin repository pointer files such as `AGENTS.md` and client adapters that import or follow it. -- Index-first discovery from the organization docs root to the canonical Workflow and its stage procedures. +- Refresh-first, index-first discovery from canonical context repositories to the Workflow and its stage procedures. - Deterministic context resolution by host, organization, repository, path, and task. - Human-reviewed changes to canonical knowledge through pull requests. - Durable agent memory that can be shared by every person and agent working in the organization. @@ -54,12 +54,12 @@ Applies to any organization that wants a shared project knowledge base and memor - **OKF-style documents.** Knowledge and memory documents MUST be Markdown files with YAML frontmatter, one primary concept per page, and stable paths that act as identity. - **Small pages and indexes.** Documentation and memory SHOULD prefer small pages, each folder SHOULD have an `index.md`, and indexes MUST let a human or agent navigate inward from the root. - **Thin pointer files.** Product repositories MUST carry pointer files that identify the organization context and link to the canonical docs and memory root indexes. Pointer files MUST NOT duplicate standards, workflow stages, or process knowledge. -- **Index-first workflow discovery.** From the docs root index, a human or agent MUST be able to reach Ways of Working, the canonical Workflow, and the procedure for the current stage by following page descriptions and links. +- **Refresh-first, index-first workflow discovery.** After every canonical context repository passes the freshness gate, a human or agent MUST be able to follow the docs root index to Ways of Working, the canonical Workflow, and the procedure for the current stage. - **Stage resolution from work.** Agents MUST infer the current stage from the prompt and current artifacts. Explicit task language MAY shortcut to the matching stage, but the shortcut MUST resolve to the canonical documentation. - **One process source.** Skills, commands, named agents, and tool-specific instruction files MUST NOT redefine Workflow stages. A client convenience MAY link to a stage procedure and add only runtime mechanics. - **Segmentation before loading.** Local agent files MUST instruct agents to segment work by host, organization, repository, path, and task before loading project standards or memory. - **Client adapters.** Tool-specific files such as `CLAUDE.md`, `.github/copilot-instructions.md`, and `.github/instructions/*.instructions.md` MAY add runtime-specific loading or path rules, but MUST point back to the same canonical roots and MUST NOT define workflow behavior. -- **Deterministic context resolution.** Agents MUST resolve context in layers: system and client policy, user preferences, organization docs, organization memory, repository pointers, path-specific instructions, then current task context. +- **Deterministic context resolution.** Agents MUST resolve context in layers: system and client policy, user preferences, repository pointer, context-repository freshness gate, organization docs, organization memory, repository context, path-specific rules, then current task context. - **Local-first availability.** The docs and memory repositories SHOULD be available locally in a predictable workspace so agents can read them without relying on search or web access. - **Fresh context before use.** Every canonical context repository MUST be fetched and exactly synchronized with its remote default branch before its contents are read. Dirty, locally ahead, diverged, wrong-branch, or unreachable repositories MUST stop context resolution rather than fall back to stale content. - **Reviewed knowledge changes.** Changes to the `docs` repository MUST happen through pull requests. Changes to memory MAY be lighter-weight, but MUST remain versioned in git. @@ -75,6 +75,7 @@ Applies to any organization that wants a shared project knowledge base and memor - A human can start at `docs/index.md` or `memory/index.md` and navigate to the same context an agent uses. - A human or agent can follow `docs/index.md` → Ways of Working → Workflow → the current stage procedure without knowing a file path in advance. - A prompt such as `Review this PR ` reaches the Review procedure directly, while `Make this issue ` reaches Define, without a parallel process definition. +- A missing, dirty, locally ahead, diverged, wrong-branch, or unreachable canonical context repository stops discovery before any context index is read. - Updating a standard in `docs` changes the canonical guidance without editing every repository. - Capturing a recurring lesson in `memory` makes it available to later agents working in the same organization. @@ -85,11 +86,12 @@ The framework uses this normative resolution order: 1. **System and client policy** — non-project instructions imposed by the agent runtime. 2. **User-global preferences** — the human operator's baseline style and risk posture. 3. **Repository pointer** — `AGENTS.md` identifies the host, organization, canonical docs root, memory root, and repository-local nuance. -4. **Organization docs** — start at `docs/index.md`, traverse to Ways of Working and Workflow, resolve the current stage, then load the relevant standards, specs, and designs. -5. **Organization memory** — start at `memory/index.md`, then load relevant lessons, gotchas, and active context. -6. **Repository context** — README, CONTRIBUTING, local docs, and narrow repository exceptions. -7. **Path-specific rules** — scoped local rules that apply to the files being read, generated, reviewed, or edited. -8. **Current task context** — issue, pull request, prompt, branch, diff, diagnostics, terminal output, and open files; use these artifacts to re-evaluate the stage after each handoff. +4. **Freshness gate** — fetch every canonical context repository and stop unless each clean default-branch checkout exactly matches its remote head. +5. **Organization docs** — start at `docs/index.md`, traverse to Ways of Working and Workflow, resolve the current stage, then load the relevant standards, specs, and designs. +6. **Organization memory** — start at `memory/index.md`, then load relevant lessons, gotchas, and active context. +7. **Repository context** — README, CONTRIBUTING, local docs, and narrow repository exceptions. +8. **Path-specific rules** — scoped local rules that apply to the files being read, generated, reviewed, or edited. +9. **Current task context** — issue, pull request, prompt, branch, diff, diagnostics, terminal output, and open files; use these artifacts to re-evaluate the stage after each handoff. A lower layer MAY refine a higher layer, but MUST NOT contradict it unless the higher layer explicitly allows a local exception. diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index 12e662f..e7d35f3 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -31,15 +31,16 @@ When an agent receives work, it follows the same documentation trail a human can ```mermaid flowchart TD task["Agent receives work"] --> pointer["1 Read AGENTS.md
resolve project docs + memory roots"] - pointer --> root["2 Read docs/index.md"] - root --> ways["3 Follow Ways of Working index"] - ways --> workflow["4 Read Workflow"] - workflow --> stage["5 Infer the current stage
read its procedure"] - stage --> context["6 Read relevant standards,
repository context, and memory"] + pointer --> refresh["2 Refresh every context repository
stop unless exactly synchronized"] + refresh --> root["3 Read docs/index.md"] + root --> ways["4 Follow Ways of Working index"] + ways --> workflow["5 Read Workflow"] + workflow --> stage["6 Infer the current stage
read its procedure"] + stage --> context["7 Read relevant standards,
repository context, and memory"] context --> work["Act and follow stage handoffs"] ``` -The indexes are the default discovery mechanism. [Workflow](Workflow.md) owns the process and routes the work to a [stage procedure](Workflow-Stages/index.md); the stage page then points to the standards and artifacts it consumes. A clear prompt such as `Review this PR ` may shortcut directly through the Workflow routing table, but it does not create a second process definition. **Local files never replace central standards — they layer specifics on top.** +Refresh is a gate before traversal, not a best-effort background step. After it passes, the indexes are the default discovery mechanism. [Workflow](Workflow.md) owns the process and routes the work to a [stage procedure](Workflow-Stages/index.md); the stage page then points to the standards and artifacts it consumes. A clear prompt such as `Review this PR ` may shortcut directly through the Workflow routing table, but it does not create a second process definition. **Local files never replace central standards — they layer specifics on top.** ## Where documentation lives From 8665d707d007fec35855f23a045fc6c51c09edd4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:13:29 +0200 Subject: [PATCH 17/30] =?UTF-8?q?=E2=9C=85=20[Tests]:=20Cover=20context=20?= =?UTF-8?q?freshness=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/Initialize-MsxWorkspace.Tests.ps1 | 170 ++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 tests/Initialize-MsxWorkspace.Tests.ps1 diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 new file mode 100644 index 0000000..830d45c --- /dev/null +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -0,0 +1,170 @@ +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' } + +Describe 'Initialize-MsxWorkspace context freshness' { + BeforeAll { + $script:bootstrap = Join-Path $PSScriptRoot '../bootstrap/Initialize-MsxWorkspace.ps1' + $script:pwsh = (Get-Process -Id $PID).Path + + function Invoke-Git { + param( + [Parameter()] + [string] $WorkingDirectory, + + [Parameter(Mandatory)] + [string[]] $Arguments + ) + + $output = if ($WorkingDirectory) { + & git -C $WorkingDirectory @Arguments 2>&1 + } else { + & git @Arguments 2>&1 + } + if ($LASTEXITCODE -ne 0) { + throw "git $($Arguments -join ' ') failed in '$WorkingDirectory': $($output | Out-String)" + } + return $output + } + + function Add-TestCommit { + param( + [Parameter(Mandatory)] + [string] $Repository, + + [Parameter(Mandatory)] + [string] $Name + ) + + Add-Content -LiteralPath (Join-Path $Repository 'context.txt') -Value $Name + Invoke-Git -WorkingDirectory $Repository -Arguments @('add', 'context.txt') | Out-Null + Invoke-Git -WorkingDirectory $Repository -Arguments @('commit', '--quiet', '-m', $Name) | Out-Null + } + + function New-ContextFixture { + $root = Join-Path ([IO.Path]::GetTempPath()) "msx-bootstrap-$([guid]::NewGuid().ToString('N'))" + $workspace = Join-Path $root 'workspace' + $remotes = Join-Path $root 'remotes' + $writers = Join-Path $root 'writers' + New-Item -ItemType Directory -Path $workspace, $remotes, $writers -Force | Out-Null + + $writerMap = @{} + foreach ($name in @('docs', 'memory')) { + $remote = Join-Path $remotes "$name.git" + $writer = Join-Path $writers $name + $checkout = Join-Path $workspace $name + + Invoke-Git -Arguments @('init', '--bare', '--quiet', '--initial-branch=main', $remote) | Out-Null + Invoke-Git -Arguments @('clone', '--quiet', $remote, $writer) | Out-Null + Invoke-Git -WorkingDirectory $writer -Arguments @('config', 'user.name', 'Fixture Writer') | Out-Null + Invoke-Git -WorkingDirectory $writer -Arguments @('config', 'user.email', 'fixture@example.invalid') | Out-Null + Set-Content -LiteralPath (Join-Path $writer 'context.txt') -Value "$name context" + Invoke-Git -WorkingDirectory $writer -Arguments @('add', 'context.txt') | Out-Null + Invoke-Git -WorkingDirectory $writer -Arguments @('commit', '--quiet', '-m', "Initialize $name") | Out-Null + Invoke-Git -WorkingDirectory $writer -Arguments @('push', '--quiet', '--set-upstream', 'origin', 'main') | Out-Null + Invoke-Git -Arguments @('clone', '--quiet', $remote, $checkout) | Out-Null + Invoke-Git -WorkingDirectory $checkout -Arguments @('config', 'user.name', 'Fixture Local') | Out-Null + Invoke-Git -WorkingDirectory $checkout -Arguments @('config', 'user.email', 'fixture-local@example.invalid') | Out-Null + $writerMap[$name] = $writer + } + + return [pscustomobject]@{ + Root = $root + Workspace = $workspace + Writers = $writerMap + Docs = Join-Path $workspace 'docs' + } + } + + function Invoke-BootstrapFixture { + param([Parameter(Mandatory)] $Fixture) + + $output = & $script:pwsh -NoProfile -File $script:bootstrap ` + -Root $Fixture.Workspace ` + -UserName 'Fixture User' ` + -UserEmail 'fixture@example.invalid' 2>&1 | Out-String + + return [pscustomobject]@{ + ExitCode = $LASTEXITCODE + Output = $output + } + } + } + + BeforeEach { + $fixture = New-ContextFixture + } + + AfterEach { + if ($fixture -and (Test-Path -LiteralPath $fixture.Root)) { + Remove-Item -LiteralPath $fixture.Root -Recurse -Force + } + } + + It 'fast-forwards a clean behind checkout to the exact remote head' { + Add-TestCommit -Repository $fixture.Writers.docs -Name 'Advance docs' + Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null + $remoteHead = (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Be 0 + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $remoteHead + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $remoteHead + } + + It 'rejects a dirty checkout without updating it' { + Set-Content -LiteralPath (Join-Path $fixture.Docs 'dirty.txt') -Value 'uncommitted' + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'uncommitted changes' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before + } + + It 'rejects a locally ahead checkout without stale fallback' { + Add-TestCommit -Repository $fixture.Docs -Name 'Local only' + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'not exactly synchronized' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before + } + + It 'rejects diverged history without updating the checkout' { + Add-TestCommit -Repository $fixture.Docs -Name 'Local divergence' + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + Add-TestCommit -Repository $fixture.Writers.docs -Name 'Remote divergence' + Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'Could not fast-forward' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before + } + + It 'rejects a checkout on a non-default branch' { + Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('switch', '--quiet', '-c', 'topic') | Out-Null + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match "not the default branch 'main'" + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('branch', '--show-current')).Trim() | Should -BeExactly 'topic' + } + + It 'rejects an unreachable remote without using local context' { + $missing = Join-Path $fixture.Root 'missing.git' + Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('remote', 'set-url', 'origin', $missing) | Out-Null + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'git fetch failed' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before + } +} From b4790fff33760a00332607bcfede7ce82d57ec35 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:18:13 +0200 Subject: [PATCH 18/30] =?UTF-8?q?=E2=9C=85=20[Tests]:=20Satisfy=20PowerShe?= =?UTF-8?q?ll=20fixture=20analysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/Initialize-MsxWorkspace.Tests.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 830d45c..64a2468 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -40,7 +40,13 @@ Describe 'Initialize-MsxWorkspace context freshness' { } function New-ContextFixture { + [CmdletBinding(SupportsShouldProcess)] + param() + $root = Join-Path ([IO.Path]::GetTempPath()) "msx-bootstrap-$([guid]::NewGuid().ToString('N'))" + if (-not $PSCmdlet.ShouldProcess($root, 'Create context fixture')) { + return + } $workspace = Join-Path $root 'workspace' $remotes = Join-Path $root 'remotes' $writers = Join-Path $root 'writers' @@ -91,6 +97,7 @@ Describe 'Initialize-MsxWorkspace context freshness' { BeforeEach { $fixture = New-ContextFixture + $fixture | Should -Not -BeNullOrEmpty } AfterEach { From a40ef22adee824d3bafe1585ffb1b0eddb7e7909 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:22:27 +0200 Subject: [PATCH 19/30] =?UTF-8?q?=E2=9C=85=20[Tests]:=20Verify=20all=20con?= =?UTF-8?q?text=20repos=20synchronize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/Initialize-MsxWorkspace.Tests.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 64a2468..8ed2fb9 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -77,6 +77,7 @@ Describe 'Initialize-MsxWorkspace context freshness' { Workspace = $workspace Writers = $writerMap Docs = Join-Path $workspace 'docs' + Memory = Join-Path $workspace 'memory' } } @@ -109,13 +110,18 @@ Describe 'Initialize-MsxWorkspace context freshness' { It 'fast-forwards a clean behind checkout to the exact remote head' { Add-TestCommit -Repository $fixture.Writers.docs -Name 'Advance docs' Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null - $remoteHead = (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + Add-TestCommit -Repository $fixture.Writers.memory -Name 'Advance memory' + Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('push', '--quiet') | Out-Null + $docsHead = (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + $memoryHead = (Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('rev-parse', 'HEAD')).Trim() $result = Invoke-BootstrapFixture -Fixture $fixture $result.ExitCode | Should -Be 0 - (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $remoteHead - (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $remoteHead + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $docsHead + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $docsHead + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $memoryHead + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $memoryHead } It 'rejects a dirty checkout without updating it' { From a1a49b1530d4726ffa0daf7a2c84168af9fb58b8 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:46:42 +0200 Subject: [PATCH 20/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Pre?= =?UTF-8?q?serve=20pluggable=20agent=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 61 ++++++++++++- bootstrap/AGENTS.template.md | 33 ++++++- bootstrap/Initialize-MsxWorkspace.ps1 | 85 ++++++++++++++++--- bootstrap/README.md | 25 ++++++ .../Frameworks/Agentic-Development/design.md | 4 +- .../Frameworks/Agentic-Development/spec.md | 3 +- .../Ways-of-Working/Agentic-Development.md | 2 +- tests/Initialize-MsxWorkspace.Tests.ps1 | 41 +++++++++ 8 files changed, 231 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 106213e..f6f22df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,13 +1,58 @@ -# Agent Pointer +# Agent Instructions + +## Main directive + +Everything is a work in progress and can be updated and improved. Fix a small problem when it is directly in scope; register a larger or unrelated problem as an issue in the repository that owns it. This repository belongs to `github.com/MSXOrg`. -Canonical context: +## Install and synchronize the ecosystem + +The agent workspace lives under `~/.msx`: + +| Repository | Local path | Purpose | Change model | +| --- | --- | --- | --- | +| `MSXOrg/docs` | `~/.msx/docs` | Reviewed organization context: workflow, standards, capabilities, and framework guidance. | Pull requests only. | +| `MSXOrg/memory` | `~/.msx/memory` | Durable organization memory: prior decisions, gotchas, and reusable working knowledge. | Follow the repository's own contribution policy. | + +From this repository, install missing context repositories and synchronize every existing clone before use: + +```powershell +pwsh bootstrap/Initialize-MsxWorkspace.ps1 ` + -UserName '' ` + -UserEmail '' +``` + +Projects that add their own docs and memory provide plug-in coordinates without changing the synchronization implementation: + +```powershell +$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = 'https://github.com/MSXOrg/docs.git' + MemoryUrl = 'https://github.com/MSXOrg/memory.git' + } + @{ + Name = 'PSModule' + Path = 'projects/PSModule' + DocsUrl = 'https://github.com/PSModule/docs.git' + MemoryUrl = 'https://github.com/PSModule/memory.git' + } +) +& ./bootstrap/Initialize-MsxWorkspace.ps1 -Project $projects +``` + +The bootstrap writes identity only to each context repository's local git configuration. It must succeed before any context is read; do not continue with missing, dirty, diverged, wrong-branch, unreachable, or stale context. + +Use a dedicated worktree for every topic branch. Follow [Git Worktrees](src/docs/Ways-of-Working/Git-Worktrees.md) for the local layout and [Branching and Merging](src/docs/Ways-of-Working/Branching-and-Merging.md) for `/-` branch names. + +## Canonical context - Docs root: [src/docs/index.md](src/docs/index.md) - Organization memory: `~/.msx/memory/index.md` -Before acting: +## Before acting 1. Segment the work by host, organization, repository, path, and task. 2. Synchronize every canonical context repository to its remote default branch; stop if any context may be stale. @@ -16,4 +61,12 @@ Before acting: 5. Read [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md), relevant standards, and organization memory. 6. Apply path-specific local rules only when they match the files in scope. -This file points to process knowledge; it does not define an agent, skill, or workflow stage. +## Working in this repository + +1. Use [README.md](README.md) to understand what this repository is and how it builds. +2. Use [CONTRIBUTING.md](CONTRIBUTING.md) for its contribution and review contract. +3. Keep work reviewable with small, descriptive micro-commits. +4. Push every commit so the remote branch, CI, and draft pull request reflect current work. +5. Improve organization memory when a verified lesson is likely to matter again; follow the memory repository's own instructions before writing. + +This file owns bootstrap and repository-specific operating instructions. The linked documentation owns reusable process knowledge; this file does not redefine a workflow stage, coding standard, or review convention. diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index abd797c..73b2200 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -2,6 +2,10 @@ The single starting point for any agent, in any repository. Before doing anything else, make sure the central workspace exists locally, then read from it. +## Main directive + +Everything is a work in progress and can be updated and improved. Fix a small problem when it is directly in scope; register a larger or unrelated problem as an issue in the repository that owns it. + ## First — bootstrap the workspace The workspace is a git-isolated clone of the central repositories under `~/.msx`. Set it up before reading context. Existing context repositories must be clean, on their default branch, and exactly synchronized with the remote: @@ -44,12 +48,29 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." } } -pwsh (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') +$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = 'https://github.com/MSXOrg/docs.git' + MemoryUrl = 'https://github.com/MSXOrg/memory.git' + } + # Add project-specific entries when this template is adopted there: + # @{ + # Name = 'PSModule' + # Path = 'projects/PSModule' + # DocsUrl = 'https://github.com/PSModule/docs.git' + # MemoryUrl = 'https://github.com/PSModule/memory.git' + # } +) +& (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') -Project $projects if ($LASTEXITCODE -ne 0) { - throw "MSX workspace synchronization failed. Do not read context until every repository is current." + throw "Context synchronization failed. Do not read context until every project is current." } ``` +Keep the MSXOrg entry and add only the additional project coordinates required by repositories that inherit this template. Every project reuses the same synchronization and validation implementation. + This produces: - `~/.msx/docs` — how work is done: ways of working, coding standards, and the agent workflow. The same content published at . @@ -68,6 +89,14 @@ Each clone has repository-local git config only; it never modifies the global gi Clear task language may shortcut the index trail: `Review this PR ` enters Review, `Make this issue ` enters Define, and `Implement ` enters Implement. The linked documentation owns each procedure; this file does not define a separate agent or skill. +## Work in the selected repository + +1. Read its `README.md` to understand the repository and its build. +2. Read its `CONTRIBUTING.md` for the contribution and review contract. +3. Use a dedicated worktree and the branch naming defined by the canonical Ways of Working. +4. Make small, descriptive micro-commits and push every commit so remote state, CI, and the draft pull request stay current. +5. Capture verified reusable lessons in organization memory, following that repository's own instructions. + ## Two write rules - **Docs change through pull requests.** Branch inside `~/.msx/docs` and open a pull request; never push its `main`. diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index a016e31..bf45ed4 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -3,13 +3,13 @@ <# .SYNOPSIS - Clone or update the MSX central workspace (docs + memory) in a git-isolated location under $HOME. + Clone or update canonical project context repositories in a git-isolated workspace under $HOME. .DESCRIPTION The single starting point for every agent. It ensures the central - documentation and memory repositories exist locally under one dedicated - workspace, so an agent reads the same evergreen docs and the same prior - memory regardless of which repository it is working in. + documentation and memory repositories for each configured project exist + locally under one dedicated workspace, so an agent reads current canonical + context regardless of which repository it is working in. The workspace is deliberately kept separate from the repositories an agent works in: @@ -17,10 +17,10 @@ - Each clone gets repository-local git config only. Nothing here modifies the global git config or the working repository's config; git still reads global and system config as usual, but this script writes only repository-local config. - - Documentation (MSXOrg/docs) is context and is changed through pull requests - only; this script never pushes its main branch. - - Memory (MSXOrg/memory) is append-only context; notes are committed and - pushed to main directly, without a pull request. + - Documentation is reviewed context and changes through pull requests; this + script never pushes a docs repository. + - Memory is durable context and follows its owning repository's contribution + policy; this script never writes or pushes memory content. The script is idempotent: it clones what is missing and synchronizes every existing context repository to the exact remote default-branch head. It stops @@ -35,6 +35,18 @@ ./Initialize-MsxWorkspace.ps1 -Root /work/.msx -Verbose Uses a custom workspace root and logs each step. +.EXAMPLE + $projects = @( + @{ + Name = 'PSModule' + Path = 'projects/PSModule' + DocsUrl = 'https://github.com/PSModule/docs.git' + MemoryUrl = 'https://github.com/PSModule/memory.git' + } + ) + ./Initialize-MsxWorkspace.ps1 -Project $projects + Installs a project's docs and memory under a project-specific workspace path. + .OUTPUTS [pscustomobject] with Repository, Path, and Changes for each workspace repository. #> @@ -53,7 +65,19 @@ param( # The git author email written to each clone's local config. [Parameter()] [ValidateNotNullOrEmpty()] - [string] $UserEmail = 'MariusStorhaug@users.noreply.github.com' + [string] $UserEmail = 'MariusStorhaug@users.noreply.github.com', + + # Projects whose canonical docs and memory repositories must be synchronized. + [Parameter()] + [ValidateNotNullOrEmpty()] + [hashtable[]] $Project = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = 'https://github.com/MSXOrg/docs.git' + MemoryUrl = 'https://github.com/MSXOrg/memory.git' + } + ) ) Set-StrictMode -Version Latest @@ -63,17 +87,49 @@ if ((-not $PSBoundParameters.ContainsKey('UserName')) -or (-not $PSBoundParamete Write-Warning "Using part of the default maintainer identity ($UserName <$UserEmail>). Pass both -UserName and -UserEmail to attribute your own commits (memory pushes to main)." } -$repositories = @( - [pscustomobject]@{ Name = 'docs'; Url = 'https://github.com/MSXOrg/docs.git'; Changes = 'pull requests' } - [pscustomobject]@{ Name = 'memory'; Url = 'https://github.com/MSXOrg/memory.git'; Changes = 'push to main' } -) +$repositories = foreach ($projectDefinition in $Project) { + foreach ($key in @('Name', 'Path', 'DocsUrl', 'MemoryUrl')) { + if (-not $projectDefinition.ContainsKey($key) -or $null -eq $projectDefinition[$key]) { + throw "Project definitions require Name, Path, DocsUrl, and MemoryUrl. Missing '$key'." + } + } + + $projectName = [string] $projectDefinition.Name + $projectPath = [string] $projectDefinition.Path + if (-not $projectName.Trim()) { + throw 'Project Name must not be empty.' + } + if ([IO.Path]::IsPathRooted($projectPath) -or '..' -in ($projectPath -split '[\\/]')) { + throw "Project Path '$projectPath' must be a safe path relative to the workspace root." + } + + $docsPath = if ($projectPath) { Join-Path $projectPath 'docs' } else { 'docs' } + $memoryPath = if ($projectPath) { Join-Path $projectPath 'memory' } else { 'memory' } + [pscustomobject]@{ + Name = "$projectName/docs" + RelativePath = $docsPath + Url = [string] $projectDefinition.DocsUrl + Changes = 'pull requests' + } + [pscustomobject]@{ + Name = "$projectName/memory" + RelativePath = $memoryPath + Url = [string] $projectDefinition.MemoryUrl + Changes = 'repository policy' + } +} + +$duplicatePaths = $repositories | Group-Object RelativePath | Where-Object Count -gt 1 +if ($duplicatePaths) { + throw "Project definitions contain duplicate workspace paths: $($duplicatePaths.Name -join ', ')." +} if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { New-Item -ItemType Directory -Force -Path $Root | Out-Null } $results = foreach ($repo in $repositories) { - $path = Join-Path $Root $repo.Name + $path = Join-Path $Root $repo.RelativePath if (Test-Path (Join-Path $path '.git')) { if ($PSCmdlet.ShouldProcess($path, 'Fetch and synchronize default branch')) { Write-Verbose "Updating $path" @@ -142,6 +198,7 @@ $results = foreach ($repo in $repositories) { } if ($PSCmdlet.ShouldProcess($repo.Url, "Clone into '$path'")) { Write-Verbose "Cloning $($repo.Url) into $path" + New-Item -ItemType Directory -Path (Split-Path -Parent $path) -Force | Out-Null git clone --quiet $repo.Url $path if ($LASTEXITCODE -ne 0) { throw "git clone failed for $($repo.Url) (exit $LASTEXITCODE). Check access and credentials (MSXOrg/memory is private)." diff --git a/bootstrap/README.md b/bootstrap/README.md index 9e9f0d6..422d469 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -11,6 +11,7 @@ The single starting point for agents: a git-isolated local clone of the MSX cent - `~/.msx/docs` is **read context** — the ways of working, coding standards, and agent workflow. Changes to it go through **pull requests**. - `~/.msx/memory` is **append-only context** — durable notes and session history. Changes to it are **pushed to main**. +- `~/.msx/projects//{docs,memory}` holds optional project-specific context added through bootstrap plug-ins. > **Prerequisite:** `MSXOrg/memory` is a private repository — the bootstrap needs access to it (and working github.com credentials) to clone or update memory. @@ -68,6 +69,30 @@ if ($LASTEXITCODE -ne 0) { } ``` +## Add project context + +The default project is MSXOrg. A repository in another project declares additional docs and memory coordinates in its agent installation chapter and passes them to the same bootstrap: + +```powershell +$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = 'https://github.com/MSXOrg/docs.git' + MemoryUrl = 'https://github.com/MSXOrg/memory.git' + } + @{ + Name = 'PSModule' + Path = 'projects/PSModule' + DocsUrl = 'https://github.com/PSModule/docs.git' + MemoryUrl = 'https://github.com/PSModule/memory.git' + } +) +& (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') -Project $projects +``` + +Each plug-in uses the same fail-closed freshness validation. `Path` is relative to `~/.msx`, so projects can choose a collision-free location without forking the bootstrap. + Wire it into the tools so it runs as the first instruction: - **Claude Code** reads `CLAUDE.md`. Add an import to `~/.claude/CLAUDE.md`: diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index 806b9cf..282f436 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -69,7 +69,7 @@ Product repositories carry local context and thin pointers: docs/ ``` -The repository owns only repository-specific nuance: build commands, architecture notes, local exceptions, and path-scoped rules. Cross-cutting standards remain in `docs`; reusable lessons remain in `memory`. +The repository owns only repository-specific nuance: bootstrap entry points, build commands, contribution mechanics, architecture notes, local exceptions, and path-scoped rules. Cross-cutting standards remain in `docs`; reusable lessons remain in `memory`. Thin means "no duplicated reusable process," not "discard the local operating contract." ## OKF page model @@ -222,6 +222,8 @@ A local bootstrap makes central context predictable: The bootstrap clones missing repositories and fetches every existing context repository before use. Each clone must be clean, checked out on the remote default branch, and exactly equal to the fetched remote head. A dirty, locally ahead, diverged, wrong-branch, or unreachable clone stops context resolution; the agent does not use a possibly stale local copy. Bootstrap writes repository-local git configuration only. +MSXOrg is the default project. Additional projects plug in a name, relative workspace path, docs URL, and memory URL. For example, PSModule can use `projects/PSModule/{docs,memory}` beneath the same workspace while reusing the identical synchronization and validation path. Repository agent files retain this small coordinate block because it is required before project documentation can be reached; the reusable bootstrap behavior remains central. + ## Memory writing rules Agents write memory only when a lesson is likely to matter again. Good memory is: diff --git a/src/docs/Frameworks/Agentic-Development/spec.md b/src/docs/Frameworks/Agentic-Development/spec.md index eb06e0b..d79c1f4 100644 --- a/src/docs/Frameworks/Agentic-Development/spec.md +++ b/src/docs/Frameworks/Agentic-Development/spec.md @@ -51,9 +51,10 @@ Applies to any organization that wants a shared project knowledge base and memor - **Organization is the project boundary.** The framework MUST resolve project context from the Git host and organization before resolving repository-specific context. - **Canonical docs repository.** Each adopting organization MUST have a `docs` repository that owns the reviewed knowledge base. - **Canonical memory repository.** Each adopting organization MUST have a `memory` repository that owns durable project memory and agent working knowledge. +- **Pluggable project context.** The bootstrap MUST accept project-specific docs and memory coordinates and collision-free relative workspace paths without requiring a fork of its synchronization logic. - **OKF-style documents.** Knowledge and memory documents MUST be Markdown files with YAML frontmatter, one primary concept per page, and stable paths that act as identity. - **Small pages and indexes.** Documentation and memory SHOULD prefer small pages, each folder SHOULD have an `index.md`, and indexes MUST let a human or agent navigate inward from the root. -- **Thin pointer files.** Product repositories MUST carry pointer files that identify the organization context and link to the canonical docs and memory root indexes. Pointer files MUST NOT duplicate standards, workflow stages, or process knowledge. +- **Thin pointer files.** Product repositories MUST carry pointer files that identify the organization context and link to the canonical docs and memory root indexes. They MAY retain agent-only bootstrap steps and repository-specific operating instructions needed to reach or safely change that context. They MUST NOT duplicate standards, workflow stages, or reusable process knowledge. - **Refresh-first, index-first workflow discovery.** After every canonical context repository passes the freshness gate, a human or agent MUST be able to follow the docs root index to Ways of Working, the canonical Workflow, and the procedure for the current stage. - **Stage resolution from work.** Agents MUST infer the current stage from the prompt and current artifacts. Explicit task language MAY shortcut to the matching stage, but the shortcut MUST resolve to the canonical documentation. - **One process source.** Skills, commands, named agents, and tool-specific instruction files MUST NOT redefine Workflow stages. A client convenience MAY link to a stage procedure and add only runtime mechanics. diff --git a/src/docs/Ways-of-Working/Agentic-Development.md b/src/docs/Ways-of-Working/Agentic-Development.md index e7d35f3..54d74c8 100644 --- a/src/docs/Ways-of-Working/Agentic-Development.md +++ b/src/docs/Ways-of-Working/Agentic-Development.md @@ -24,7 +24,7 @@ This spec rests on the [Principles](Principles/index.md). Four apply directly: ## Architecture -Agent configuration files are **pointers, not containers**. They identify the canonical documentation root; they do not select a persona or contain workflow stages. Documentation lives where it belongs — repo-specific docs in each repository's `README.md`, `CONTRIBUTING.md`, and `docs/`; cross-cutting standards in the org-level documentation site. +Agent configuration files are **pointers, not process containers**. They identify canonical context and may retain the bootstrap steps and repository-specific operating instructions an agent needs before it can reach that context. They do not select a persona, copy workflow stages, or restate standards. Documentation lives where it belongs — repo-specific context in each repository's `README.md`, `CONTRIBUTING.md`, and `docs/`; cross-cutting guidance in the org-level documentation site. When an agent receives work, it follows the same documentation trail a human can follow: diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 8ed2fb9..d045e6b 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -53,6 +53,7 @@ Describe 'Initialize-MsxWorkspace context freshness' { New-Item -ItemType Directory -Path $workspace, $remotes, $writers -Force | Out-Null $writerMap = @{} + $remoteMap = @{} foreach ($name in @('docs', 'memory')) { $remote = Join-Path $remotes "$name.git" $writer = Join-Path $writers $name @@ -70,12 +71,14 @@ Describe 'Initialize-MsxWorkspace context freshness' { Invoke-Git -WorkingDirectory $checkout -Arguments @('config', 'user.name', 'Fixture Local') | Out-Null Invoke-Git -WorkingDirectory $checkout -Arguments @('config', 'user.email', 'fixture-local@example.invalid') | Out-Null $writerMap[$name] = $writer + $remoteMap[$name] = $remote } return [pscustomobject]@{ Root = $root Workspace = $workspace Writers = $writerMap + Remotes = $remoteMap Docs = Join-Path $workspace 'docs' Memory = Join-Path $workspace 'memory' } @@ -180,4 +183,42 @@ Describe 'Initialize-MsxWorkspace context freshness' { $result.Output | Should -Match 'git fetch failed' (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before } + + It 'installs additional project context through plug-in coordinates' { + $runner = Join-Path $fixture.Root 'invoke-project-bootstrap.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } + @{ + Name = 'Project' + Path = 'projects/Project' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Be 0 -Because $output + $projectDocs = Join-Path $fixture.Workspace 'projects/Project/docs' + $projectMemory = Join-Path $fixture.Workspace 'projects/Project/memory' + Test-Path -LiteralPath (Join-Path $projectDocs '.git') | Should -BeTrue + Test-Path -LiteralPath (Join-Path $projectMemory '.git') | Should -BeTrue + (Invoke-Git -WorkingDirectory $projectDocs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + (Invoke-Git -WorkingDirectory $projectMemory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('rev-parse', 'HEAD')).Trim() + } } From 5a573b34181f0038558f5a8b7d49002408301f20 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:51:58 +0200 Subject: [PATCH 21/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Cla?= =?UTF-8?q?rify=20bootstrap=20identity=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/Initialize-MsxWorkspace.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index bf45ed4..2ffb248 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -84,7 +84,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ((-not $PSBoundParameters.ContainsKey('UserName')) -or (-not $PSBoundParameters.ContainsKey('UserEmail'))) { - Write-Warning "Using part of the default maintainer identity ($UserName <$UserEmail>). Pass both -UserName and -UserEmail to attribute your own commits (memory pushes to main)." + Write-Warning "Using part of the default maintainer identity ($UserName <$UserEmail>). Pass both -UserName and -UserEmail to set your own repository-local author identity." } $repositories = foreach ($projectDefinition in $Project) { From 7994668ae2ab9de4ad6f4072e11b13b8b5467e08 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:56:38 +0200 Subject: [PATCH 22/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Defer=20memory?= =?UTF-8?q?=20writes=20to=20repository=20policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/AGENTS.template.md | 2 +- bootstrap/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 73b2200..827a081 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -100,4 +100,4 @@ Clear task language may shortcut the index trail: `Review this PR ` enters ## Two write rules - **Docs change through pull requests.** Branch inside `~/.msx/docs` and open a pull request; never push its `main`. -- **Memory pushes to main.** Commit and push notes directly inside `~/.msx/memory`; no pull request. +- **Memory follows repository policy.** Read the selected memory repository's `AGENTS.md` and `CONTRIBUTING.md` before writing. diff --git a/bootstrap/README.md b/bootstrap/README.md index 422d469..85df4d4 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -10,7 +10,7 @@ The single starting point for agents: a git-isolated local clone of the MSX cent ## The model - `~/.msx/docs` is **read context** — the ways of working, coding standards, and agent workflow. Changes to it go through **pull requests**. -- `~/.msx/memory` is **append-only context** — durable notes and session history. Changes to it are **pushed to main**. +- `~/.msx/memory` is **durable context** — notes and session history governed by that repository's contribution policy. - `~/.msx/projects//{docs,memory}` holds optional project-specific context added through bootstrap plug-ins. > **Prerequisite:** `MSXOrg/memory` is a private repository — the bootstrap needs access to it (and working github.com credentials) to clone or update memory. From 212fab421d319b6958a76060c8ded2cb57d52d29 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 12:02:07 +0200 Subject: [PATCH 23/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Nor?= =?UTF-8?q?malize=20project=20context=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/Initialize-MsxWorkspace.ps1 | 6 +++-- tests/Initialize-MsxWorkspace.Tests.ps1 | 33 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 2ffb248..7df40b8 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -95,13 +95,15 @@ $repositories = foreach ($projectDefinition in $Project) { } $projectName = [string] $projectDefinition.Name - $projectPath = [string] $projectDefinition.Path + $projectPath = ([string] $projectDefinition.Path).Trim() if (-not $projectName.Trim()) { throw 'Project Name must not be empty.' } - if ([IO.Path]::IsPathRooted($projectPath) -or '..' -in ($projectPath -split '[\\/]')) { + $pathSegments = @($projectPath -split '[\\/]' | Where-Object { $_ -and $_ -ne '.' }) + if ([IO.Path]::IsPathRooted($projectPath) -or '..' -in $pathSegments) { throw "Project Path '$projectPath' must be a safe path relative to the workspace root." } + $projectPath = $pathSegments -join [IO.Path]::DirectorySeparatorChar $docsPath = if ($projectPath) { Join-Path $projectPath 'docs' } else { 'docs' } $memoryPath = if ($projectPath) { Join-Path $projectPath 'memory' } else { 'memory' } diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index d045e6b..4319ac9 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -200,7 +200,7 @@ Describe 'Initialize-MsxWorkspace context freshness' { } @{ Name = 'Project' - Path = 'projects/Project' + Path = './projects/Project/' DocsUrl = '$docsRemote' MemoryUrl = '$memoryRemote' } @@ -221,4 +221,35 @@ exit `$LASTEXITCODE (Invoke-Git -WorkingDirectory $projectMemory -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('rev-parse', 'HEAD')).Trim() } + + It 'rejects duplicate project paths after normalization' { + $runner = Join-Path $fixture.Root 'invoke-duplicate-bootstrap.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'One' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } + @{ + Name = 'Two' + Path = '.' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'duplicate workspace paths' + } } From c4f4adb8fd0277e84c8e5e01d82cad70b895bed1 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 12:27:06 +0200 Subject: [PATCH 24/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Res?= =?UTF-8?q?tore=20bare=20docs=20worktree=20topology?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 6 +- bootstrap/AGENTS.template.md | 23 +- bootstrap/Initialize-MsxWorkspace.ps1 | 297 +++++++++++++----- bootstrap/README.md | 25 +- .../Frameworks/Agentic-Development/design.md | 17 +- src/docs/Ways-of-Working/Git-Worktrees.md | 59 ++-- tests/Initialize-MsxWorkspace.Tests.ps1 | 63 +++- 7 files changed, 362 insertions(+), 128 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f6f22df..5966e0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,8 +12,8 @@ The agent workspace lives under `~/.msx`: | Repository | Local path | Purpose | Change model | | --- | --- | --- | --- | -| `MSXOrg/docs` | `~/.msx/docs` | Reviewed organization context: workflow, standards, capabilities, and framework guidance. | Pull requests only. | -| `MSXOrg/memory` | `~/.msx/memory` | Durable organization memory: prior decisions, gotchas, and reusable working knowledge. | Follow the repository's own contribution policy. | +| `MSXOrg/docs` | `~/.msx/docs.git` + `~/.msx/docs` | Bare backing repository plus clean readable main worktree for reviewed organization context. | Pull requests through topic worktrees only. | +| `MSXOrg/memory` | `~/.msx/memory` | Durable organization memory: prior decisions, gotchas, and reusable working knowledge. | Commit and push directly to `main`, per that repository's policy. | From this repository, install missing context repositories and synchronize every existing clone before use: @@ -67,6 +67,6 @@ Use a dedicated worktree for every topic branch. Follow [Git Worktrees](src/docs 2. Use [CONTRIBUTING.md](CONTRIBUTING.md) for its contribution and review contract. 3. Keep work reviewable with small, descriptive micro-commits. 4. Push every commit so the remote branch, CI, and draft pull request reflect current work. -5. Improve organization memory when a verified lesson is likely to matter again; follow the memory repository's own instructions before writing. +5. Improve organization memory when a verified lesson is likely to matter again; commit and push MSXOrg memory directly to `main`. This file owns bootstrap and repository-specific operating instructions. The linked documentation owns reusable process knowledge; this file does not redefine a workflow stage, coding standard, or review convention. diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 827a081..28f20e2 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -12,14 +12,28 @@ The workspace is a git-isolated clone of the central repositories under `~/.msx` ```powershell $docs = Join-Path $HOME '.msx/docs' +$docsBacking = "$docs.git" if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { throw "$docs exists but is not a git repository. Remove it and re-run." } if (-not (Test-Path (Join-Path $docs '.git'))) { - New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null - git clone https://github.com/MSXOrg/docs.git $docs + if (-not (Test-Path $docsBacking)) { + New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null + git clone --bare https://github.com/MSXOrg/docs.git $docsBacking + if ($LASTEXITCODE -ne 0) { + throw "Bare clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and credentials." + } + git --git-dir=$docsBacking config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' + git --git-dir=$docsBacking fetch origin --prune --quiet + } + if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { + throw "$docsBacking exists but is not a bare repository." + } + $defaultBranch = git --git-dir=$docsBacking symbolic-ref HEAD | + ForEach-Object { $_ -replace 'refs/heads/', '' } + git --git-dir=$docsBacking worktree add $docs $defaultBranch if ($LASTEXITCODE -ne 0) { - throw "git clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and github.com credentials, then re-run." + throw "Could not create the canonical MSXOrg/docs worktree at $docs." } } else { $refspec = '+refs/heads/*:refs/remotes/origin/*' @@ -73,7 +87,8 @@ Keep the MSXOrg entry and add only the additional project coordinates required b This produces: -- `~/.msx/docs` — how work is done: ways of working, coding standards, and the agent workflow. The same content published at . +- `~/.msx/docs.git` — bare backing repository for central docs. +- `~/.msx/docs` — clean, readable main worktree containing ways of working, standards, and workflow guidance. - `~/.msx/memory` — what has been learned before: durable notes and prior session context. Each clone has repository-local git config only; it never modifies the global git config or the repository being worked in (git still reads them, but only repository-local config is written). diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 7df40b8..33fe035 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -14,13 +14,12 @@ The workspace is deliberately kept separate from the repositories an agent works in: - - Each clone gets repository-local git config only. Nothing here modifies the - global git config or the working repository's config; git still reads global - and system config as usual, but this script writes only repository-local config. - - Documentation is reviewed context and changes through pull requests; this - script never pushes a docs repository. - - Memory is durable context and follows its owning repository's contribution - policy; this script never writes or pushes memory content. + - Each docs repository uses a bare backing repository plus a canonical clean + default-branch worktree. Topic branches use separate worktrees. + - Each memory repository remains a simple default-branch checkout. + - Every checkout gets repository-local git config only. Nothing here modifies + global git config or the working product repository. + - The script synchronizes context but never writes or pushes repository content. The script is idempotent: it clones what is missing and synchronizes every existing context repository to the exact remote default-branch head. It stops @@ -48,7 +47,8 @@ Installs a project's docs and memory under a project-specific workspace path. .OUTPUTS - [pscustomobject] with Repository, Path, and Changes for each workspace repository. + [pscustomobject] with Repository, Path, BackingPath, and Changes for each + workspace repository. #> [CmdletBinding(SupportsShouldProcess)] param( @@ -87,6 +87,123 @@ if ((-not $PSBoundParameters.ContainsKey('UserName')) -or (-not $PSBoundParamete Write-Warning "Using part of the default maintainer identity ($UserName <$UserEmail>). Pass both -UserName and -UserEmail to set your own repository-local author identity." } +function Sync-ContextRemote { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $GitPath, + + [Parameter(Mandatory)] + [string] $RepositoryUrl, + + [Parameter()] + [switch] $Bare + ) + + if (-not $PSCmdlet.ShouldProcess($GitPath, 'Fetch canonical remote state')) { + return + } + + [string[]] $gitRoot = if ($Bare) { @("--git-dir=$GitPath") } else { @('-C', $GitPath) } + $allBranchesRefspec = '+refs/heads/*:refs/remotes/origin/*' + $fetchRefspecs = @(& git @gitRoot config --get-all remote.origin.fetch) + if ($LASTEXITCODE -notin @(0, 1)) { + throw "git config remote.origin.fetch failed for '$GitPath' (exit $LASTEXITCODE)." + } + if ($allBranchesRefspec -notin $fetchRefspecs) { + & git @gitRoot config --add remote.origin.fetch $allBranchesRefspec + if ($LASTEXITCODE -ne 0) { + throw "Could not configure remote tracking branches for '$GitPath' (exit $LASTEXITCODE)." + } + } + + & git @gitRoot fetch origin --prune --quiet + if ($LASTEXITCODE -ne 0) { + throw "git fetch failed for '$GitPath' (exit $LASTEXITCODE). Check access to $RepositoryUrl." + } + & git @gitRoot remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Cannot detect the remote default branch for '$GitPath'. Check origin before using this context." + } + + $defaultRef = (& git @gitRoot symbolic-ref --quiet --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Cannot resolve the remote default branch for '$GitPath'. Repair origin/HEAD before using this context." + } + return [pscustomobject]@{ + DefaultRef = $defaultRef + DefaultBranch = $defaultRef -replace '^origin/', '' + RemoteHead = (& git @gitRoot rev-parse $defaultRef | Out-String).Trim() + } +} + +function Sync-ContextCheckout { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $Path, + + [Parameter(Mandatory)] + [string] $RepositoryUrl + ) + + $remote = Sync-ContextRemote -GitPath $Path -RepositoryUrl $RepositoryUrl -Confirm:$false + if (-not $remote -or -not $PSCmdlet.ShouldProcess($Path, "Synchronize $($remote.DefaultBranch)")) { + return $remote + } + + $currentBranch = (git -C $Path branch --show-current | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "git branch --show-current failed for '$Path' (exit $LASTEXITCODE)." + } + if ($currentBranch -ne $remote.DefaultBranch) { + throw "'$Path' is on '$currentBranch', not the default branch '$($remote.DefaultBranch)'. Switch branches before using this context." + } + + $status = @(git -C $Path status --porcelain) + if ($LASTEXITCODE -ne 0) { + throw "git status failed for '$Path' (exit $LASTEXITCODE)." + } + if ($status.Count -gt 0) { + throw "'$Path' has uncommitted changes. Commit, push, or remove them before using this context." + } + + git -C $Path merge --ff-only --quiet $remote.DefaultRef + if ($LASTEXITCODE -ne 0) { + throw "Could not fast-forward '$Path' to '$($remote.DefaultRef)'. Resolve its diverged history before using this context." + } + $localHead = (git -C $Path rev-parse HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "git rev-parse HEAD failed for '$Path' (exit $LASTEXITCODE)." + } + if ($localHead -ne $remote.RemoteHead) { + throw "'$Path' is not exactly synchronized with '$($remote.DefaultRef)'. Push or reconcile local commits before using this context." + } + return $remote +} + +function Set-ContextIdentity { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $Path, + + [Parameter(Mandatory)] + [string] $Name, + + [Parameter(Mandatory)] + [string] $Email + ) + + if (-not $PSCmdlet.ShouldProcess($Path, 'Set repository-local git identity')) { + return + } + git -C $Path config user.name $Name + if ($LASTEXITCODE -ne 0) { throw "git config user.name failed for '$Path' (exit $LASTEXITCODE)." } + git -C $Path config user.email $Email + if ($LASTEXITCODE -ne 0) { throw "git config user.email failed for '$Path' (exit $LASTEXITCODE)." } +} + $repositories = foreach ($projectDefinition in $Project) { foreach ($key in @('Name', 'Path', 'DocsUrl', 'MemoryUrl')) { if (-not $projectDefinition.ContainsKey($key) -or $null -eq $projectDefinition[$key]) { @@ -109,12 +226,14 @@ $repositories = foreach ($projectDefinition in $Project) { $memoryPath = if ($projectPath) { Join-Path $projectPath 'memory' } else { 'memory' } [pscustomobject]@{ Name = "$projectName/docs" + Kind = 'docs' RelativePath = $docsPath Url = [string] $projectDefinition.DocsUrl Changes = 'pull requests' } [pscustomobject]@{ Name = "$projectName/memory" + Kind = 'memory' RelativePath = $memoryPath Url = [string] $projectDefinition.MemoryUrl Changes = 'repository policy' @@ -132,92 +251,122 @@ if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { $results = foreach ($repo in $repositories) { $path = Join-Path $Root $repo.RelativePath - if (Test-Path (Join-Path $path '.git')) { - if ($PSCmdlet.ShouldProcess($path, 'Fetch and synchronize default branch')) { - Write-Verbose "Updating $path" - $allBranchesRefspec = '+refs/heads/*:refs/remotes/origin/*' - $fetchRefspecs = @(git -C $path config --get-all remote.origin.fetch) - if ($LASTEXITCODE -notin @(0, 1)) { - throw "git config remote.origin.fetch failed for '$path' (exit $LASTEXITCODE)." + if ($repo.Kind -eq 'memory') { + if (-not (Test-Path (Join-Path $path '.git'))) { + if (Test-Path $path) { + throw "Cannot clone memory into '$path': it exists but is not a git repository." } - if ($allBranchesRefspec -notin $fetchRefspecs) { - git -C $path config --add remote.origin.fetch $allBranchesRefspec + if ($PSCmdlet.ShouldProcess($repo.Url, "Clone memory into '$path'")) { + New-Item -ItemType Directory -Path (Split-Path -Parent $path) -Force | Out-Null + git clone --quiet $repo.Url $path if ($LASTEXITCODE -ne 0) { - throw "Could not configure remote tracking branches for '$path' (exit $LASTEXITCODE)." + throw "git clone failed for $($repo.Url) (exit $LASTEXITCODE). Check access and credentials." } } + } + Sync-ContextCheckout -Path $path -RepositoryUrl $repo.Url -Confirm:$false | Out-Null + Set-ContextIdentity -Path $path -Name $UserName -Email $UserEmail -Confirm:$false + [pscustomobject]@{ + Repository = $repo.Name + Path = $path + BackingPath = $null + Changes = $repo.Changes + } + continue + } - git -C $path fetch origin --prune --quiet - if ($LASTEXITCODE -ne 0) { - throw "git fetch failed for '$path' (exit $LASTEXITCODE). Check network access and credentials for $($repo.Url)." - } - - git -C $path remote set-head origin --auto | Out-Null - if ($LASTEXITCODE -ne 0) { - throw "Cannot detect the remote default branch for '$path'. Check origin before using this context." - } - $defaultRef = (git -C $path symbolic-ref --quiet --short refs/remotes/origin/HEAD | Out-String).Trim() + $expectedBackingPath = "$path.git" + $backingPath = $null + $gitEntry = Join-Path $path '.git' + if (Test-Path $gitEntry -PathType Container) { + # Safe simple-clone migration: synchronize first, preserve all refs in a + # new bare backing repository, and retain the old clone as a backup. + $remote = Sync-ContextCheckout -Path $path -RepositoryUrl $repo.Url -Confirm:$false + if (Test-Path $expectedBackingPath) { + throw "Cannot migrate '$path': backing path '$expectedBackingPath' already exists." + } + $backupPath = "$path.simple-clone-backup" + if (Test-Path $backupPath) { + throw "Cannot migrate '$path': backup path '$backupPath' already exists. Reconcile it first." + } + if ($PSCmdlet.ShouldProcess($path, "Migrate simple clone to '$expectedBackingPath'")) { + git clone --bare --quiet $path $expectedBackingPath if ($LASTEXITCODE -ne 0) { - throw "Cannot resolve the remote default branch for '$path'. Repair origin/HEAD before using this context." + throw "Could not create bare backing repository '$expectedBackingPath' (exit $LASTEXITCODE)." } - $defaultBranch = $defaultRef -replace '^origin/', '' - $currentBranch = (git -C $path branch --show-current | Out-String).Trim() + git --git-dir=$expectedBackingPath remote set-url origin $repo.Url if ($LASTEXITCODE -ne 0) { - throw "git branch --show-current failed for '$path' (exit $LASTEXITCODE)." - } - if ($currentBranch -ne $defaultBranch) { - throw "'$path' is on '$currentBranch', not the default branch '$defaultBranch'. Switch branches before using this context." + throw "Could not set origin on '$expectedBackingPath' (exit $LASTEXITCODE)." } + Sync-ContextRemote -GitPath $expectedBackingPath -RepositoryUrl $repo.Url -Bare -Confirm:$false | Out-Null - $status = @(git -C $path status --porcelain) - if ($LASTEXITCODE -ne 0) { - throw "git status failed for '$path' (exit $LASTEXITCODE)." - } - if ($status.Count -gt 0) { - throw "'$path' has uncommitted changes. Commit, push, or remove them before using this context." - } - - git -C $path merge --ff-only --quiet $defaultRef - if ($LASTEXITCODE -ne 0) { - throw "Could not fast-forward '$path' to '$defaultRef'. Resolve its diverged history before using this context." - } - - $localHead = (git -C $path rev-parse HEAD | Out-String).Trim() - if ($LASTEXITCODE -ne 0) { - throw "git rev-parse HEAD failed for '$path' (exit $LASTEXITCODE)." - } - $remoteHead = (git -C $path rev-parse $defaultRef | Out-String).Trim() - if ($LASTEXITCODE -ne 0) { - throw "git rev-parse '$defaultRef' failed for '$path' (exit $LASTEXITCODE)." - } - if ($localHead -ne $remoteHead) { - throw "'$path' is not exactly synchronized with '$defaultRef'. Push or reconcile local commits before using this context." + Move-Item -LiteralPath $path -Destination $backupPath + try { + git --git-dir=$expectedBackingPath worktree add --quiet $path $remote.DefaultBranch + if ($LASTEXITCODE -ne 0) { + throw "Could not create canonical docs worktree '$path' (exit $LASTEXITCODE)." + } + Sync-ContextCheckout -Path $path -RepositoryUrl $repo.Url -Confirm:$false | Out-Null + } catch { + if (Test-Path $path) { + git --git-dir=$expectedBackingPath worktree remove --force $path 2>$null + Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue + } + if (-not (Test-Path $path) -and (Test-Path $backupPath)) { + Move-Item -LiteralPath $backupPath -Destination $path + } + throw } + Write-Warning "Migrated '$path' to bare+worktree layout. Verify it, then remove retained backup '$backupPath'." } + $backingPath = $expectedBackingPath + } elseif (Test-Path $gitEntry -PathType Leaf) { + $backingPath = (git -C $path rev-parse --path-format=absolute --git-common-dir | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Cannot resolve the backing repository for docs worktree '$path'." + } + $isBare = (git --git-dir=$backingPath rev-parse --is-bare-repository | Out-String).Trim() + if ($LASTEXITCODE -ne 0 -or $isBare -ne 'true') { + throw "Docs worktree '$path' is not backed by a bare repository. Repair it before using context." + } + } elseif (Test-Path $path) { + throw "Cannot install docs at '$path': it exists but is not a supported git checkout." } else { - if (Test-Path $path) { - throw "Cannot clone into '$path': it exists but is not a git repository. Remove it or choose a different -Root." + $backingPath = $expectedBackingPath + if (-not (Test-Path $backingPath)) { + if ($PSCmdlet.ShouldProcess($repo.Url, "Clone bare docs backing into '$backingPath'")) { + New-Item -ItemType Directory -Path (Split-Path -Parent $backingPath) -Force | Out-Null + git clone --bare --quiet $repo.Url $backingPath + if ($LASTEXITCODE -ne 0) { + throw "Bare clone failed for $($repo.Url) (exit $LASTEXITCODE)." + } + } } - if ($PSCmdlet.ShouldProcess($repo.Url, "Clone into '$path'")) { - Write-Verbose "Cloning $($repo.Url) into $path" - New-Item -ItemType Directory -Path (Split-Path -Parent $path) -Force | Out-Null - git clone --quiet $repo.Url $path + $isBare = (git --git-dir=$backingPath rev-parse --is-bare-repository | Out-String).Trim() + if ($LASTEXITCODE -ne 0 -or $isBare -ne 'true') { + throw "Docs backing path '$backingPath' is not a bare repository." + } + $remote = Sync-ContextRemote -GitPath $backingPath -RepositoryUrl $repo.Url -Bare -Confirm:$false + $localDefault = (git --git-dir=$backingPath rev-parse "refs/heads/$($remote.DefaultBranch)" | Out-String).Trim() + if ($LASTEXITCODE -ne 0 -or $localDefault -ne $remote.RemoteHead) { + throw "Bare docs branch '$($remote.DefaultBranch)' is not exactly synchronized. Repair '$backingPath' before creating its main worktree." + } + if ($PSCmdlet.ShouldProcess($path, 'Create canonical docs worktree')) { + git --git-dir=$backingPath worktree add --quiet $path $remote.DefaultBranch if ($LASTEXITCODE -ne 0) { - throw "git clone failed for $($repo.Url) (exit $LASTEXITCODE). Check access and credentials (MSXOrg/memory is private)." + throw "Could not create canonical docs worktree '$path' (exit $LASTEXITCODE)." } } } - # Isolated identity: write repository-local config only. Git still reads - # global and system config; the script never writes to them. - if ($PSCmdlet.ShouldProcess($path, 'Set repository-local git identity')) { - git -C $path config user.name $UserName - if ($LASTEXITCODE -ne 0) { throw "git config user.name failed for '$path' (exit $LASTEXITCODE)." } - git -C $path config user.email $UserEmail - if ($LASTEXITCODE -ne 0) { throw "git config user.email failed for '$path' (exit $LASTEXITCODE)." } + Sync-ContextCheckout -Path $path -RepositoryUrl $repo.Url -Confirm:$false | Out-Null + Set-ContextIdentity -Path $path -Name $UserName -Email $UserEmail -Confirm:$false + [pscustomobject]@{ + Repository = $repo.Name + Path = $path + BackingPath = $backingPath + Changes = $repo.Changes } - - [pscustomobject]@{ Repository = $repo.Name; Path = $path; Changes = $repo.Changes } } $results diff --git a/bootstrap/README.md b/bootstrap/README.md index 85df4d4..fd0e31d 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -10,8 +10,9 @@ The single starting point for agents: a git-isolated local clone of the MSX cent ## The model - `~/.msx/docs` is **read context** — the ways of working, coding standards, and agent workflow. Changes to it go through **pull requests**. +- `~/.msx/docs.git` is the bare backing repository for the readable, clean `~/.msx/docs` main worktree. - `~/.msx/memory` is **durable context** — notes and session history governed by that repository's contribution policy. -- `~/.msx/projects//{docs,memory}` holds optional project-specific context added through bootstrap plug-ins. +- `~/.msx/projects//docs.git` and `docs/` provide the same bare+main-worktree model for optional project docs; `memory/` remains a simple checkout. > **Prerequisite:** `MSXOrg/memory` is a private repository — the bootstrap needs access to it (and working github.com credentials) to clone or update memory. @@ -27,14 +28,28 @@ Run the bootstrap: ```powershell $docs = Join-Path $HOME '.msx/docs' +$docsBacking = "$docs.git" if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { throw "$docs exists but is not a git repository. Remove it and re-run." } if (-not (Test-Path (Join-Path $docs '.git'))) { - New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null - git clone https://github.com/MSXOrg/docs.git $docs + if (-not (Test-Path $docsBacking)) { + New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null + git clone --bare https://github.com/MSXOrg/docs.git $docsBacking + if ($LASTEXITCODE -ne 0) { + throw "Bare clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and credentials." + } + git --git-dir=$docsBacking config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' + git --git-dir=$docsBacking fetch origin --prune --quiet + } + if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { + throw "$docsBacking exists but is not a bare repository." + } + $defaultBranch = git --git-dir=$docsBacking symbolic-ref HEAD | + ForEach-Object { $_ -replace 'refs/heads/', '' } + git --git-dir=$docsBacking worktree add $docs $defaultBranch if ($LASTEXITCODE -ne 0) { - throw "git clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and github.com credentials, then re-run." + throw "Could not create the canonical MSXOrg/docs worktree at $docs." } } else { $refspec = '+refs/heads/*:refs/remotes/origin/*' @@ -93,6 +108,8 @@ $projects = @( Each plug-in uses the same fail-closed freshness validation. `Path` is relative to `~/.msx`, so projects can choose a collision-free location without forking the bootstrap. +Existing clean simple docs clones are migrated automatically. The original clone is retained beside the new layout as `docs.simple-clone-backup` for manual verification and removal. Existing docs worktrees backed by another bare path are reused in place. Dirty, ahead, diverged, wrong-branch, conflicting-path, or otherwise unsafe layouts stop with actionable guidance before conversion. + Wire it into the tools so it runs as the first instruction: - **Claude Code** reads `CLAUDE.md`. Add an import to `~/.claude/CLAUDE.md`: diff --git a/src/docs/Frameworks/Agentic-Development/design.md b/src/docs/Frameworks/Agentic-Development/design.md index 282f436..25e8568 100644 --- a/src/docs/Frameworks/Agentic-Development/design.md +++ b/src/docs/Frameworks/Agentic-Development/design.md @@ -209,15 +209,14 @@ A local bootstrap makes central context predictable: ```text ~/.msx/ - AI-Platform/ - docs/ - memory/ - MSXOrg/ - docs/ - memory/ - PSModule/ - docs/ - memory/ + docs.git/ # MSXOrg/docs bare backing repository + docs/ # clean MSXOrg/docs main worktree + memory/ # simple MSXOrg/memory checkout + projects/ + PSModule/ + docs.git/ # optional project docs backing repository + docs/ # optional project docs main worktree + memory/ # optional project memory checkout ``` The bootstrap clones missing repositories and fetches every existing context repository before use. Each clone must be clean, checked out on the remote default branch, and exactly equal to the fetched remote head. A dirty, locally ahead, diverged, wrong-branch, or unreachable clone stops context resolution; the agent does not use a possibly stale local copy. Bootstrap writes repository-local git configuration only. diff --git a/src/docs/Ways-of-Working/Git-Worktrees.md b/src/docs/Ways-of-Working/Git-Worktrees.md index e71e6d9..a1a51bf 100644 --- a/src/docs/Ways-of-Working/Git-Worktrees.md +++ b/src/docs/Ways-of-Working/Git-Worktrees.md @@ -14,7 +14,7 @@ All repositories are set up as **bare clones with worktrees**. Each repository-d The reason this layout is the default — not the occasional convenience it is in most projects — is **parallelism**. [Agentic development](Agentic-Development.md) does not proceed one delivery leaf at a time. A single developer can have several agents working at once, each on a different Task or Bug, alongside their own hands-on changes. Worktrees are what make running many streams at once safe instead of chaotic: - **One worktree per repository-delivery Task or Bug, one agent per worktree.** Each agent gets its own working directory, its own branch, and its own uncommitted state. Two agents never write to the same checkout, so their edits cannot corrupt one another. -- **No stashing, no branch-switching, no waiting.** Because the worktrees are independent, the agent finishing Task #42 never disturbs the one still working on Bug #99 — and neither touches the clean `/` reference you read from. Nobody has to reach a clean tree before anyone else can move. +- **No stashing, no branch-switching, no waiting.** Because the worktrees are independent, the agent finishing Task #42 never disturbs the one still working on Bug #99 — and neither touches the clean canonical `/` worktree you read from. Nobody has to reach a clean tree before anyone else can move. - **Fan out, then integrate.** A batch of independent delivery leaves can be started together — one worktree each — worked concurrently, and merged back one at a time through the normal [branch-and-PR flow](Branching-and-Merging.md) as each finishes. In a single ordinary clone the opposite is forced: one branch checked out at a time, every human and agent contending for the same files, constant stashing and switching. Worktrees turn "several issues at once" from a hazard into the default working mode — which is what makes local agentic development practical at all. @@ -26,24 +26,24 @@ In a single ordinary clone the opposite is forced: one branch checked out at a t | Only one branch checked out at a time | Each delivery leaf gets a worktree — parallel by default | | Switching branches requires clean state | Worktrees are independent — no stashing or committing WIP | | Agent work blocks human work on same repo | Different worktrees, no interference | -| Default branch gets dirty during development | `/` worktree is always a clean reference | +| Default branch gets dirty during development | Canonical `/` worktree is always a clean reference | ## Repository layout ```text -/ -├── .bare/ # bare git data (the actual repository) -├── .git # file containing: gitdir: ./.bare -├── / # worktree: default branch (always clean, never worked in directly) +/ +├── .git/ # bare backing repository +├── / # canonical default-branch worktree (always clean) ├── 42-add-pagination/ # worktree folder; branch: feat/42-add-pagination └── 99-null-ref/ # worktree folder; branch: fix/99-null-ref ``` -- **`.bare/`** — the shared git object store. All worktrees share this. -- **`.git`** — a file (not a directory) that points git tooling to `.bare/`. -- **`/`** — the default branch worktree (e.g. `main` or `master`). Kept as a clean reference. Used for diffing, reading docs, running comparisons. Never directly committed to. +- **`.git/`** — the shared bare object store. All worktrees use this backing repository. +- **`/`** — the canonical default-branch worktree. Kept clean and exactly synchronized for reading, diffing, and comparisons. Never directly committed to. - **`-/`** — one worktree folder per repository-delivery Task or Bug in flight, named by issue number and a short slug. The folder is a concise local path; its branch uses the required `/-` name, so the two names do not need to match. +For the central MSX context, this becomes `~/.msx/docs.git` plus the readable `~/.msx/docs` main worktree. Memory remains a simple checkout at `~/.msx/memory`. + ## Remotes Every repository has exactly two remotes (or one, if it is not a fork): @@ -75,27 +75,26 @@ Both remotes are configured with full refspecs so `git fetch --all --prune` keep ## Setup (one-time per repository) ```powershell -# Clone as bare into .bare/ -git clone --bare https://github.com//.git .bare - -# Create the .git pointer file -Set-Content .git "gitdir: ./.bare" -NoNewline +# From the workspace root, clone the bare backing repository. +$repo = '' +git clone --bare "https://github.com//$repo.git" "$repo.git" # Configure fetch refspec (bare clones don't set this automatically) -git -C .bare config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' +git --git-dir="$repo.git" config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' # Fetch remote branches -git -C .bare fetch origin +git --git-dir="$repo.git" fetch origin # Determine the default branch -$defaultBranch = git -C .bare symbolic-ref HEAD | ForEach-Object { $_ -replace 'refs/heads/', '' } +$defaultBranch = git --git-dir="$repo.git" symbolic-ref HEAD | + ForEach-Object { $_ -replace 'refs/heads/', '' } -# Create the default branch worktree -git -C .bare worktree add "../$defaultBranch" $defaultBranch +# Create the canonical default-branch worktree at the repository name. +git --git-dir="$repo.git" worktree add $repo $defaultBranch # Set upstream tracking (prevents "Publish Branch" prompt in VS Code) -git -C .bare config "branch.$defaultBranch.remote" origin -git -C .bare config "branch.$defaultBranch.merge" "refs/heads/$defaultBranch" +git --git-dir="$repo.git" config "branch.$defaultBranch.remote" origin +git --git-dir="$repo.git" config "branch.$defaultBranch.merge" "refs/heads/$defaultBranch" ``` > The [Checkout-GitHubRepo](https://github.com/MariusStorhaug/.dev/blob/main/.github/Checkout-GitHubRepo.ps1) script automates this for all repositories. @@ -103,15 +102,17 @@ git -C .bare config "branch.$defaultBranch.merge" "refs/heads/$defaultBranch" ## Working on a delivery leaf ```powershell -# From the repo root (where .bare/ lives) -$defaultBranch = git -C .bare symbolic-ref HEAD | ForEach-Object { $_ -replace 'refs/heads/', '' } +# From the workspace root (where .git lives) +$repo = '' +$defaultBranch = git --git-dir="$repo.git" symbolic-ref HEAD | + ForEach-Object { $_ -replace 'refs/heads/', '' } $worktreeName = '42-add-pagination' $branchName = 'feat/42-add-pagination' -git -C .bare worktree add "../$worktreeName" -b $branchName $defaultBranch +git --git-dir="$repo.git" worktree add $worktreeName -b $branchName $defaultBranch # Set upstream tracking (prevents "Publish Branch" prompt in VS Code) -git -C .bare config "branch.$branchName.remote" origin -git -C .bare config "branch.$branchName.merge" "refs/heads/$branchName" +git --git-dir="$repo.git" config "branch.$branchName.remote" origin +git --git-dir="$repo.git" config "branch.$branchName.merge" "refs/heads/$branchName" # Open in VS Code code $worktreeName @@ -123,13 +124,13 @@ Then follow the normal Implement flow: initial commit → push → draft PR → ```powershell # Remove the worktree -git -C .bare worktree remove 42-add-pagination +git --git-dir=".git" worktree remove 42-add-pagination # Delete the local branch ref -git -C .bare branch -D feat/42-add-pagination +git --git-dir=".git" branch -D feat/42-add-pagination # Prune if needed (removes stale worktree references) -git -C .bare worktree prune +git --git-dir=".git" worktree prune ``` ## Where this connects diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 4319ac9..434c05a 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -87,10 +87,24 @@ Describe 'Initialize-MsxWorkspace context freshness' { function Invoke-BootstrapFixture { param([Parameter(Mandatory)] $Fixture) - $output = & $script:pwsh -NoProfile -File $script:bootstrap ` - -Root $Fixture.Workspace ` - -UserName 'Fixture User' ` - -UserEmail 'fixture@example.invalid' 2>&1 | Out-String + $runner = Join-Path $Fixture.Root 'invoke-bootstrap.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $Fixture.Workspace.Replace("'", "''") + $docsRemote = $Fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $Fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Fixture' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String return [pscustomobject]@{ ExitCode = $LASTEXITCODE @@ -120,11 +134,18 @@ Describe 'Initialize-MsxWorkspace context freshness' { $result = Invoke-BootstrapFixture -Fixture $fixture - $result.ExitCode | Should -Be 0 + $result.ExitCode | Should -Be 0 -Because $result.Output (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $docsHead (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $docsHead (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $memoryHead (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'origin/main')).Trim() | Should -BeExactly $memoryHead + Test-Path -LiteralPath (Join-Path $fixture.Docs '.git') -PathType Leaf | Should -BeTrue + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') -PathType Container | Should -BeTrue + (Invoke-Git -Arguments @("--git-dir=$(Join-Path $fixture.Workspace 'docs.git')", 'rev-parse', '--is-bare-repository')).Trim() | + Should -BeExactly 'true' + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.simple-clone-backup') | Should -BeTrue + Test-Path -LiteralPath (Join-Path $fixture.Memory '.git') -PathType Container | Should -BeTrue + (Invoke-BootstrapFixture -Fixture $fixture).ExitCode | Should -Be 0 } It 'rejects a dirty checkout without updating it' { @@ -216,6 +237,12 @@ exit `$LASTEXITCODE $projectMemory = Join-Path $fixture.Workspace 'projects/Project/memory' Test-Path -LiteralPath (Join-Path $projectDocs '.git') | Should -BeTrue Test-Path -LiteralPath (Join-Path $projectMemory '.git') | Should -BeTrue + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'projects/Project/docs.git') | Should -BeTrue + (Invoke-Git -Arguments @( + "--git-dir=$(Join-Path $fixture.Workspace 'projects/Project/docs.git')", + 'rev-parse', + '--is-bare-repository' + )).Trim() | Should -BeExactly 'true' (Invoke-Git -WorkingDirectory $projectDocs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() (Invoke-Git -WorkingDirectory $projectMemory -Arguments @('rev-parse', 'HEAD')).Trim() | @@ -252,4 +279,30 @@ exit `$LASTEXITCODE $LASTEXITCODE | Should -Not -Be 0 $output | Should -Match 'duplicate workspace paths' } + + It 'reuses a canonical docs worktree with a legacy bare backing path' { + Remove-Item -LiteralPath $fixture.Docs -Recurse -Force + $legacyBacking = Join-Path $fixture.Root 'legacy-docs-backing.git' + Invoke-Git -Arguments @('clone', '--bare', '--quiet', $fixture.Remotes.docs, $legacyBacking) | Out-Null + Invoke-Git -Arguments @( + "--git-dir=$legacyBacking", + 'config', + '--add', + 'remote.origin.fetch', + '+refs/heads/*:refs/remotes/origin/*' + ) | Out-Null + Invoke-Git -Arguments @("--git-dir=$legacyBacking", 'fetch', '--quiet', 'origin') | Out-Null + Invoke-Git -Arguments @("--git-dir=$legacyBacking", 'worktree', 'add', '--quiet', $fixture.Docs, 'main') | Out-Null + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Be 0 -Because $result.Output + $commonDir = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @( + 'rev-parse', + '--path-format=absolute', + '--git-common-dir' + )).Trim() + [IO.Path]::GetFullPath($commonDir) | Should -BeExactly ([IO.Path]::GetFullPath($legacyBacking)) + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + } } From 973eea0967530c0a44aa05dd0a4e7ea476d6bbc6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 12:50:40 +0200 Subject: [PATCH 25/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Har?= =?UTF-8?q?den=20docs=20topology=20bootstrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/AGENTS.template.md | 50 ++++- bootstrap/Initialize-MsxWorkspace.ps1 | 101 ++++++++-- bootstrap/README.md | 54 +++++- tests/Initialize-MsxWorkspace.Tests.ps1 | 238 +++++++++++++++++++++++- 4 files changed, 412 insertions(+), 31 deletions(-) diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 28f20e2..8939462 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -11,7 +11,10 @@ Everything is a work in progress and can be updated and improved. Fix a small pr The workspace is a git-isolated clone of the central repositories under `~/.msx`. Set it up before reading context. Existing context repositories must be clean, on their default branch, and exactly synchronized with the remote: ```powershell -$docs = Join-Path $HOME '.msx/docs' +$workspaceRoot = if ($env:MSX_WORKSPACE_ROOT) { $env:MSX_WORKSPACE_ROOT } else { Join-Path $HOME '.msx' } +$docsUrl = if ($env:MSX_DOCS_URL) { $env:MSX_DOCS_URL } else { 'https://github.com/MSXOrg/docs.git' } +$memoryUrl = if ($env:MSX_MEMORY_URL) { $env:MSX_MEMORY_URL } else { 'https://github.com/MSXOrg/memory.git' } +$docs = Join-Path $workspaceRoot 'docs' $docsBacking = "$docs.git" if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { throw "$docs exists but is not a git repository. Remove it and re-run." @@ -19,18 +22,45 @@ if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { if (-not (Test-Path (Join-Path $docs '.git'))) { if (-not (Test-Path $docsBacking)) { New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null - git clone --bare https://github.com/MSXOrg/docs.git $docsBacking + git clone --bare $docsUrl $docsBacking if ($LASTEXITCODE -ne 0) { throw "Bare clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and credentials." } - git --git-dir=$docsBacking config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' - git --git-dir=$docsBacking fetch origin --prune --quiet } if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { throw "$docsBacking exists but is not a bare repository." } - $defaultBranch = git --git-dir=$docsBacking symbolic-ref HEAD | - ForEach-Object { $_ -replace 'refs/heads/', '' } + $refspec = '+refs/heads/*:refs/remotes/origin/*' + if ($refspec -notin @(git --git-dir=$docsBacking config --get-all remote.origin.fetch)) { + git --git-dir=$docsBacking config --add remote.origin.fetch $refspec + if ($LASTEXITCODE -ne 0) { throw "Could not configure $docsBacking." } + } + git --git-dir=$docsBacking fetch origin --prune --quiet + if ($LASTEXITCODE -ne 0) { throw "Could not refresh $docsBacking. Do not use stale context." } + git --git-dir=$docsBacking remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not detect the MSXOrg/docs default branch." } + $defaultRef = (git --git-dir=$docsBacking symbolic-ref --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve origin/HEAD in $docsBacking." } + $defaultBranch = $defaultRef -replace '^origin/', '' + $remoteHead = (git --git-dir=$docsBacking rev-parse $defaultRef | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve $defaultRef in $docsBacking." } + $localRef = "refs/heads/$defaultBranch" + $localHead = (git --git-dir=$docsBacking rev-parse --verify $localRef 2>$null | Out-String).Trim() + if ($LASTEXITCODE -eq 128) { + git --git-dir=$docsBacking update-ref $localRef $remoteHead + } elseif ($LASTEXITCODE -ne 0) { + throw "Could not inspect $localRef in $docsBacking." + } elseif ($localHead -ne $remoteHead) { + git --git-dir=$docsBacking merge-base --is-ancestor $localHead $remoteHead + if ($LASTEXITCODE -ne 0) { throw "$localRef is ahead or diverged in $docsBacking." } + if ("branch $localRef" -in @(git --git-dir=$docsBacking worktree list --porcelain)) { + throw "$localRef is checked out elsewhere. Update that worktree first." + } + git --git-dir=$docsBacking update-ref $localRef $remoteHead $localHead + } + if ($LASTEXITCODE -ne 0 -or (git --git-dir=$docsBacking rev-parse $localRef) -ne $remoteHead) { + throw "$localRef is not exactly synchronized with $defaultRef." + } git --git-dir=$docsBacking worktree add $docs $defaultBranch if ($LASTEXITCODE -ne 0) { throw "Could not create the canonical MSXOrg/docs worktree at $docs." @@ -66,8 +96,8 @@ $projects = @( @{ Name = 'MSXOrg' Path = '' - DocsUrl = 'https://github.com/MSXOrg/docs.git' - MemoryUrl = 'https://github.com/MSXOrg/memory.git' + DocsUrl = $docsUrl + MemoryUrl = $memoryUrl } # Add project-specific entries when this template is adopted there: # @{ @@ -77,7 +107,7 @@ $projects = @( # MemoryUrl = 'https://github.com/PSModule/memory.git' # } ) -& (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') -Project $projects +& (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') -Root $workspaceRoot -Project $projects if ($LASTEXITCODE -ne 0) { throw "Context synchronization failed. Do not read context until every project is current." } @@ -114,5 +144,5 @@ Clear task language may shortcut the index trail: `Review this PR ` enters ## Two write rules -- **Docs change through pull requests.** Branch inside `~/.msx/docs` and open a pull request; never push its `main`. +- **Docs change through topic worktrees and pull requests.** Create a topic worktree from `~/.msx/docs.git`; never branch or work inside the canonical `~/.msx/docs` main worktree. - **Memory follows repository policy.** Read the selected memory repository's `AGENTS.md` and `CONTRIBUTING.md` before writing. diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 33fe035..2fa33d3 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -182,6 +182,53 @@ function Sync-ContextCheckout { return $remote } +function Sync-BareDefaultBranch { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $BackingPath, + + [Parameter(Mandatory)] + [pscustomobject] $Remote + ) + + $localRef = "refs/heads/$($Remote.DefaultBranch)" + $localHead = (git --git-dir=$BackingPath rev-parse --verify $localRef 2>$null | Out-String).Trim() + if ($LASTEXITCODE -eq 128) { + if ($PSCmdlet.ShouldProcess($localRef, "Create at $($Remote.RemoteHead)")) { + git --git-dir=$BackingPath update-ref $localRef $Remote.RemoteHead + if ($LASTEXITCODE -ne 0) { + throw "Could not create bare docs branch '$localRef' in '$BackingPath'." + } + } + return + } + if ($LASTEXITCODE -ne 0) { + throw "Could not inspect bare docs branch '$localRef' in '$BackingPath'." + } + if ($localHead -eq $Remote.RemoteHead) { + return + } + + git --git-dir=$BackingPath merge-base --is-ancestor $localHead $Remote.RemoteHead + if ($LASTEXITCODE -ne 0) { + throw "Bare docs branch '$localRef' is ahead or diverged. Reconcile '$BackingPath' before creating its canonical worktree." + } + $worktreeState = @(git --git-dir=$BackingPath worktree list --porcelain) + if ($LASTEXITCODE -ne 0) { + throw "Could not inspect worktrees for '$BackingPath'." + } + if ("branch $localRef" -in $worktreeState) { + throw "Bare docs branch '$localRef' is checked out in another worktree. Update that worktree before creating the canonical one." + } + if ($PSCmdlet.ShouldProcess($localRef, "Fast-forward to $($Remote.RemoteHead)")) { + git --git-dir=$BackingPath update-ref $localRef $Remote.RemoteHead $localHead + if ($LASTEXITCODE -ne 0) { + throw "Could not fast-forward bare docs branch '$localRef' in '$BackingPath'." + } + } +} + function Set-ContextIdentity { [CmdletBinding(SupportsShouldProcess)] param( @@ -240,9 +287,25 @@ $repositories = foreach ($projectDefinition in $Project) { } } -$duplicatePaths = $repositories | Group-Object RelativePath | Where-Object Count -gt 1 -if ($duplicatePaths) { - throw "Project definitions contain duplicate workspace paths: $($duplicatePaths.Name -join ', ')." +$occupiedPaths = foreach ($repository in $repositories) { + [pscustomobject]@{ Repository = $repository.Name; Path = $repository.RelativePath } + if ($repository.Kind -eq 'docs') { + [pscustomobject]@{ Repository = "$($repository.Name) backing"; Path = "$($repository.RelativePath).git" } + } +} +for ($left = 0; $left -lt $occupiedPaths.Count; $left++) { + $leftPath = ($occupiedPaths[$left].Path -replace '\\', '/').Trim('/').ToLowerInvariant() + for ($right = $left + 1; $right -lt $occupiedPaths.Count; $right++) { + $rightPath = ($occupiedPaths[$right].Path -replace '\\', '/').Trim('/').ToLowerInvariant() + $collision = ( + $leftPath -eq $rightPath -or + $leftPath.StartsWith("$rightPath/", [StringComparison]::Ordinal) -or + $rightPath.StartsWith("$leftPath/", [StringComparison]::Ordinal) + ) + if ($collision) { + throw "Project workspace paths overlap: '$($occupiedPaths[$left].Path)' and '$($occupiedPaths[$right].Path)'." + } + } } if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { @@ -290,15 +353,30 @@ $results = foreach ($repo in $repositories) { throw "Cannot migrate '$path': backup path '$backupPath' already exists. Reconcile it first." } if ($PSCmdlet.ShouldProcess($path, "Migrate simple clone to '$expectedBackingPath'")) { - git clone --bare --quiet $path $expectedBackingPath + $sourceRefs = @(git -C $path for-each-ref '--format=%(refname) %(objectname)' refs/heads refs/tags) if ($LASTEXITCODE -ne 0) { - throw "Could not create bare backing repository '$expectedBackingPath' (exit $LASTEXITCODE)." + throw "Could not inventory branches and tags in '$path' before migration." } - git --git-dir=$expectedBackingPath remote set-url origin $repo.Url - if ($LASTEXITCODE -ne 0) { - throw "Could not set origin on '$expectedBackingPath' (exit $LASTEXITCODE)." + try { + git clone --bare --quiet $path $expectedBackingPath + if ($LASTEXITCODE -ne 0) { + throw "Could not create bare backing repository '$expectedBackingPath' (exit $LASTEXITCODE)." + } + git --git-dir=$expectedBackingPath remote set-url origin $repo.Url + if ($LASTEXITCODE -ne 0) { + throw "Could not set origin on '$expectedBackingPath' (exit $LASTEXITCODE)." + } + Sync-ContextRemote -GitPath $expectedBackingPath -RepositoryUrl $repo.Url -Bare -Confirm:$false | Out-Null + $backingRefs = @(git --git-dir=$expectedBackingPath for-each-ref '--format=%(refname) %(objectname)' refs/heads refs/tags) + if ($LASTEXITCODE -ne 0 -or (Compare-Object $sourceRefs $backingRefs)) { + throw "Bare backing repository '$expectedBackingPath' did not preserve every local branch and tag." + } + } catch { + if (Test-Path $expectedBackingPath) { + Remove-Item -LiteralPath $expectedBackingPath -Recurse -Force + } + throw "Migration preparation failed for '$path'; the original clone is unchanged. $($_.Exception.Message)" } - Sync-ContextRemote -GitPath $expectedBackingPath -RepositoryUrl $repo.Url -Bare -Confirm:$false | Out-Null Move-Item -LiteralPath $path -Destination $backupPath try { @@ -347,10 +425,7 @@ $results = foreach ($repo in $repositories) { throw "Docs backing path '$backingPath' is not a bare repository." } $remote = Sync-ContextRemote -GitPath $backingPath -RepositoryUrl $repo.Url -Bare -Confirm:$false - $localDefault = (git --git-dir=$backingPath rev-parse "refs/heads/$($remote.DefaultBranch)" | Out-String).Trim() - if ($LASTEXITCODE -ne 0 -or $localDefault -ne $remote.RemoteHead) { - throw "Bare docs branch '$($remote.DefaultBranch)' is not exactly synchronized. Repair '$backingPath' before creating its main worktree." - } + Sync-BareDefaultBranch -BackingPath $backingPath -Remote $remote -Confirm:$false if ($PSCmdlet.ShouldProcess($path, 'Create canonical docs worktree')) { git --git-dir=$backingPath worktree add --quiet $path $remote.DefaultBranch if ($LASTEXITCODE -ne 0) { diff --git a/bootstrap/README.md b/bootstrap/README.md index fd0e31d..2b9c06e 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -27,7 +27,10 @@ The loaded `AGENTS.md` points to the roots; discovery happens in documentation. Run the bootstrap: ```powershell -$docs = Join-Path $HOME '.msx/docs' +$workspaceRoot = if ($env:MSX_WORKSPACE_ROOT) { $env:MSX_WORKSPACE_ROOT } else { Join-Path $HOME '.msx' } +$docsUrl = if ($env:MSX_DOCS_URL) { $env:MSX_DOCS_URL } else { 'https://github.com/MSXOrg/docs.git' } +$memoryUrl = if ($env:MSX_MEMORY_URL) { $env:MSX_MEMORY_URL } else { 'https://github.com/MSXOrg/memory.git' } +$docs = Join-Path $workspaceRoot 'docs' $docsBacking = "$docs.git" if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { throw "$docs exists but is not a git repository. Remove it and re-run." @@ -35,18 +38,45 @@ if ((Test-Path $docs) -and -not (Test-Path (Join-Path $docs '.git'))) { if (-not (Test-Path (Join-Path $docs '.git'))) { if (-not (Test-Path $docsBacking)) { New-Item -ItemType Directory -Force -Path (Split-Path -Parent $docs) | Out-Null - git clone --bare https://github.com/MSXOrg/docs.git $docsBacking + git clone --bare $docsUrl $docsBacking if ($LASTEXITCODE -ne 0) { throw "Bare clone of MSXOrg/docs failed (exit $LASTEXITCODE). Check network access and credentials." } - git --git-dir=$docsBacking config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' - git --git-dir=$docsBacking fetch origin --prune --quiet } if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { throw "$docsBacking exists but is not a bare repository." } - $defaultBranch = git --git-dir=$docsBacking symbolic-ref HEAD | - ForEach-Object { $_ -replace 'refs/heads/', '' } + $refspec = '+refs/heads/*:refs/remotes/origin/*' + if ($refspec -notin @(git --git-dir=$docsBacking config --get-all remote.origin.fetch)) { + git --git-dir=$docsBacking config --add remote.origin.fetch $refspec + if ($LASTEXITCODE -ne 0) { throw "Could not configure $docsBacking." } + } + git --git-dir=$docsBacking fetch origin --prune --quiet + if ($LASTEXITCODE -ne 0) { throw "Could not refresh $docsBacking. Do not use stale context." } + git --git-dir=$docsBacking remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not detect the MSXOrg/docs default branch." } + $defaultRef = (git --git-dir=$docsBacking symbolic-ref --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve origin/HEAD in $docsBacking." } + $defaultBranch = $defaultRef -replace '^origin/', '' + $remoteHead = (git --git-dir=$docsBacking rev-parse $defaultRef | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve $defaultRef in $docsBacking." } + $localRef = "refs/heads/$defaultBranch" + $localHead = (git --git-dir=$docsBacking rev-parse --verify $localRef 2>$null | Out-String).Trim() + if ($LASTEXITCODE -eq 128) { + git --git-dir=$docsBacking update-ref $localRef $remoteHead + } elseif ($LASTEXITCODE -ne 0) { + throw "Could not inspect $localRef in $docsBacking." + } elseif ($localHead -ne $remoteHead) { + git --git-dir=$docsBacking merge-base --is-ancestor $localHead $remoteHead + if ($LASTEXITCODE -ne 0) { throw "$localRef is ahead or diverged in $docsBacking." } + if ("branch $localRef" -in @(git --git-dir=$docsBacking worktree list --porcelain)) { + throw "$localRef is checked out elsewhere. Update that worktree first." + } + git --git-dir=$docsBacking update-ref $localRef $remoteHead $localHead + } + if ($LASTEXITCODE -ne 0 -or (git --git-dir=$docsBacking rev-parse $localRef) -ne $remoteHead) { + throw "$localRef is not exactly synchronized with $defaultRef." + } git --git-dir=$docsBacking worktree add $docs $defaultBranch if ($LASTEXITCODE -ne 0) { throw "Could not create the canonical MSXOrg/docs worktree at $docs." @@ -78,7 +108,15 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." } } -pwsh (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') +$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = $docsUrl + MemoryUrl = $memoryUrl + } +) +& (Join-Path $docs 'bootstrap/Initialize-MsxWorkspace.ps1') -Root $workspaceRoot -Project $projects if ($LASTEXITCODE -ne 0) { throw "MSX workspace synchronization failed. Do not read context until every repository is current." } @@ -110,6 +148,8 @@ Each plug-in uses the same fail-closed freshness validation. `Path` is relative Existing clean simple docs clones are migrated automatically. The original clone is retained beside the new layout as `docs.simple-clone-backup` for manual verification and removal. Existing docs worktrees backed by another bare path are reused in place. Dirty, ahead, diverged, wrong-branch, conflicting-path, or otherwise unsafe layouts stop with actionable guidance before conversion. +Docs changes use topic worktrees created from `~/.msx/docs.git`; never branch or work inside the canonical `~/.msx/docs` main worktree. + Wire it into the tools so it runs as the first instruction: - **Claude Code** reads `CLAUDE.md`. Add an import to `~/.claude/CLAUDE.md`: diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 434c05a..9691899 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -3,6 +3,8 @@ Describe 'Initialize-MsxWorkspace context freshness' { BeforeAll { $script:bootstrap = Join-Path $PSScriptRoot '../bootstrap/Initialize-MsxWorkspace.ps1' + $script:agentTemplate = Join-Path $PSScriptRoot '../bootstrap/AGENTS.template.md' + $script:bootstrapReadme = Join-Path $PSScriptRoot '../bootstrap/README.md' $script:pwsh = (Get-Process -Id $PID).Path function Invoke-Git { @@ -64,7 +66,15 @@ Describe 'Initialize-MsxWorkspace context freshness' { Invoke-Git -WorkingDirectory $writer -Arguments @('config', 'user.name', 'Fixture Writer') | Out-Null Invoke-Git -WorkingDirectory $writer -Arguments @('config', 'user.email', 'fixture@example.invalid') | Out-Null Set-Content -LiteralPath (Join-Path $writer 'context.txt') -Value "$name context" + if ($name -eq 'docs') { + $bootstrapDirectory = Join-Path $writer 'bootstrap' + New-Item -ItemType Directory -Path $bootstrapDirectory | Out-Null + Copy-Item -LiteralPath $script:bootstrap -Destination $bootstrapDirectory + } Invoke-Git -WorkingDirectory $writer -Arguments @('add', 'context.txt') | Out-Null + if ($name -eq 'docs') { + Invoke-Git -WorkingDirectory $writer -Arguments @('add', 'bootstrap/Initialize-MsxWorkspace.ps1') | Out-Null + } Invoke-Git -WorkingDirectory $writer -Arguments @('commit', '--quiet', '-m', "Initialize $name") | Out-Null Invoke-Git -WorkingDirectory $writer -Arguments @('push', '--quiet', '--set-upstream', 'origin', 'main') | Out-Null Invoke-Git -Arguments @('clone', '--quiet', $remote, $checkout) | Out-Null @@ -111,6 +121,41 @@ exit `$LASTEXITCODE Output = $output } } + + function Invoke-BootstrapSeed { + param( + [Parameter(Mandatory)] + $Fixture, + + [Parameter(Mandatory)] + [string] $MarkdownPath, + + [Parameter(Mandatory)] + [string] $Workspace + ) + + $markdown = Get-Content -LiteralPath $MarkdownPath -Raw + $match = [regex]::Match($markdown, '(?s)```powershell\r?\n(.*?)\r?\n```') + if (-not $match.Success) { + throw "No PowerShell seed block found in '$MarkdownPath'." + } + $runner = Join-Path $Fixture.Root "seed-$([IO.Path]::GetFileNameWithoutExtension($MarkdownPath)).ps1" + Set-Content -LiteralPath $runner -Value $match.Groups[1].Value + $previousRoot = $env:MSX_WORKSPACE_ROOT + $previousDocs = $env:MSX_DOCS_URL + $previousMemory = $env:MSX_MEMORY_URL + try { + $env:MSX_WORKSPACE_ROOT = $Workspace + $env:MSX_DOCS_URL = $Fixture.Remotes.docs + $env:MSX_MEMORY_URL = $Fixture.Remotes.memory + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $output } + } finally { + $env:MSX_WORKSPACE_ROOT = $previousRoot + $env:MSX_DOCS_URL = $previousDocs + $env:MSX_MEMORY_URL = $previousMemory + } + } } BeforeEach { @@ -125,6 +170,10 @@ exit `$LASTEXITCODE } It 'fast-forwards a clean behind checkout to the exact remote head' { + Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('branch', 'local-topic') | Out-Null + Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('tag', 'local-tag') | Out-Null + $topicHead = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'local-topic')).Trim() + $tagHead = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'local-tag')).Trim() Add-TestCommit -Repository $fixture.Writers.docs -Name 'Advance docs' Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null Add-TestCommit -Repository $fixture.Writers.memory -Name 'Advance memory' @@ -143,6 +192,10 @@ exit `$LASTEXITCODE Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') -PathType Container | Should -BeTrue (Invoke-Git -Arguments @("--git-dir=$(Join-Path $fixture.Workspace 'docs.git')", 'rev-parse', '--is-bare-repository')).Trim() | Should -BeExactly 'true' + (Invoke-Git -Arguments @("--git-dir=$(Join-Path $fixture.Workspace 'docs.git')", 'rev-parse', 'local-topic')).Trim() | + Should -BeExactly $topicHead + (Invoke-Git -Arguments @("--git-dir=$(Join-Path $fixture.Workspace 'docs.git')", 'rev-parse', 'local-tag')).Trim() | + Should -BeExactly $tagHead Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.simple-clone-backup') | Should -BeTrue Test-Path -LiteralPath (Join-Path $fixture.Memory '.git') -PathType Container | Should -BeTrue (Invoke-BootstrapFixture -Fixture $fixture).ExitCode | Should -Be 0 @@ -277,7 +330,41 @@ exit `$LASTEXITCODE $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String $LASTEXITCODE | Should -Not -Be 0 - $output | Should -Match 'duplicate workspace paths' + $output | Should -Match 'workspace paths overlap' + } + + It 'rejects project paths nested inside canonical docs storage' -ForEach @( + @{ UnsafePath = 'docs' } + @{ UnsafePath = 'docs.git' } + ) { + $runner = Join-Path $fixture.Root "invoke-overlap-$($UnsafePath.Replace('.', '-')).ps1" + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'MSXOrg' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } + @{ + Name = 'Unsafe' + Path = '$UnsafePath' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'workspace paths overlap' } It 'reuses a canonical docs worktree with a legacy bare backing path' { @@ -305,4 +392,153 @@ exit `$LASTEXITCODE [IO.Path]::GetFullPath($commonDir) | Should -BeExactly ([IO.Path]::GetFullPath($legacyBacking)) Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse } + + It 'installs missing docs as bare backing plus main worktree and memory as a simple clone' { + $emptyRoot = Join-Path $fixture.Root 'empty-workspace' + $runner = Join-Path $fixture.Root 'invoke-empty-bootstrap.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Fixture' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$emptyRoot' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Be 0 -Because $output + $docs = Join-Path $emptyRoot 'docs' + $backing = Join-Path $emptyRoot 'docs.git' + $memory = Join-Path $emptyRoot 'memory' + Test-Path -LiteralPath (Join-Path $docs '.git') -PathType Leaf | Should -BeTrue + (Invoke-Git -Arguments @("--git-dir=$backing", 'rev-parse', '--is-bare-repository')).Trim() | + Should -BeExactly 'true' + (Invoke-Git -WorkingDirectory $docs -Arguments @('branch', '--show-current')).Trim() | Should -BeExactly 'main' + (Invoke-Git -WorkingDirectory $docs -Arguments @('status', '--porcelain')) | Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + Test-Path -LiteralPath (Join-Path $memory '.git') -PathType Container | + Should -BeTrue -Because $result.Output + $emptyFixture = [pscustomobject]@{ + Root = $fixture.Root + Workspace = $emptyRoot + Remotes = $fixture.Remotes + } + (Invoke-BootstrapFixture -Fixture $emptyFixture).ExitCode | Should -Be 0 + } + + It 'fast-forwards an existing bare backing before creating its missing main worktree' { + $emptyRoot = Join-Path $fixture.Root 'backing-only-workspace' + New-Item -ItemType Directory -Path $emptyRoot | Out-Null + $backing = Join-Path $emptyRoot 'docs.git' + Invoke-Git -Arguments @('clone', '--bare', '--quiet', $fixture.Remotes.docs, $backing) | Out-Null + Add-TestCommit -Repository $fixture.Writers.docs -Name 'Advance after bare clone' + Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null + $remoteHead = (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + $runner = Join-Path $fixture.Root 'invoke-backing-only-bootstrap.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Fixture' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$emptyRoot' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Be 0 -Because $output + $docs = Join-Path $emptyRoot 'docs' + (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $remoteHead + (Invoke-Git -Arguments @("--git-dir=$backing", 'rev-parse', 'main')).Trim() | Should -BeExactly $remoteHead + } + + It 'rolls back migration preparation failure without changing the simple clone' { + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $runner = Join-Path $fixture.Root 'invoke-failed-migration.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $missingDocs = (Join-Path $fixture.Root 'missing-docs.git').Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Fixture' + Path = '' + DocsUrl = '$missingDocs' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'Migration preparation failed' + Test-Path -LiteralPath (Join-Path $fixture.Docs '.git') -PathType Container | Should -BeTrue + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before + } + + It 'installs canonical topology from the seed block' -ForEach @( + @{ Name = 'agent template'; MarkdownPath = '../bootstrap/AGENTS.template.md' } + @{ Name = 'bootstrap README'; MarkdownPath = '../bootstrap/README.md' } + ) { + $workspace = Join-Path $fixture.Root "seed-$($Name.Replace(' ', '-'))" + $seedPath = Join-Path $PSScriptRoot $MarkdownPath + + $result = Invoke-BootstrapSeed -Fixture $fixture -MarkdownPath $seedPath -Workspace $workspace + + $result.ExitCode | Should -Be 0 -Because $result.Output + $docs = Join-Path $workspace 'docs' + $backing = Join-Path $workspace 'docs.git' + $memory = Join-Path $workspace 'memory' + Test-Path -LiteralPath (Join-Path $docs '.git') -PathType Leaf | Should -BeTrue + (Invoke-Git -Arguments @("--git-dir=$backing", 'rev-parse', '--is-bare-repository')).Trim() | + Should -BeExactly 'true' + (Invoke-Git -WorkingDirectory $docs -Arguments @('branch', '--show-current')).Trim() | Should -BeExactly 'main' + (Invoke-Git -WorkingDirectory $docs -Arguments @('status', '--porcelain')) | Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + Test-Path -LiteralPath (Join-Path $memory '.git') -PathType Container | + Should -BeTrue -Because $result.Output + (Invoke-Git -WorkingDirectory $docs -Arguments @('config', '--local', 'user.name')).Trim() | + Should -BeExactly 'Marius Storhaug' + (Invoke-BootstrapSeed -Fixture $fixture -MarkdownPath $seedPath -Workspace $workspace).ExitCode | + Should -Be 0 + } + + It 'refreshes a stale bare backing before the seed creates its canonical worktree' { + $workspace = Join-Path $fixture.Root 'seed-stale-backing' + New-Item -ItemType Directory -Path $workspace | Out-Null + $backing = Join-Path $workspace 'docs.git' + Invoke-Git -Arguments @('clone', '--bare', '--quiet', $fixture.Remotes.docs, $backing) | Out-Null + Add-TestCommit -Repository $fixture.Writers.docs -Name 'Advance before seed' + Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('push', '--quiet') | Out-Null + $remoteHead = (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapSeed -Fixture $fixture -MarkdownPath $script:agentTemplate -Workspace $workspace + + $result.ExitCode | Should -Be 0 -Because $result.Output + $docs = Join-Path $workspace 'docs' + (Invoke-Git -Arguments @("--git-dir=$backing", 'rev-parse', 'main')).Trim() | Should -BeExactly $remoteHead + (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $remoteHead + } } From b3c9255a3d63bea4acf9c68c95be4a38e33eaa0d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:00:31 +0200 Subject: [PATCH 26/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Com?= =?UTF-8?q?plete=20topology=20rollback=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/Initialize-MsxWorkspace.ps1 | 8 +++++++- src/docs/Ways-of-Working/Git-Worktrees.md | 6 +++--- tests/Initialize-MsxWorkspace.Tests.ps1 | 12 +++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 2fa33d3..76b4abe 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -380,6 +380,9 @@ $results = foreach ($repo in $repositories) { Move-Item -LiteralPath $path -Destination $backupPath try { + if ($env:MSX_BOOTSTRAP_TEST_FAIL_AFTER_DOCS_MOVE -eq '1') { + throw 'Injected post-move migration failure.' + } git --git-dir=$expectedBackingPath worktree add --quiet $path $remote.DefaultBranch if ($LASTEXITCODE -ne 0) { throw "Could not create canonical docs worktree '$path' (exit $LASTEXITCODE)." @@ -393,7 +396,10 @@ $results = foreach ($repo in $repositories) { if (-not (Test-Path $path) -and (Test-Path $backupPath)) { Move-Item -LiteralPath $backupPath -Destination $path } - throw + if (Test-Path $expectedBackingPath) { + Remove-Item -LiteralPath $expectedBackingPath -Recurse -Force + } + throw "Migration activation failed for '$path'; the original clone was restored and partial backing removed. $($_.Exception.Message)" } Write-Warning "Migrated '$path' to bare+worktree layout. Verify it, then remove retained backup '$backupPath'." } diff --git a/src/docs/Ways-of-Working/Git-Worktrees.md b/src/docs/Ways-of-Working/Git-Worktrees.md index a1a51bf..0387a9c 100644 --- a/src/docs/Ways-of-Working/Git-Worktrees.md +++ b/src/docs/Ways-of-Working/Git-Worktrees.md @@ -124,13 +124,13 @@ Then follow the normal Implement flow: initial commit → push → draft PR → ```powershell # Remove the worktree -git --git-dir=".git" worktree remove 42-add-pagination +git --git-dir="${repo}.git" worktree remove 42-add-pagination # Delete the local branch ref -git --git-dir=".git" branch -D feat/42-add-pagination +git --git-dir="${repo}.git" branch -D feat/42-add-pagination # Prune if needed (removes stale worktree references) -git --git-dir=".git" worktree prune +git --git-dir="${repo}.git" worktree prune ``` ## Where this connects diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 9691899..70f6522 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -426,7 +426,7 @@ exit `$LASTEXITCODE (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() Test-Path -LiteralPath (Join-Path $memory '.git') -PathType Container | - Should -BeTrue -Because $result.Output + Should -BeTrue -Because $output $emptyFixture = [pscustomobject]@{ Root = $fixture.Root Workspace = $emptyRoot @@ -468,19 +468,20 @@ exit `$LASTEXITCODE (Invoke-Git -Arguments @("--git-dir=$backing", 'rev-parse', 'main')).Trim() | Should -BeExactly $remoteHead } - It 'rolls back migration preparation failure without changing the simple clone' { + It 'rolls back a post-move migration failure without changing the simple clone' { $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() $runner = Join-Path $fixture.Root 'invoke-failed-migration.ps1' $bootstrap = $script:bootstrap.Replace("'", "''") $workspace = $fixture.Workspace.Replace("'", "''") - $missingDocs = (Join-Path $fixture.Root 'missing-docs.git').Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") @" +`$env:MSX_BOOTSTRAP_TEST_FAIL_AFTER_DOCS_MOVE = '1' `$projects = @( @{ Name = 'Fixture' Path = '' - DocsUrl = '$missingDocs' + DocsUrl = '$docsRemote' MemoryUrl = '$memoryRemote' } ) @@ -491,9 +492,10 @@ exit `$LASTEXITCODE $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String $LASTEXITCODE | Should -Not -Be 0 - $output | Should -Match 'Migration preparation failed' + $output | Should -Match 'Migration activation failed' Test-Path -LiteralPath (Join-Path $fixture.Docs '.git') -PathType Container | Should -BeTrue Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.simple-clone-backup') | Should -BeFalse (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before } From edde2c9f02609cfa8a82d6c0b41b0f703b8c4651 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:09:49 +0200 Subject: [PATCH 27/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Rej?= =?UTF-8?q?ect=20all=20context=20path=20overlaps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/Initialize-MsxWorkspace.ps1 | 31 ++++++++++++++-- tests/Initialize-MsxWorkspace.Tests.ps1 | 48 +++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 76b4abe..2265c12 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -273,6 +273,8 @@ $repositories = foreach ($projectDefinition in $Project) { $memoryPath = if ($projectPath) { Join-Path $projectPath 'memory' } else { 'memory' } [pscustomobject]@{ Name = "$projectName/docs" + Project = $projectName + ProjectPath = $projectPath Kind = 'docs' RelativePath = $docsPath Url = [string] $projectDefinition.DocsUrl @@ -280,6 +282,8 @@ $repositories = foreach ($projectDefinition in $Project) { } [pscustomobject]@{ Name = "$projectName/memory" + Project = $projectName + ProjectPath = $projectPath Kind = 'memory' RelativePath = $memoryPath Url = [string] $projectDefinition.MemoryUrl @@ -288,14 +292,37 @@ $repositories = foreach ($projectDefinition in $Project) { } $occupiedPaths = foreach ($repository in $repositories) { - [pscustomobject]@{ Repository = $repository.Name; Path = $repository.RelativePath } + [pscustomobject]@{ + Project = $repository.Project + Repository = $repository.Name + Path = $repository.RelativePath + } if ($repository.Kind -eq 'docs') { - [pscustomobject]@{ Repository = "$($repository.Name) backing"; Path = "$($repository.RelativePath).git" } + if ($repository.ProjectPath) { + [pscustomobject]@{ + Project = $repository.Project + Repository = "$($repository.Project) root" + Path = $repository.ProjectPath + } + } + [pscustomobject]@{ + Project = $repository.Project + Repository = "$($repository.Name) backing" + Path = "$($repository.RelativePath).git" + } + [pscustomobject]@{ + Project = $repository.Project + Repository = "$($repository.Name) migration backup" + Path = "$($repository.RelativePath).simple-clone-backup" + } } } for ($left = 0; $left -lt $occupiedPaths.Count; $left++) { $leftPath = ($occupiedPaths[$left].Path -replace '\\', '/').Trim('/').ToLowerInvariant() for ($right = $left + 1; $right -lt $occupiedPaths.Count; $right++) { + if ($occupiedPaths[$left].Project -eq $occupiedPaths[$right].Project) { + continue + } $rightPath = ($occupiedPaths[$right].Path -replace '\\', '/').Trim('/').ToLowerInvariant() $collision = ( $leftPath -eq $rightPath -or diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 70f6522..126e080 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -333,11 +333,19 @@ exit `$LASTEXITCODE $output | Should -Match 'workspace paths overlap' } - It 'rejects project paths nested inside canonical docs storage' -ForEach @( + It 'rejects project paths overlapping canonical context storage' -ForEach @( @{ UnsafePath = 'docs' } @{ UnsafePath = 'docs.git' } + @{ UnsafePath = 'memory' } + @{ UnsafePath = 'docs/child' } + @{ UnsafePath = 'docs.git/child' } + @{ UnsafePath = 'memory/child' } + @{ UnsafePath = 'docs.simple-clone-backup' } + @{ UnsafePath = 'docs.simple-clone-backup/child' } ) { - $runner = Join-Path $fixture.Root "invoke-overlap-$($UnsafePath.Replace('.', '-')).ps1" + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $safeName = $UnsafePath -replace '[^A-Za-z0-9-]', '-' + $runner = Join-Path $fixture.Root "invoke-overlap-$safeName.ps1" $bootstrap = $script:bootstrap.Replace("'", "''") $workspace = $fixture.Workspace.Replace("'", "''") $docsRemote = $fixture.Remotes.docs.Replace("'", "''") @@ -365,6 +373,42 @@ exit `$LASTEXITCODE $LASTEXITCODE | Should -Not -Be 0 $output | Should -Match 'workspace paths overlap' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $before + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + } + + It 'rejects overlapping non-empty project roots before mutation' { + $runner = Join-Path $fixture.Root 'invoke-overlapping-roots.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Parent' + Path = 'projects/Parent' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } + @{ + Name = 'Child' + Path = 'projects/Parent/Child' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'workspace paths overlap' + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'projects') | Should -BeFalse } It 'reuses a canonical docs worktree with a legacy bare backing path' { From fec49c67c4cd25ed61584089fd01557e4eed7e00 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:18:07 +0200 Subject: [PATCH 28/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Ver?= =?UTF-8?q?ify=20canonical=20context=20remotes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/AGENTS.template.md | 6 ++++++ bootstrap/Initialize-MsxWorkspace.ps1 | 7 +++++++ bootstrap/README.md | 6 ++++++ tests/Initialize-MsxWorkspace.Tests.ps1 | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index 8939462..e95d248 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -30,6 +30,9 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { throw "$docsBacking exists but is not a bare repository." } + if ((git --git-dir=$docsBacking remote get-url origin) -ne $docsUrl) { + throw "$docsBacking origin does not match canonical $docsUrl." + } $refspec = '+refs/heads/*:refs/remotes/origin/*' if ($refspec -notin @(git --git-dir=$docsBacking config --get-all remote.origin.fetch)) { git --git-dir=$docsBacking config --add remote.origin.fetch $refspec @@ -66,6 +69,9 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { throw "Could not create the canonical MSXOrg/docs worktree at $docs." } } else { + if ((git -C $docs remote get-url origin) -ne $docsUrl) { + throw "$docs origin does not match canonical $docsUrl." + } $refspec = '+refs/heads/*:refs/remotes/origin/*' if ($refspec -notin @(git -C $docs config --get-all remote.origin.fetch)) { git -C $docs config --add remote.origin.fetch $refspec diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 2265c12..7299a3d 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -105,6 +105,13 @@ function Sync-ContextRemote { } [string[]] $gitRoot = if ($Bare) { @("--git-dir=$GitPath") } else { @('-C', $GitPath) } + $originUrl = (& git @gitRoot remote get-url origin | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Cannot resolve origin for '$GitPath'. Configure it as '$RepositoryUrl'." + } + if ($originUrl -ne $RepositoryUrl) { + throw "Origin for '$GitPath' is '$originUrl', not canonical '$RepositoryUrl'. Repair it before using this context." + } $allBranchesRefspec = '+refs/heads/*:refs/remotes/origin/*' $fetchRefspecs = @(& git @gitRoot config --get-all remote.origin.fetch) if ($LASTEXITCODE -notin @(0, 1)) { diff --git a/bootstrap/README.md b/bootstrap/README.md index 2b9c06e..4e47b61 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -46,6 +46,9 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ((git --git-dir=$docsBacking rev-parse --is-bare-repository) -ne 'true') { throw "$docsBacking exists but is not a bare repository." } + if ((git --git-dir=$docsBacking remote get-url origin) -ne $docsUrl) { + throw "$docsBacking origin does not match canonical $docsUrl." + } $refspec = '+refs/heads/*:refs/remotes/origin/*' if ($refspec -notin @(git --git-dir=$docsBacking config --get-all remote.origin.fetch)) { git --git-dir=$docsBacking config --add remote.origin.fetch $refspec @@ -82,6 +85,9 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { throw "Could not create the canonical MSXOrg/docs worktree at $docs." } } else { + if ((git -C $docs remote get-url origin) -ne $docsUrl) { + throw "$docs origin does not match canonical $docsUrl." + } $refspec = '+refs/heads/*:refs/remotes/origin/*' if ($refspec -notin @(git -C $docs config --get-all remote.origin.fetch)) { git -C $docs config --add remote.origin.fetch $refspec diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index 126e080..dc1d54d 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -249,6 +249,7 @@ exit `$LASTEXITCODE It 'rejects an unreachable remote without using local context' { $missing = Join-Path $fixture.Root 'missing.git' Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('remote', 'set-url', 'origin', $missing) | Out-Null + $fixture.Remotes.docs = $missing $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() $result = Invoke-BootstrapFixture -Fixture $fixture @@ -258,6 +259,23 @@ exit `$LASTEXITCODE (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before } + It 'rejects a non-canonical origin before fetching context' { + Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @( + 'remote', + 'set-url', + 'origin', + $fixture.Remotes.memory + ) | Out-Null + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'not canonical' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $before + } + It 'installs additional project context through plug-in coordinates' { $runner = Join-Path $fixture.Root 'invoke-project-bootstrap.ps1' $bootstrap = $script:bootstrap.Replace("'", "''") From a8b9ee147a302586e112e8ae64b09a4c5a53b2dd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:30:48 +0200 Subject: [PATCH 29/30] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20[Maintenance]:=20Pre?= =?UTF-8?q?flight=20all=20context=20safety?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/Initialize-MsxWorkspace.ps1 | 117 ++++++++++++--- tests/Initialize-MsxWorkspace.Tests.ps1 | 181 +++++++++++++++++++++++- 2 files changed, 280 insertions(+), 18 deletions(-) diff --git a/bootstrap/Initialize-MsxWorkspace.ps1 b/bootstrap/Initialize-MsxWorkspace.ps1 index 7299a3d..80ddff9 100644 --- a/bootstrap/Initialize-MsxWorkspace.ps1 +++ b/bootstrap/Initialize-MsxWorkspace.ps1 @@ -87,8 +87,7 @@ if ((-not $PSBoundParameters.ContainsKey('UserName')) -or (-not $PSBoundParamete Write-Warning "Using part of the default maintainer identity ($UserName <$UserEmail>). Pass both -UserName and -UserEmail to set your own repository-local author identity." } -function Sync-ContextRemote { - [CmdletBinding(SupportsShouldProcess)] +function Assert-ContextOrigin { param( [Parameter(Mandatory)] [string] $GitPath, @@ -100,10 +99,6 @@ function Sync-ContextRemote { [switch] $Bare ) - if (-not $PSCmdlet.ShouldProcess($GitPath, 'Fetch canonical remote state')) { - return - } - [string[]] $gitRoot = if ($Bare) { @("--git-dir=$GitPath") } else { @('-C', $GitPath) } $originUrl = (& git @gitRoot remote get-url origin | Out-String).Trim() if ($LASTEXITCODE -ne 0) { @@ -112,6 +107,27 @@ function Sync-ContextRemote { if ($originUrl -ne $RepositoryUrl) { throw "Origin for '$GitPath' is '$originUrl', not canonical '$RepositoryUrl'. Repair it before using this context." } +} + +function Sync-ContextRemote { + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string] $GitPath, + + [Parameter(Mandatory)] + [string] $RepositoryUrl, + + [Parameter()] + [switch] $Bare + ) + + if (-not $PSCmdlet.ShouldProcess($GitPath, 'Fetch canonical remote state')) { + return + } + + [string[]] $gitRoot = if ($Bare) { @("--git-dir=$GitPath") } else { @('-C', $GitPath) } + Assert-ContextOrigin -GitPath $GitPath -RepositoryUrl $RepositoryUrl -Bare:$Bare $allBranchesRefspec = '+refs/heads/*:refs/remotes/origin/*' $fetchRefspecs = @(& git @gitRoot config --get-all remote.origin.fetch) if ($LASTEXITCODE -notin @(0, 1)) { @@ -137,10 +153,14 @@ function Sync-ContextRemote { if ($LASTEXITCODE -ne 0) { throw "Cannot resolve the remote default branch for '$GitPath'. Repair origin/HEAD before using this context." } + $remoteHead = (& git @gitRoot rev-parse $defaultRef | Out-String).Trim() + if ($LASTEXITCODE -ne 0 -or -not $remoteHead) { + throw "Cannot resolve remote head '$defaultRef' for '$GitPath'." + } return [pscustomobject]@{ DefaultRef = $defaultRef DefaultBranch = $defaultRef -replace '^origin/', '' - RemoteHead = (& git @gitRoot rev-parse $defaultRef | Out-String).Trim() + RemoteHead = $remoteHead } } @@ -258,6 +278,7 @@ function Set-ContextIdentity { if ($LASTEXITCODE -ne 0) { throw "git config user.email failed for '$Path' (exit $LASTEXITCODE)." } } +$projectNames = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $repositories = foreach ($projectDefinition in $Project) { foreach ($key in @('Name', 'Path', 'DocsUrl', 'MemoryUrl')) { if (-not $projectDefinition.ContainsKey($key) -or $null -eq $projectDefinition[$key]) { @@ -270,6 +291,10 @@ $repositories = foreach ($projectDefinition in $Project) { if (-not $projectName.Trim()) { throw 'Project Name must not be empty.' } + $projectName = $projectName.Trim() + if (-not $projectNames.Add($projectName)) { + throw "Project definitions require unique names. Duplicate: '$projectName'." + } $pathSegments = @($projectPath -split '[\\/]' | Where-Object { $_ -and $_ -ne '.' }) if ([IO.Path]::IsPathRooted($projectPath) -or '..' -in $pathSegments) { throw "Project Path '$projectPath' must be a safe path relative to the workspace root." @@ -342,6 +367,37 @@ for ($left = 0; $left -lt $occupiedPaths.Count; $left++) { } } +foreach ($repository in $repositories | Where-Object Kind -eq 'memory') { + $memoryPath = Join-Path $Root $repository.RelativePath + $memoryGitEntry = Join-Path $memoryPath '.git' + if (Test-Path $memoryGitEntry -PathType Leaf) { + throw "Memory context '$memoryPath' is a worktree, but memory requires a simple checkout with a .git directory." + } + if ((Test-Path $memoryPath) -and -not (Test-Path $memoryGitEntry -PathType Container)) { + throw "Memory context '$memoryPath' is not a supported simple git checkout." + } +} + +foreach ($repository in $repositories) { + $contextPath = Join-Path $Root $repository.RelativePath + $gitEntry = Join-Path $contextPath '.git' + if ($repository.Kind -eq 'memory' -and (Test-Path $gitEntry -PathType Container)) { + Assert-ContextOrigin -GitPath $contextPath -RepositoryUrl $repository.Url + } elseif ($repository.Kind -eq 'docs') { + if (Test-Path $gitEntry -PathType Container) { + Assert-ContextOrigin -GitPath $contextPath -RepositoryUrl $repository.Url + } elseif (Test-Path $gitEntry -PathType Leaf) { + $commonDir = (git -C $contextPath rev-parse --path-format=absolute --git-common-dir | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { + throw "Cannot resolve docs backing repository for '$contextPath'." + } + Assert-ContextOrigin -GitPath $commonDir -RepositoryUrl $repository.Url -Bare + } elseif (Test-Path "$contextPath.git") { + Assert-ContextOrigin -GitPath "$contextPath.git" -RepositoryUrl $repository.Url -Bare + } + } +} + if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { New-Item -ItemType Directory -Force -Path $Root | Out-Null } @@ -349,9 +405,13 @@ if ($PSCmdlet.ShouldProcess($Root, 'Create workspace root')) { $results = foreach ($repo in $repositories) { $path = Join-Path $Root $repo.RelativePath if ($repo.Kind -eq 'memory') { - if (-not (Test-Path (Join-Path $path '.git'))) { + $memoryGitEntry = Join-Path $path '.git' + if (Test-Path $memoryGitEntry -PathType Leaf) { + throw "Memory context '$path' is a worktree, but memory requires a simple checkout with a .git directory." + } + if (-not (Test-Path $memoryGitEntry -PathType Container)) { if (Test-Path $path) { - throw "Cannot clone memory into '$path': it exists but is not a git repository." + throw "Cannot clone memory into '$path': it exists but is not a supported simple git checkout." } if ($PSCmdlet.ShouldProcess($repo.Url, "Clone memory into '$path'")) { New-Item -ItemType Directory -Path (Split-Path -Parent $path) -Force | Out-Null @@ -412,8 +472,13 @@ $results = foreach ($repo in $repositories) { throw "Migration preparation failed for '$path'; the original clone is unchanged. $($_.Exception.Message)" } - Move-Item -LiteralPath $path -Destination $backupPath + $moved = $false try { + if ($env:MSX_BOOTSTRAP_TEST_FAIL_DOCS_MOVE -eq '1') { + throw 'Injected migration move failure.' + } + Move-Item -LiteralPath $path -Destination $backupPath -ErrorAction Stop + $moved = $true if ($env:MSX_BOOTSTRAP_TEST_FAIL_AFTER_DOCS_MOVE -eq '1') { throw 'Injected post-move migration failure.' } @@ -423,17 +488,37 @@ $results = foreach ($repo in $repositories) { } Sync-ContextCheckout -Path $path -RepositoryUrl $repo.Url -Confirm:$false | Out-Null } catch { - if (Test-Path $path) { + $activationError = $_ + $rollbackErrors = [Collections.Generic.List[string]]::new() + if ($moved -and (Test-Path $path)) { git --git-dir=$expectedBackingPath worktree remove --force $path 2>$null - Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction SilentlyContinue + if ($LASTEXITCODE -ne 0) { + $rollbackErrors.Add("git worktree remove failed for '$path'.") + } + try { + Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction Stop + } catch { + $rollbackErrors.Add("Could not remove partial worktree '$path': $($_.Exception.Message)") + } } - if (-not (Test-Path $path) -and (Test-Path $backupPath)) { - Move-Item -LiteralPath $backupPath -Destination $path + if ($moved -and -not (Test-Path $path) -and (Test-Path $backupPath)) { + try { + Move-Item -LiteralPath $backupPath -Destination $path -ErrorAction Stop + } catch { + $rollbackErrors.Add("Could not restore '$backupPath' to '$path': $($_.Exception.Message)") + } } if (Test-Path $expectedBackingPath) { - Remove-Item -LiteralPath $expectedBackingPath -Recurse -Force + try { + Remove-Item -LiteralPath $expectedBackingPath -Recurse -Force -ErrorAction Stop + } catch { + $rollbackErrors.Add("Could not remove partial backing '$expectedBackingPath': $($_.Exception.Message)") + } + } + if ($rollbackErrors.Count -gt 0) { + throw "Migration activation and rollback both failed. $($rollbackErrors -join ' ') Original error: $($activationError.Exception.Message)" } - throw "Migration activation failed for '$path'; the original clone was restored and partial backing removed. $($_.Exception.Message)" + throw "Migration activation failed for '$path'; the original clone is usable and partial backing removed. $($activationError.Exception.Message)" } Write-Warning "Migrated '$path' to bare+worktree layout. Verify it, then remove retained backup '$backupPath'." } diff --git a/tests/Initialize-MsxWorkspace.Tests.ps1 b/tests/Initialize-MsxWorkspace.Tests.ps1 index dc1d54d..65393f1 100644 --- a/tests/Initialize-MsxWorkspace.Tests.ps1 +++ b/tests/Initialize-MsxWorkspace.Tests.ps1 @@ -276,6 +276,79 @@ exit `$LASTEXITCODE Should -BeExactly $before } + It 'rejects a non-canonical bare docs origin before mutation' { + (Invoke-BootstrapFixture -Fixture $fixture).ExitCode | Should -Be 0 + $backing = Join-Path $fixture.Workspace 'docs.git' + Invoke-Git -Arguments @( + "--git-dir=$backing", + 'remote', + 'set-url', + 'origin', + $fixture.Remotes.memory + ) | Out-Null + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'not canonical' + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $before + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + } + + It 'rejects a non-canonical memory origin before docs migration' { + Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @( + 'remote', + 'set-url', + 'origin', + $fixture.Remotes.docs + ) | Out-Null + $docsBefore = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $memoryBefore = (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'not canonical' + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $docsBefore + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $memoryBefore + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + } + + It 'rejects a memory worktree before docs migration' { + Remove-Item -LiteralPath $fixture.Memory -Recurse -Force + $memoryBacking = Join-Path $fixture.Root 'memory-backing.git' + Invoke-Git -Arguments @('clone', '--bare', '--quiet', $fixture.Remotes.memory, $memoryBacking) | Out-Null + Invoke-Git -Arguments @( + "--git-dir=$memoryBacking", + 'worktree', + 'add', + '--quiet', + $fixture.Memory, + 'main' + ) | Out-Null + $docsBefore = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $memoryBefore = (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() + + $result = Invoke-BootstrapFixture -Fixture $fixture + + $result.ExitCode | Should -Not -Be 0 + $result.Output | Should -Match 'memory requires a simple checkout' + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $docsBefore + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $memoryBefore + } + It 'installs additional project context through plug-in coordinates' { $runner = Join-Path $fixture.Root 'invoke-project-bootstrap.ps1' $bootstrap = $script:bootstrap.Replace("'", "''") @@ -351,6 +424,51 @@ exit `$LASTEXITCODE $output | Should -Match 'workspace paths overlap' } + It 'rejects duplicate project names before mutation' -ForEach @( + @{ SecondPath = '' } + @{ SecondPath = 'docs' } + ) { + $beforeDocs = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $beforeMemory = (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() + $runner = Join-Path $fixture.Root "invoke-duplicate-name-$($SecondPath -replace '[^A-Za-z0-9]', '-').ps1" + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$projects = @( + @{ + Name = 'Duplicate' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } + @{ + Name = 'Duplicate' + Path = '$SecondPath' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'unique names' + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $beforeDocs + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $beforeMemory + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + } + It 'rejects project paths overlapping canonical context storage' -ForEach @( @{ UnsafePath = 'docs' } @{ UnsafePath = 'docs.git' } @@ -361,7 +479,8 @@ exit `$LASTEXITCODE @{ UnsafePath = 'docs.simple-clone-backup' } @{ UnsafePath = 'docs.simple-clone-backup/child' } ) { - $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $beforeDocs = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $beforeMemory = (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() $safeName = $UnsafePath -replace '[^A-Za-z0-9-]', '-' $runner = Join-Path $fixture.Root "invoke-overlap-$safeName.ps1" $bootstrap = $script:bootstrap.Replace("'", "''") @@ -391,10 +510,18 @@ exit `$LASTEXITCODE $LASTEXITCODE | Should -Not -Be 0 $output | Should -Match 'workspace paths overlap' + $unsafeRoot = Join-Path $fixture.Workspace $UnsafePath + foreach ($child in @('docs', 'docs.git', 'memory', 'docs.simple-clone-backup')) { + Test-Path -LiteralPath (Join-Path $unsafeRoot $child) | Should -BeFalse + } (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | - Should -BeExactly $before + Should -BeExactly $beforeDocs + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $beforeMemory (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $fixture.Memory -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty } It 'rejects overlapping non-empty project roots before mutation' { @@ -489,12 +616,28 @@ exit `$LASTEXITCODE Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() Test-Path -LiteralPath (Join-Path $memory '.git') -PathType Container | Should -BeTrue -Because $output + (Invoke-Git -WorkingDirectory $memory -Arguments @('branch', '--show-current')).Trim() | + Should -BeExactly 'main' + (Invoke-Git -WorkingDirectory $memory -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + (Invoke-Git -WorkingDirectory $memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('rev-parse', 'HEAD')).Trim() + foreach ($repository in @($docs, $memory)) { + (Invoke-Git -WorkingDirectory $repository -Arguments @('config', '--local', 'user.name')).Trim() | + Should -BeExactly 'Fixture User' + (Invoke-Git -WorkingDirectory $repository -Arguments @('config', '--local', 'user.email')).Trim() | + Should -BeExactly 'fixture@example.invalid' + } $emptyFixture = [pscustomobject]@{ Root = $fixture.Root Workspace = $emptyRoot Remotes = $fixture.Remotes } (Invoke-BootstrapFixture -Fixture $emptyFixture).ExitCode | Should -Be 0 + (Invoke-Git -WorkingDirectory $docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.docs -Arguments @('rev-parse', 'HEAD')).Trim() + (Invoke-Git -WorkingDirectory $memory -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly (Invoke-Git -WorkingDirectory $fixture.Writers.memory -Arguments @('rev-parse', 'HEAD')).Trim() } It 'fast-forwards an existing bare backing before creating its missing main worktree' { @@ -561,6 +704,40 @@ exit `$LASTEXITCODE (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | Should -BeExactly $before } + It 'removes prepared backing when moving the simple clone fails' { + $before = (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() + $runner = Join-Path $fixture.Root 'invoke-failed-move.ps1' + $bootstrap = $script:bootstrap.Replace("'", "''") + $workspace = $fixture.Workspace.Replace("'", "''") + $docsRemote = $fixture.Remotes.docs.Replace("'", "''") + $memoryRemote = $fixture.Remotes.memory.Replace("'", "''") + @" +`$env:MSX_BOOTSTRAP_TEST_FAIL_DOCS_MOVE = '1' +`$projects = @( + @{ + Name = 'Fixture' + Path = '' + DocsUrl = '$docsRemote' + MemoryUrl = '$memoryRemote' + } +) +& '$bootstrap' -Root '$workspace' -Project `$projects -UserName 'Fixture User' -UserEmail 'fixture@example.invalid' +exit `$LASTEXITCODE +"@ | Set-Content -LiteralPath $runner + + $output = & $script:pwsh -NoProfile -File $runner 2>&1 | Out-String + + $LASTEXITCODE | Should -Not -Be 0 + $output | Should -Match 'Migration activation failed' + Test-Path -LiteralPath (Join-Path $fixture.Docs '.git') -PathType Container | Should -BeTrue + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.git') | Should -BeFalse + Test-Path -LiteralPath (Join-Path $fixture.Workspace 'docs.simple-clone-backup') | Should -BeFalse + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('rev-parse', 'HEAD')).Trim() | + Should -BeExactly $before + (Invoke-Git -WorkingDirectory $fixture.Docs -Arguments @('status', '--porcelain')) | + Should -BeNullOrEmpty + } + It 'installs canonical topology from the seed block' -ForEach @( @{ Name = 'agent template'; MarkdownPath = '../bootstrap/AGENTS.template.md' } @{ Name = 'bootstrap README'; MarkdownPath = '../bootstrap/README.md' } From b854996692cf77418b581cd73457650f8de6526c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:53:02 +0200 Subject: [PATCH 30/30] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Resolve=20seed?= =?UTF-8?q?=20default=20branches=20dynamically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- bootstrap/AGENTS.template.md | 17 +++++++++++------ bootstrap/README.md | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bootstrap/AGENTS.template.md b/bootstrap/AGENTS.template.md index e95d248..dd56c2c 100644 --- a/bootstrap/AGENTS.template.md +++ b/bootstrap/AGENTS.template.md @@ -83,19 +83,24 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ($LASTEXITCODE -ne 0) { throw "git fetch of MSXOrg/docs failed (exit $LASTEXITCODE). Do not use stale context." } + git -C $docs remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not detect the MSXOrg/docs default branch." } + $defaultRef = (git -C $docs symbolic-ref --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve origin/HEAD in $docs." } + $defaultBranch = $defaultRef -replace '^origin/', '' $branch = (git -C $docs branch --show-current | Out-String).Trim() - if ($branch -ne 'main') { - throw "$docs is on '$branch', not 'main'. Switch branches before using this context." + if ($branch -ne $defaultBranch) { + throw "$docs is on '$branch', not '$defaultBranch'. Switch branches before using this context." } if (@(git -C $docs status --porcelain).Count -gt 0) { throw "$docs has uncommitted changes. Resolve them before using this context." } - git -C $docs merge --ff-only --quiet origin/main + git -C $docs merge --ff-only --quiet $defaultRef if ($LASTEXITCODE -ne 0) { - throw "MSXOrg/docs cannot fast-forward to origin/main. Do not use stale context." + throw "MSXOrg/docs cannot fast-forward to $defaultRef. Do not use stale context." } - if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse origin/main)) { - throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." + if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse $defaultRef)) { + throw "$docs is not exactly synchronized with $defaultRef. Reconcile local commits before using this context." } } $projects = @( diff --git a/bootstrap/README.md b/bootstrap/README.md index 4e47b61..469d7a8 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -99,19 +99,24 @@ if (-not (Test-Path (Join-Path $docs '.git'))) { if ($LASTEXITCODE -ne 0) { throw "git fetch of MSXOrg/docs failed (exit $LASTEXITCODE). Do not use stale context." } + git -C $docs remote set-head origin --auto | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not detect the MSXOrg/docs default branch." } + $defaultRef = (git -C $docs symbolic-ref --short refs/remotes/origin/HEAD | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { throw "Could not resolve origin/HEAD in $docs." } + $defaultBranch = $defaultRef -replace '^origin/', '' $branch = (git -C $docs branch --show-current | Out-String).Trim() - if ($branch -ne 'main') { - throw "$docs is on '$branch', not 'main'. Switch branches before using this context." + if ($branch -ne $defaultBranch) { + throw "$docs is on '$branch', not '$defaultBranch'. Switch branches before using this context." } if (@(git -C $docs status --porcelain).Count -gt 0) { throw "$docs has uncommitted changes. Resolve them before using this context." } - git -C $docs merge --ff-only --quiet origin/main + git -C $docs merge --ff-only --quiet $defaultRef if ($LASTEXITCODE -ne 0) { - throw "MSXOrg/docs cannot fast-forward to origin/main. Do not use stale context." + throw "MSXOrg/docs cannot fast-forward to $defaultRef. Do not use stale context." } - if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse origin/main)) { - throw "$docs is not exactly synchronized with origin/main. Reconcile local commits before using this context." + if ((git -C $docs rev-parse HEAD) -ne (git -C $docs rev-parse $defaultRef)) { + throw "$docs is not exactly synchronized with $defaultRef. Reconcile local commits before using this context." } } $projects = @(