Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .opencode/skills/always-on-guidance/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,52 @@ tags: [always-on, coding, quality]

Rules that apply to every coding session.

## Critical Safety

- NEVER force push to main

## Tool Usage Discipline

- Check `hivemind_find` before solving problems from scratch
- Read files before editing — never guess at content
- Use `org_*` tools for work item management
- Use `comms_*` tools for agent messaging and file reservations
- Use `forge_*` tools for multi-agent coordination
- Use `hivemind_*` tools for learning storage and retrieval
- Check `hivemind_find` before solving problems from scratch

## Code Quality

### Structure
- Functions do one thing well
- No dead code or unused imports

### Clarity
- Names reveal intent — no abbreviations
- Comments explain *why*, not *what*
- No dead code or unused imports
- Error messages include context

## Testing

- Write tests for all new code
### Test Infrastructure
- Use `db.OpenMemory()` for database tests
- Use `t.TempDir()` for filesystem tests
- Standard library `testing` package only — no testify

### Test Practice
- Write tests for all new code
- Test names: `TestXxx_Description`

## Error Handling

### Error Propagation
- Return errors, don't panic
- Wrap errors with context: `fmt.Errorf("operation: %w", err)`

### Error Coverage
- Handle all error paths — no ignored returns
- Use `errors.Is` for sentinel error checks

## Git Discipline

- Conventional commits: `type: description`
- Never force push to main
- Commit early, commit often
22 changes: 8 additions & 14 deletions .opencode/skills/forge-global/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,17 @@ Patterns for forge coordination that apply across projects.

## When to Forge

Use a forge when:
- Task touches 3+ files
- Task has independent subtasks that can parallelize
- Task benefits from specialized workers (e.g., tests vs implementation)

Don't forge when:
- Task is a single-file change
- Task requires sequential steps with tight coupling
- Task is exploratory or investigative
| Signal | Forge | Skip |
|--------|-------|------|
| File count | Task touches 3+ files | Task is a single-file change |
| Task structure | Independent subtasks that can parallelize | Sequential steps with tight coupling |
| Work type | Benefits from specialized workers (e.g., tests vs implementation) | Exploratory or investigative work |

## File Reservation Protocol

1. Workers MUST call `comms_reserve(paths=[...])` before editing
2. Reservations are exclusive by default
3. Set `ttl_seconds` to auto-release after timeout
4. Always release when done: `comms_release(paths=[...])`
5. Coordinator can emergency release: `comms_release_all()`
1. FIRST, workers MUST call `comms_reserve(paths=[...], ttl_seconds=300)` before editing any files (5-minute auto-release) — reservations are exclusive by default
2. THEN, always release when done: `comms_release(paths=[...])`
3. FINALLY, coordinator can emergency release if workers fail: `comms_release_all()`

## Worker Spawning

Expand Down
157 changes: 157 additions & 0 deletions internal/agentkit/agentkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ func TestWorkerPrompt_HardenedStructure(t *testing.T) {
}
}


func TestForgeMD_StructuralHardening(t *testing.T) {
// Read forge.md from embedded content.
data, err := content.ReadFile("content/commands/forge.md")
Expand Down Expand Up @@ -903,6 +904,162 @@ func TestForgeCoordinationSkill_StructuralHardening(t *testing.T) {
}
}

func TestAlwaysOnGuidance_StructuralHardening(t *testing.T) {
data, err := content.ReadFile("content/skills/always-on-guidance/SKILL.md")
if err != nil {
t.Fatalf("read embedded always-on-guidance/SKILL.md: %v", err)
}
text := string(data)

// (1) Critical Safety section exists and appears before Tool Usage Discipline.
safetyIdx := strings.Index(text, "## Critical Safety")
if safetyIdx < 0 {
t.Error("always-on-guidance: missing '## Critical Safety' section")
}
toolUsageIdx := strings.Index(text, "## Tool Usage Discipline")
if toolUsageIdx < 0 {
t.Error("always-on-guidance: missing '## Tool Usage Discipline' section")
}
if safetyIdx >= 0 && toolUsageIdx >= 0 && safetyIdx >= toolUsageIdx {
t.Error("always-on-guidance: '## Critical Safety' must appear before '## Tool Usage Discipline'")
}

// (2) Force push rule uses RFC 2119 uppercase keyword (DR-002).
if !strings.Contains(text, "NEVER force push") {
t.Error("always-on-guidance: force push rule must use RFC 2119 keyword 'NEVER'")
}

// (3) hivemind_find is the first item in Tool Usage Discipline section.
if toolUsageIdx >= 0 {
afterToolUsage := text[toolUsageIdx:]
firstDashIdx := strings.Index(afterToolUsage, "\n- ")
if firstDashIdx < 0 {
t.Error("always-on-guidance: no list items in Tool Usage Discipline")
} else {
// Extract the first list item line.
firstItemStart := firstDashIdx + 3 // skip "\n- "
firstItemEnd := strings.Index(afterToolUsage[firstItemStart:], "\n")
if firstItemEnd < 0 {
firstItemEnd = len(afterToolUsage) - firstItemStart
}
firstItem := afterToolUsage[firstItemStart : firstItemStart+firstItemEnd]
if !strings.Contains(firstItem, "hivemind_find") {
t.Errorf("always-on-guidance: first Tool Usage item should mention hivemind_find, got %q", firstItem)
}
}
}

// (4) Code Quality split into sub-headers.
for _, sub := range []string{"### Structure", "### Clarity"} {
if !strings.Contains(text, sub) {
t.Errorf("always-on-guidance: missing Code Quality sub-header %q", sub)
}
}

// (5) Testing split into sub-headers.
for _, sub := range []string{"### Test Infrastructure", "### Test Practice"} {
if !strings.Contains(text, sub) {
t.Errorf("always-on-guidance: missing Testing sub-header %q", sub)
}
}

// (6) Error Handling split into sub-headers.
for _, sub := range []string{"### Error Propagation", "### Error Coverage"} {
if !strings.Contains(text, sub) {
t.Errorf("always-on-guidance: missing Error Handling sub-header %q", sub)
}
}
}

func TestForgeGlobal_StructuralHardening(t *testing.T) {
data, err := content.ReadFile("content/skills/forge-global/SKILL.md")
if err != nil {
t.Fatalf("read embedded forge-global/SKILL.md: %v", err)
}
text := string(data)

// (1) Decision table format with Signal/Forge/Skip columns.
if !strings.Contains(text, "| Signal | Forge | Skip |") {
t.Error("forge-global: missing decision table header (Signal/Forge/Skip)")
}

// (2) All 6 original criteria present in decision table.
criteria := []string{
"File count",
"Task structure",
"Work type",
"3+ files",
"single-file change",
"parallelize",
}
for _, c := range criteria {
if !strings.Contains(text, c) {
t.Errorf("forge-global: decision table missing criterion %q", c)
}
}

// (3) Temporal ordering markers in File Reservation Protocol.
for _, marker := range []string{"FIRST,", "THEN,", "FINALLY,"} {
if !strings.Contains(text, marker) {
t.Errorf("forge-global: missing temporal marker %q in File Reservation Protocol", marker)
}
}

// (4) TTL inlined in step 1 with specific value.
if !strings.Contains(text, "ttl_seconds=300") {
t.Error("forge-global: ttl_seconds=300 must be inlined in reservation step")
}

// (5) TTL explanation parenthetical present (5-minute auto-release).
if !strings.Contains(text, "(5-minute auto-release)") {
t.Error("forge-global: missing '(5-minute auto-release)' explanation for TTL")
}

// (6) No standalone TTL bullet (old format removed).
lines := strings.Split(text, "\n")
for _, line := range lines {
trimmed := strings.TrimSpace(line)
// Old format was a standalone step like "3. Set `ttl_seconds` to auto-release..."
if strings.HasPrefix(trimmed, "3.") && strings.Contains(trimmed, "ttl_seconds") && strings.Contains(trimmed, "auto-release") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM
The test iterates all lines looking for one that starts with "3." AND contains "ttl_seconds" AND contains "auto-release". The current forge-global SKILL.md step 3 reads:

3. FINALLY, coordinator can emergency release if workers fail: comms_release_all()

This line starts with "3." but does not contain "ttl_seconds", so the triple-AND condition can never be true. The test passes trivially — it's testing for the absence of something that's already structurally impossible given how the file was rewritten. It would still pass if someone reintroduced a 4. Set ttl_seconds... step (the old format used step 3, but a future author might use step 4). The assertion is coupled to the old numbering, not to the actual invariant ("no standalone TTL step exists").
A more robust check:

for _, line := range lines {
    trimmed := strings.TrimSpace(line)
    if strings.Contains(trimmed, "ttl_seconds") && strings.Contains(trimmed, "auto-release") && !strings.Contains(trimmed, "comms_reserve") {
        t.Error("forge-global: standalone TTL step should be removed (inlined into step 1)")
    }
}

This fires regardless of step number and regardless of whether the line is a numbered step at all.

Also
MEDIUM
The conditional at agentkit_test.go:1023:

if strings.HasPrefix(trimmed, "3.") && strings.Contains(trimmed, "ttl_seconds") && strings.Contains(trimmed, "auto-release") {

Tracing the input: text comes from content.ReadFile("content/skills/forge-global/SKILL.md"). The file's line starting with "3." is "3. FINALLY, coordinator can emergency release if workers fail: comms_release_all()". This line does not contain "ttl_seconds", so the branch body (the t.Error call) is dead code. The test would need a future regression that coincidentally uses "3." as the step number AND reintroduces both "ttl_seconds" and "auto-release" in the same line to ever trigger.

t.Error("forge-global: standalone TTL step should be removed (inlined into step 1)")
}
}
}

func TestSkillFiles_DriftDetection(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM
The function name TestSkillFiles_DriftDetection uses the plural generic "SkillFiles" (not "TwoSkillFiles" or "HardenedSkillFiles"). A new contributor reading this name would expect it to cover all embedded skill files. The skills slice at line 1037-1040 hardcodes only ["always-on-guidance", "forge-global"]. Meanwhile TestForgeCoordinationSkill_StructuralHardening separately covers forge-coordination. The remaining 4 skills (replicator-cli, testing-patterns, system-design, learning-systems) have no drift detection.
The doc comment (line 1030-1031) says "Verify embedded skill files match the .opencode/ scaffolded copies" — again generic, not qualified to a subset.
Either the name should be scoped (TestHardenedSkillFiles_DriftDetection) or the implementation should cover all skills.

Also
LOW
agentkit_test.go:894-904 already verifies byte-for-byte identity of the forge-coordination embedded vs .opencode/ copies. The new TestSkillFiles_DriftDetection at line 1029 covers always-on-guidance and forge-global but does not include forge-coordination — so the repo now has two independent drift-detection mechanisms that cover disjoint skill subsets. Neither covers all 7 skills. This isn't a bug, but the name TestSkillFiles_DriftDetection implies comprehensive drift detection for skill files generically. Anyone extending it to all 7 skills would introduce a duplicate assertion for forge-coordination.

// TC-007: Verify embedded skill files match the .opencode/ scaffolded copies.
// This detects drift where one copy is updated but the other is not.
//
// Find the repo root by walking up from the test working directory
// until we find go.mod.
repoRoot := findRepoRoot(t)

skills := []string{
"always-on-guidance",
"forge-global",
}

for _, skill := range skills {
embeddedPath := filepath.Join("content", "skills", skill, "SKILL.md")
embedded, err := content.ReadFile(embeddedPath)
if err != nil {
t.Fatalf("read embedded %s: %v", embeddedPath, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agentkit_test.go:1046 and 1052: Both ReadFile error handlers use t.Fatalf, which stops the entire test on the first skill that fails to read. If both skills have issues, only the first is reported. For a 2-element loop this is marginal, but t.Errorf + continue would be more informative. The drift comparison itself at line 1055 correctly uses t.Errorf.

}

scaffoldedPath := filepath.Join(repoRoot, ".opencode", "skills", skill, "SKILL.md")
scaffolded, err := os.ReadFile(scaffoldedPath)
if err != nil {
t.Fatalf("read scaffolded %s: %v", scaffoldedPath, err)
}

if string(embedded) != string(scaffolded) {
t.Errorf("drift detected: embedded %s differs from scaffolded .opencode/skills/%s/SKILL.md", embeddedPath, skill)
}
}
}



func TestSkillTemplates_HaveNameField(t *testing.T) {
// Walk the embedded content filesystem and verify every SKILL.md
// has a "name: <directory-name>" field in its YAML frontmatter.
Expand Down
20 changes: 16 additions & 4 deletions internal/agentkit/content/skills/always-on-guidance/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,52 @@ tags: [always-on, coding, quality]

Rules that apply to every coding session.

## Critical Safety
Comment thread
jflowers marked this conversation as resolved.

- NEVER force push to main

## Tool Usage Discipline

- Check `hivemind_find` before solving problems from scratch
- Read files before editing — never guess at content
- Use `org_*` tools for work item management
- Use `comms_*` tools for agent messaging and file reservations
- Use `forge_*` tools for multi-agent coordination
- Use `hivemind_*` tools for learning storage and retrieval
- Check `hivemind_find` before solving problems from scratch

## Code Quality

### Structure
- Functions do one thing well
- No dead code or unused imports

### Clarity
- Names reveal intent — no abbreviations
- Comments explain *why*, not *what*
- No dead code or unused imports
- Error messages include context

## Testing

- Write tests for all new code
### Test Infrastructure
- Use `db.OpenMemory()` for database tests
- Use `t.TempDir()` for filesystem tests
- Standard library `testing` package only — no testify

### Test Practice
- Write tests for all new code
- Test names: `TestXxx_Description`

## Error Handling

### Error Propagation
- Return errors, don't panic
- Wrap errors with context: `fmt.Errorf("operation: %w", err)`

### Error Coverage
- Handle all error paths — no ignored returns
- Use `errors.Is` for sentinel error checks

## Git Discipline

- Conventional commits: `type: description`
- Never force push to main
- Commit early, commit often
22 changes: 8 additions & 14 deletions internal/agentkit/content/skills/forge-global/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,17 @@ Patterns for forge coordination that apply across projects.

## When to Forge

Use a forge when:
- Task touches 3+ files
- Task has independent subtasks that can parallelize
- Task benefits from specialized workers (e.g., tests vs implementation)

Don't forge when:
- Task is a single-file change
- Task requires sequential steps with tight coupling
- Task is exploratory or investigative
| Signal | Forge | Skip |
|--------|-------|------|
| File count | Task touches 3+ files | Task is a single-file change |
| Task structure | Independent subtasks that can parallelize | Sequential steps with tight coupling |
| Work type | Benefits from specialized workers (e.g., tests vs implementation) | Exploratory or investigative work |

## File Reservation Protocol

1. Workers MUST call `comms_reserve(paths=[...])` before editing
2. Reservations are exclusive by default
3. Set `ttl_seconds` to auto-release after timeout
4. Always release when done: `comms_release(paths=[...])`
5. Coordinator can emergency release: `comms_release_all()`
1. FIRST, workers MUST call `comms_reserve(paths=[...], ttl_seconds=300)` before editing any files (5-minute auto-release) — reservations are exclusive by default
2. THEN, always release when done: `comms_release(paths=[...])`
3. FINALLY, coordinator can emergency release if workers fail: `comms_release_all()`

## Worker Spawning

Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/harden-skill-compression/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: unbound-force
created: 2026-08-02
Loading