Skip to content

feat(archetypes): add scope metadata to .md files and implement GetScope() (SO-89)#5

Open
castrojo wants to merge 4 commits intomainfrom
feat/SO-89-archetype-scope-metadata
Open

feat(archetypes): add scope metadata to .md files and implement GetScope() (SO-89)#5
castrojo wants to merge 4 commits intomainfrom
feat/SO-89-archetype-scope-metadata

Conversation

@castrojo
Copy link
Copy Markdown
Owner

@castrojo castrojo commented Apr 9, 2026

Summary

Implements SO-89: adds ## Scope sections to archetype markdown files and a GetScope(slug string) ([]string, error) parser in Go.

Changes

Archetype .md files (9 files updated)

Each file now contains a ## Scope section with a comma-separated list of domain tags:

Archetype Scope tags
ceo delegation, review, planning
devops infra, ci-cd, pipeline, workflow, ops
architect application-code, go, typescript, svelte, architecture, design, fullstack
auditor audit, review, analysis
castrojo-docs docs, documentation, writing
castrojo-engineer-bluefin application-code, go, bluefin, lts, aurora, containers
castrojo-engineer-cncf application-code, go, typescript, svelte, cncf, website
castrojo-qa review, testing, qa
principle-engineer application-code, go, typescript, svelte, architecture, design, fullstack

internal/archetypes/archetypes.go

Added GetScope(slug string) ([]string, error):

  • Reads archetype file via existing Read() (supports override dirs)
  • Parses the ## Scope section, splits comma-separated tags
  • Returns []string{} (empty slice, no error) for missing files or missing Scope section

internal/archetypes/archetypes_test.go (new file)

6 unit tests:

  • TestGetScope_Devops — verifies infra/ci-cd/pipeline/workflow/ops
  • TestGetScope_Architect — verifies application-code/go/typescript/svelte/architecture/design/fullstack
  • TestGetScope_CastrojoDocs — verifies docs/documentation/writing
  • TestGetScope_NonexistentSlug — empty slice, no error (AC4)
  • TestGetScope_NoScopeSectionother.md has no Scope section → empty slice, no error (AC4)
  • TestGetScope_CEO — verifies delegation/review/planning

Acceptance Criteria

  • ✅ AC1: Each archetype .md file contains a ## Scope section
  • ✅ AC2: GetScope(slug string) ([]string, error) implemented and returns scope list
  • ✅ AC3: Unit tests cover devops, architect, castrojo-docs (plus CEO and backward-compat cases)
  • ✅ AC4: Returns empty slice (not error) for missing archetype or missing Scope section

Test output

=== RUN   TestGetScope_Devops
--- PASS: TestGetScope_Devops (0.00s)
=== RUN   TestGetScope_Architect
--- PASS: TestGetScope_Architect (0.00s)
=== RUN   TestGetScope_CastrojoDocs
--- PASS: TestGetScope_CastrojoDocs (0.00s)
=== RUN   TestGetScope_NonexistentSlug
--- PASS: TestGetScope_NonexistentSlug (0.00s)
=== RUN   TestGetScope_NoScopeSection
--- PASS: TestGetScope_NoScopeSection (0.00s)
=== RUN   TestGetScope_CEO
--- PASS: TestGetScope_CEO (0.00s)
PASS
ok  github.com/msoedov/secondorder/internal/archetypes 0.002s

Closes SO-89. Unblocks SO-90 (API advisory warning) and SO-91 (UI scope-mismatch display).

castrojo added 2 commits April 9, 2026 08:29
- Add copilot option to runner <select> so existing copilot-runner
  agents preserve their value on save
- Add data-original attribute to capture the runner value at page load
- Add #copilot-runner-warning hidden banner shown by checkCopilotRunnerWarning()
  when user changes away from copilot before saving
- Add checkCopilotRunnerWarning() JS function called from onchange handler
- Add copilot model list to updateModels() in partials.html

AC:
1. Warning shown when runner changed from copilot to another value ✓
2. Warning text: 'Warning: changing the runner from copilot may break this agent.' ✓
3. Client-side JS fires before save (onchange on <select>) ✓
4. No warning shown when copilot runner is unchanged ✓
5. go build ./... and go test ./... pass ✓
… scope list (SO-89)

- Add ## Scope section to 9 archetype .md files:
  ceo, devops, architect, auditor, castrojo-docs,
  castrojo-engineer-bluefin, castrojo-engineer-cncf, castrojo-qa, principle-engineer
- Implement archetypes.GetScope(slug string) ([]string, error)
  - Parses ## Scope section, returns comma-separated tags
  - Returns empty slice (not error) for missing archetype or missing section (AC4)
- Add archetypes_test.go with 6 tests covering AC2/AC3/AC4:
  TestGetScope_Devops, TestGetScope_Architect, TestGetScope_CastrojoDocs,
  TestGetScope_NonexistentSlug, TestGetScope_NoScopeSection, TestGetScope_CEO

Closes SO-89
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new GetScope() function to extract scope tags from archetype markdown files, provides comprehensive test coverage, adds "Scope" sections to multiple archetype documents, and enhances the UI with a Copilot runner selection warning and dynamic model population.

Changes

Cohort / File(s) Summary
GetScope Function & Tests
internal/archetypes/archetypes.go, internal/archetypes/archetypes_test.go
New exported function GetScope(slug string) parses archetype .md files, extracts comma-separated tags from the ## Scope section, and returns an empty slice for missing files or absent sections. Comprehensive test suite validates expected scope tags for multiple archetypes (devops, architect, castrojo-docs, ceo), boundary conditions (stops parsing at next ## heading), and error handling for unknown slugs.
Archetype Scope Definitions
internal/archetypes/{architect, auditor, castrojo-docs, castrojo-engineer-bluefin, castrojo-engineer-cncf, castrojo-qa, ceo, devops}.md, internal/archetypes/principle-engineer.md, internal/archetypes/testdata/scope-boundary.md
Added ## Scope sections to 8 existing archetype documents listing relevant work areas and responsibilities. New principle-engineer.md archetype document defines role scope, responsibilities, constraints, and workflow. Test fixture scope-boundary.md validates parser boundary handling.
Copilot Runner UI Enhancement
internal/templates/agent_detail.html, internal/templates/partials.html
Added checkCopilotRunnerWarning() JavaScript function to display warning when Copilot runner is deselected. Enhanced updateModels() to populate runner-specific models when copilot runner is selected (Claude/GPT-5/Gemini variants). Added data-original attribute to track runner state changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Template as agent_detail.html
    participant JS as JavaScript Handler
    participant ModelUpdater as updateModels()

    User->>Template: Change runner dropdown
    Template->>JS: checkCopilotRunnerWarning()
    JS->>JS: Compare original vs. new value
    alt Original was 'copilot'
        JS->>Template: Show warning alert
    else
        JS->>Template: Hide warning alert
    end
    Template->>ModelUpdater: Call updateModels()
    ModelUpdater->>ModelUpdater: Check if runner === 'copilot'
    alt Runner is 'copilot'
        ModelUpdater->>Template: Populate Copilot-specific models
    else
        ModelUpdater->>Template: Use default models
    end
Loading
sequenceDiagram
    participant Caller
    participant GetScope as GetScope(slug)
    participant FileSystem as File System
    participant Parser as Parser

    Caller->>GetScope: Request scope for archetype
    GetScope->>FileSystem: Read archetype .md file
    alt File not found
        FileSystem-->>GetScope: Error or empty
        GetScope-->>Caller: Return empty slice, no error
    else File exists
        FileSystem-->>GetScope: File content
        GetScope->>Parser: Extract ## Scope section
        Parser->>Parser: Find ## Scope heading
        Parser->>Parser: Parse until next ## heading
        Parser->>Parser: Split first line by comma
        alt Scope section found
            Parser-->>GetScope: Extracted tags
            GetScope-->>Caller: Return tag slice, no error
        else Scope section missing
            Parser-->>GetScope: No tags
            GetScope-->>Caller: Return empty slice, no error
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Scopes defined with tags so clear,
Archetypes now show their sphere,
Copilot warnings take their flight,
Templates parse with pure delight,
A test suite bounds each section tight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding scope metadata to archetype markdown files and implementing the GetScope() function.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context on scope sections added to archetype files, the GetScope() implementation, test coverage, and acceptance criteria.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a GetScope function to parse scope metadata from archetype markdown files and updates various archetypes with these tags. It also adds a new 'Principal Engineer' archetype and enhances the agent detail UI with a 'copilot' runner option and associated warnings. Review feedback identifies a logic error in the GetScope parser that prevents it from reading tags across multiple lines as documented, and notes a spelling inconsistency between the filename and content of the new 'Principal Engineer' archetype.

Comment on lines +144 to +145
// Scope section is a single line of tags; stop after first non-blank line
break
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The break statement here limits the scope section to only the first non-blank line of tags. This contradicts the function's documentation on line 102, which states that tags can be on "line(s)" (plural). Removing this break allows the parser to correctly handle tags spread across multiple lines until the next header is encountered, making the implementation consistent with the docstring.

@@ -0,0 +1,43 @@
# Principal Engineer
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The filename principle-engineer.md (and the corresponding slug) contains a typo. The content of the file correctly uses "Principal Engineer" (lines 1, 3, 13, 40), which is the standard professional title. It is recommended to rename the file to principal-engineer.md to ensure consistency between the filename/slug and the archetype's title.

castrojo added 2 commits April 9, 2026 11:42
…SO-102)

Proves that GetScope() stops parsing at the next ## heading and does
not bleed content from subsequent sections into the returned scope list.

Uses principle-engineer.md which has '## Scope clarification' before
'## Scope', verifying the boundary-stop behavior explicitly.

Closes QA Finding 3 from SO-101.
…(SO-103)

Replace the principle-engineer.md-based TestGetScope_StopsAtNextHeading with a
cleaner version that uses a dedicated fixture file.

Fixture: internal/archetypes/testdata/scope-boundary.md
  ## Scope
  foo, bar

  ## Other Section
  baz, qux

The test proves GetScope() returns ["foo", "bar"] and NOT ["foo", "bar", "baz", "qux"],
with explicit positive and negative assertions confirming the parser stops at '##'.

Uses SetOverridesDir("testdata") + t.Cleanup to isolate the test without
modifying global state permanently.

Closes SO-103.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/archetypes/archetypes_test.go (1)

8-97: Consider extracting a shared scope assertion helper.

These tests repeat the same length/index comparison pattern. A helper (or table-driven structure) would reduce duplication and simplify future archetype additions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/archetypes/archetypes_test.go` around lines 8 - 97, The tests for
GetScope duplicate identical length and element-by-element checks across
TestGetScope_Devops/Architect/CastrojoDocs/CEO/NonexistentSlug/NoScopeSection;
extract a helper (e.g., assertScopeEqual(t *testing.T, got []string, want
[]string, name string)) that checks lengths and each index and calls
t.Fatalf/t.Errorf with contextual messages, then replace the repeated checks in
each TestGetScope_* to call that helper (or convert the tests to a table-driven
TestGetScope that iterates cases with name, slug, expected and uses the same
helper for assertions) so assertion logic is centralized and duplication removed
while still calling GetScope in each test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/archetypes/archetypes.go`:
- Around line 110-114: The current Read(slug) error handling treats every read
failure as “archetype not found” and returns an empty slice, which hides real IO
errors; update the logic around the Read(slug) call so only a true “not found”
condition returns []string{}, nil and all other errors are returned to the
caller. Concretely, detect the not-found sentinel (e.g., errors.Is(err,
ErrNotFound) or os.IsNotExist(err) depending on your Read implementation) and
keep the empty-slice return for that case, but for any other err return nil,
err; adjust the branch that currently returns []string{}, nil to perform this
conditional check against the specific NotFound error.

---

Nitpick comments:
In `@internal/archetypes/archetypes_test.go`:
- Around line 8-97: The tests for GetScope duplicate identical length and
element-by-element checks across
TestGetScope_Devops/Architect/CastrojoDocs/CEO/NonexistentSlug/NoScopeSection;
extract a helper (e.g., assertScopeEqual(t *testing.T, got []string, want
[]string, name string)) that checks lengths and each index and calls
t.Fatalf/t.Errorf with contextual messages, then replace the repeated checks in
each TestGetScope_* to call that helper (or convert the tests to a table-driven
TestGetScope that iterates cases with name, slug, expected and uses the same
helper for assertions) so assertion logic is centralized and duplication removed
while still calling GetScope in each test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2e00eceb-d628-44d3-8753-bef1dcc683f6

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2bc6a and c8c3141.

📒 Files selected for processing (14)
  • internal/archetypes/archetypes.go
  • internal/archetypes/archetypes_test.go
  • internal/archetypes/architect.md
  • internal/archetypes/auditor.md
  • internal/archetypes/castrojo-docs.md
  • internal/archetypes/castrojo-engineer-bluefin.md
  • internal/archetypes/castrojo-engineer-cncf.md
  • internal/archetypes/castrojo-qa.md
  • internal/archetypes/ceo.md
  • internal/archetypes/devops.md
  • internal/archetypes/principle-engineer.md
  • internal/archetypes/testdata/scope-boundary.md
  • internal/templates/agent_detail.html
  • internal/templates/partials.html

Comment on lines +110 to +114
data, err := Read(slug)
if err != nil {
// Archetype not found — return empty slice, no error (backward-compatible)
return []string{}, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not suppress non-not found read failures.

At Line 111, all Read errors are treated as “missing archetype.” This masks genuine IO/read failures and can silently return incorrect empty scopes.

🔧 Proposed fix
 import (
 	"embed"
+	"errors"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"strings"
 )
@@
 	data, err := Read(slug)
 	if err != nil {
-		// Archetype not found — return empty slice, no error (backward-compatible)
-		return []string{}, nil
+		// Missing archetype is backward-compatible: empty scope, no error.
+		if errors.Is(err, fs.ErrNotExist) {
+			return []string{}, nil
+		}
+		// Propagate unexpected read failures.
+		return nil, err
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/archetypes/archetypes.go` around lines 110 - 114, The current
Read(slug) error handling treats every read failure as “archetype not found” and
returns an empty slice, which hides real IO errors; update the logic around the
Read(slug) call so only a true “not found” condition returns []string{}, nil and
all other errors are returned to the caller. Concretely, detect the not-found
sentinel (e.g., errors.Is(err, ErrNotFound) or os.IsNotExist(err) depending on
your Read implementation) and keep the empty-slice return for that case, but for
any other err return nil, err; adjust the branch that currently returns
[]string{}, nil to perform this conditional check against the specific NotFound
error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant