From b3588a1b888313e7b7780b3ff9f6d583333db564 Mon Sep 17 00:00:00 2001 From: Jay Flowers Date: Sun, 2 Aug 2026 16:34:22 -0400 Subject: [PATCH 1/3] fix: harden forge-global and always-on-guidance skills for compression resilience Restructure two embedded skill files to improve survival under DCP context compression, addressing six specific vulnerabilities (#51): - Replace parallel do/don't lists with decision table in forge-global - Inline TTL parameter into reservation step, add temporal ordering - Add Critical Safety section in always-on-guidance for safety rules - Move hivemind_find from last to first position in Tool Usage - Split Code Quality, Testing, Error Handling into sub-headed groups - Sync .opencode/skills/ copies with canonical embedded source No semantic changes to any rules -- structural reorganization only. Closes #51 Assisted-by: claude-opus Generated with AI assistance (claude-opus) --- .opencode/skills/always-on-guidance/SKILL.md | 20 ++++- .opencode/skills/forge-global/SKILL.md | 22 ++--- .../skills/always-on-guidance/SKILL.md | 20 ++++- .../content/skills/forge-global/SKILL.md | 22 ++--- .../harden-skill-compression/.openspec.yaml | 2 + .../harden-skill-compression/design.md | 61 +++++++++++++ .../harden-skill-compression/proposal.md | 71 +++++++++++++++ .../specs/compression-resilience.md | 88 +++++++++++++++++++ .../changes/harden-skill-compression/tasks.md | 46 ++++++++++ 9 files changed, 316 insertions(+), 36 deletions(-) create mode 100644 openspec/changes/harden-skill-compression/.openspec.yaml create mode 100644 openspec/changes/harden-skill-compression/design.md create mode 100644 openspec/changes/harden-skill-compression/proposal.md create mode 100644 openspec/changes/harden-skill-compression/specs/compression-resilience.md create mode 100644 openspec/changes/harden-skill-compression/tasks.md diff --git a/.opencode/skills/always-on-guidance/SKILL.md b/.opencode/skills/always-on-guidance/SKILL.md index 70ed48e..79a7418 100644 --- a/.opencode/skills/always-on-guidance/SKILL.md +++ b/.opencode/skills/always-on-guidance/SKILL.md @@ -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 diff --git a/.opencode/skills/forge-global/SKILL.md b/.opencode/skills/forge-global/SKILL.md index f6d0dbe..f19b4d8 100644 --- a/.opencode/skills/forge-global/SKILL.md +++ b/.opencode/skills/forge-global/SKILL.md @@ -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 — 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 diff --git a/internal/agentkit/content/skills/always-on-guidance/SKILL.md b/internal/agentkit/content/skills/always-on-guidance/SKILL.md index 70ed48e..79a7418 100644 --- a/internal/agentkit/content/skills/always-on-guidance/SKILL.md +++ b/internal/agentkit/content/skills/always-on-guidance/SKILL.md @@ -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 diff --git a/internal/agentkit/content/skills/forge-global/SKILL.md b/internal/agentkit/content/skills/forge-global/SKILL.md index f6d0dbe..f19b4d8 100644 --- a/internal/agentkit/content/skills/forge-global/SKILL.md +++ b/internal/agentkit/content/skills/forge-global/SKILL.md @@ -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 — 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 diff --git a/openspec/changes/harden-skill-compression/.openspec.yaml b/openspec/changes/harden-skill-compression/.openspec.yaml new file mode 100644 index 0000000..63fe932 --- /dev/null +++ b/openspec/changes/harden-skill-compression/.openspec.yaml @@ -0,0 +1,2 @@ +schema: unbound-force +created: 2026-08-02 diff --git a/openspec/changes/harden-skill-compression/design.md b/openspec/changes/harden-skill-compression/design.md new file mode 100644 index 0000000..bfcc5d8 --- /dev/null +++ b/openspec/changes/harden-skill-compression/design.md @@ -0,0 +1,61 @@ +## Context + +Two embedded skill files -- `forge-global` and `always-on-guidance` -- use flat bullet lists and parallel "do/don't" structures that DCP context compression is known to weaken. The issue ([#51](https://github.com/unbound-force/replicator/issues/51)) documents six specific vulnerabilities where critical constraints sit in low-survival positions. + +The proposal (constitution alignment: all PASS/N/A) calls for structural reorganization without semantic changes to any rule. + +## Goals / Non-Goals + +### Goals +- Restructure `forge-global` to use compression-resilient patterns (decision table, inline parameters, ordered steps) +- Restructure `always-on-guidance` to use compression-resilient patterns (priority positioning, dedicated safety section, smaller lists) +- Preserve exact semantic meaning of all existing rules +- Keep both files concise -- compression resilience should not mean verbosity + +### Non-Goals +- Adding new rules or behavioral constraints +- Changing the skill loading mechanism or embedded asset pipeline +- Hardening other skills beyond the two identified in #51 +- Automated compression-survival testing (out of scope for this change) + +## Decisions + +### D1: Decision table replaces opposing lists in forge-global + +The "When to Forge / Don't forge" parallel lists will be replaced with a single decision table using columns for criteria, "forge" action, and "don't forge" action. Decision tables survive compression better because they are a single coherent structure rather than two lists that a compressor might flatten independently. + +Format: markdown table with Signal / Forge / Skip columns. + +### D2: TTL inlined into reservation step, not standalone bullet + +Line 27 (`Set ttl_seconds to auto-release after timeout`) is a standalone bullet that compression drops. It will be inlined into step 1 of the protocol: `Workers MUST call comms_reserve(paths=[...], ttl_seconds=300) before editing`. The specific parameter becomes part of the action rather than a separate detail. + +### D3: Protocol steps get explicit ordering language + +The 5-step File Reservation Protocol will use explicit temporal markers ("FIRST", "THEN", "FINALLY") in addition to numbered steps. Ordered temporal language survives compression better than bare numbered lists because the ordering semantics are embedded in each item rather than implied by position. + +### D4: hivemind_find moves to first position + +"Check hivemind_find before solving problems from scratch" moves from last bullet (position 6, lowest survival) to first bullet (position 1, highest survival) in Tool Usage. First-position items have the highest compression survival rate. + +### D5: Critical safety rules get dedicated section + +"Never force push to main" is currently a mid-list bullet in Git Discipline, weighted equally with stylistic guidance. It moves to a new `## Critical Safety` section at the top of the file (after the title), giving it header-level protection. Compressors preserve section headers even when they drop individual bullets. + +### D6: Lists reduced to 2-3 items via sub-headers + +The current 5-item Code Quality list, 4-item Error Handling list, and 5-item Testing list will be reorganized under sub-headers to create groups of 2-3 items. Shorter lists lose fewer items under compression because the ratio of dropped items is lower. The 3-item Git Discipline section (which will have 2 items after D5 extracts the safety rule) is already within the threshold and does not need splitting. + +## Risks / Trade-offs + +### Risk: Structural changes misinterpreted as semantic changes + +**Mitigation**: Each restructured section preserves the exact same rules. Review should verify no rules are added, removed, or weakened. + +### Risk: Decision table format less scannable than bullet lists + +**Accepted trade-off**: Decision tables require slightly more cognitive load to parse but survive compression significantly better. The table is small (3 rows) and the trade-off favors reliability over scanability. + +### Risk: Longer file from sub-headers + +**Accepted trade-off**: Adding sub-headers increases line count slightly but each list becomes shorter. The net effect on token count is minimal, and shorter lists have better compression survival. diff --git a/openspec/changes/harden-skill-compression/proposal.md b/openspec/changes/harden-skill-compression/proposal.md new file mode 100644 index 0000000..f0c0784 --- /dev/null +++ b/openspec/changes/harden-skill-compression/proposal.md @@ -0,0 +1,71 @@ +## Why + +Two deployed skills -- `forge-global` and `always-on-guidance` -- contain behavioral constraints structured in ways that DCP context compression is likely to weaken or drop entirely. Critical decision logic (conditional "when to / when not to" rules), safety constraints ("never force push to main"), and specific operational parameters (TTL settings) sit in list positions with low compression survival rates. + +When these constraints are lost, agents make worse decisions: inappropriate parallelization of tightly-coupled tasks, hanging file reservations, re-solving already-solved problems, and potentially force-pushing to main. This is a quality and safety issue that affects every agent session loading these skills. + +Tracked by [unbound-force/replicator#51](https://github.com/unbound-force/replicator/issues/51). Related to [unbound-force/unbound-force#346](https://github.com/unbound-force/unbound-force/issues/346). + +## What Changes + +Restructure the content of two embedded skill files to improve survival under DCP context compression, without changing the semantic meaning of any rule. + +### forge-global (`internal/agentkit/content/skills/forge-global/SKILL.md`) + +1. Replace parallel "When to / Don't" lists with a decision table -- decision structures survive compression better than opposing lists +2. Inline TTL guidance into the reservation step rather than a standalone bullet +3. Add explicit ordering markers to the File Reservation Protocol steps + +### always-on-guidance (`internal/agentkit/content/skills/always-on-guidance/SKILL.md`) + +1. Move "Check `hivemind_find` first" from last position to first position in Tool Usage (first-position = highest survival) +2. Separate critical safety rules into a dedicated `## Critical Safety` section with its own header +3. Reduce list sizes by grouping related rules under sub-headers (2-3 items per list instead of 5-6) + +## Capabilities + +### New Capabilities +- None + +### Modified Capabilities +- `forge-global skill`: Restructured for compression resilience -- decision table replaces parallel lists, protocol steps gain ordering language, TTL guidance inlined +- `always-on-guidance skill`: Restructured for compression resilience -- safety rules elevated to dedicated section, list sizes reduced, critical items repositioned + +### Removed Capabilities +- None + +## Impact + +- **Files**: `internal/agentkit/content/skills/forge-global/SKILL.md`, `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- **Embedded assets**: These skills are embedded via `go:embed` and distributed in the replicator binary +- **Agent behavior**: No semantic changes to any rules -- only structural reorganization for compression resilience +- **Tests**: Existing tests in `internal/agentkit/agentkit_test.go` (specifically `TestScaffold_FreshDirectory`, `TestScaffold_FileCount`, and `TestSkillTemplates_HaveNameField`) verify embedded skill file existence, count, and frontmatter validity. These serve as the regression baseline. Parity tests are unaffected (skills are not covered by parity tests). +- **Documentation**: No updates required to AGENTS.md or README.md -- this change is structural reorganization of existing embedded content with no user-facing behavior change. + +## Constitution Alignment + +Assessed against the Replicator project constitution (`.specify/memory/constitution.md`), which extends the Unbound Force org constitution v1.1.0. + +### I. Autonomous Collaboration + +**Assessment**: N/A + +This change modifies skill file structure only. It does not affect MCP tool interfaces, JSON outputs, or inter-agent communication via comms. Artifacts remain self-describing. + +### II. Composability First + +**Assessment**: PASS + +Skills are embedded in the standalone binary. This change does not introduce any new dependencies or external service requirements. The binary continues to work alone. + +### III. Observable Quality + +**Assessment**: PASS + +The restructured skills maintain all existing quality rules and behavioral constraints. No tool response shapes change. The change improves quality by ensuring constraints survive compression to actually reach agents. + +### IV. Testability + +**Assessment**: PASS + +Existing tests in `internal/agentkit/agentkit_test.go` verify embedded skill file existence, count, and frontmatter validity. These tests serve as the regression baseline. `make build` verifies `go:embed` compilation. `make test` runs the full suite including agentkit tests. No new test infrastructure is required because the change modifies content within existing embedded files, not the embedding mechanism itself. diff --git a/openspec/changes/harden-skill-compression/specs/compression-resilience.md b/openspec/changes/harden-skill-compression/specs/compression-resilience.md new file mode 100644 index 0000000..f043f27 --- /dev/null +++ b/openspec/changes/harden-skill-compression/specs/compression-resilience.md @@ -0,0 +1,88 @@ +## ADDED Requirements + +### Requirement: Critical Safety Section in always-on-guidance + +The `always-on-guidance` skill MUST contain a dedicated `## Critical Safety` section positioned before all other rule sections. Safety-critical constraints (e.g., "Never force push to main") MUST appear in this section, not intermixed with stylistic guidance. + +#### Scenario: Safety section positioned first +- **GIVEN** the restructured always-on-guidance skill file +- **WHEN** the file content is inspected +- **THEN** the `## Critical Safety` section MUST appear before all other rule sections in the file + +_Compression rationale: header-level sections with first-position placement have the highest compression survival rate._ + +--- + +### Requirement: Decision Table Format for Forge Criteria + +The `forge-global` skill MUST present forge/skip criteria in a single decision table rather than parallel "do/don't" lists. The table MUST include columns for the evaluation signal, the forge action, and the skip action. The table MUST cover all criteria from the original two lists (3 forge criteria, 3 skip criteria). + +#### Scenario: Decision table replaces parallel lists +- **GIVEN** the restructured forge-global skill file +- **WHEN** the file content is inspected +- **THEN** the forge/skip criteria MUST be formatted as a markdown table with Signal, Forge, and Skip columns +- **AND** all 6 original criteria (3 forge, 3 skip) MUST be present in the table + +_Compression rationale: decision tables survive as a single coherent structure rather than being split into independent fragments._ + +--- + +### Requirement: Explicit Temporal Ordering in Protocol Steps + +The File Reservation Protocol in `forge-global` MUST use explicit temporal ordering language (e.g., "FIRST", "THEN", "FINALLY") within each step, in addition to numbered step markers. + +#### Scenario: Protocol steps contain temporal markers +- **GIVEN** the restructured forge-global skill file +- **WHEN** the File Reservation Protocol steps are inspected +- **THEN** each step MUST contain an explicit temporal marker (FIRST, THEN, or FINALLY) + +_Compression rationale: temporal ordering embedded in each step is recoverable from any surviving step, not solely from list position._ + +--- + +## MODIFIED Requirements + +### Requirement: Tool Usage Priority Order in always-on-guidance + +The `hivemind_find` guidance ("Check hivemind_find before solving problems from scratch") MUST be positioned as the first item in the Tool Usage section. Previously: this item was positioned last (position 6) in the Tool Usage list. + +#### Scenario: hivemind_find is first in Tool Usage +- **GIVEN** the restructured always-on-guidance skill file +- **WHEN** the Tool Usage section items are inspected +- **THEN** the `hivemind_find` guidance MUST be the first item in the list + +_Compression rationale: first-position items have the highest compression survival rate._ + +--- + +### Requirement: TTL Parameter Inline in Reservation Protocol + +The `ttl_seconds` parameter guidance MUST be inlined into the reservation step (step 1) of the File Reservation Protocol rather than appearing as a separate standalone bullet. Previously: TTL guidance appeared as an independent step 3 ("Set ttl_seconds to auto-release after timeout"). + +#### Scenario: TTL parameter embedded in reservation step +- **GIVEN** the restructured forge-global skill file +- **WHEN** the File Reservation Protocol is inspected +- **THEN** the first step MUST include `ttl_seconds` as part of the `comms_reserve` call +- **AND** no standalone TTL bullet MUST exist as a separate step + +_Compression rationale: parameters inlined into primary actions survive because they are part of the action, not standalone details._ + +--- + +### Requirement: List Size Reduction in always-on-guidance + +Rule lists in `always-on-guidance` SHOULD contain no more than 3 items each. Lists exceeding 3 items MUST be split into sub-groups under descriptive sub-headers. This applies to Code Quality, Error Handling, and Testing sections. Previously: lists contained 4-6 items under a single section header. + +#### Scenario: Rule lists are within size limits +- **GIVEN** the restructured always-on-guidance skill file +- **WHEN** any rule section's sub-lists are inspected +- **THEN** each sub-list MUST contain no more than 3 items +- **AND** the total rule count in each section MUST equal the original count (no rules added or removed) + +_Compression rationale: shorter lists (2-3 items) lose a lower proportion of items under compression than longer lists (5-6 items)._ + +--- + +## REMOVED Requirements + +No requirements are removed. All existing rules retain their semantic meaning; only their structural presentation changes. diff --git a/openspec/changes/harden-skill-compression/tasks.md b/openspec/changes/harden-skill-compression/tasks.md new file mode 100644 index 0000000..98abf44 --- /dev/null +++ b/openspec/changes/harden-skill-compression/tasks.md @@ -0,0 +1,46 @@ + + +## 1. Harden forge-global skill + +All tasks modify the same file (`internal/agentkit/content/skills/forge-global/SKILL.md`) -- no parallel execution. + +- [x] 1.1 Replace "When to Forge / Don't forge" parallel lists with a decision table using Signal / Forge / Skip columns. Table MUST cover all 6 original criteria (3 forge, 3 skip). (design decision D1) + **Files**: `internal/agentkit/content/skills/forge-global/SKILL.md` +- [x] 1.2 Inline `ttl_seconds` parameter into reservation step 1 (`comms_reserve(paths=[...], ttl_seconds=300)`) and remove standalone TTL bullet (design decision D2) + **Files**: `internal/agentkit/content/skills/forge-global/SKILL.md` +- [x] 1.3 Add explicit temporal ordering language ("FIRST", "THEN", "FINALLY") to File Reservation Protocol steps (design decision D3) + **Files**: `internal/agentkit/content/skills/forge-global/SKILL.md` + +## 2. Harden always-on-guidance skill + +All tasks modify the same file (`internal/agentkit/content/skills/always-on-guidance/SKILL.md`) -- no parallel execution. + +- [x] 2.1 Add `## Critical Safety` section after the title, move "Never force push to main" into it (design decision D5) + **Files**: `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- [x] 2.2 Move "Check `hivemind_find` before solving problems from scratch" from last to first position in Tool Usage (design decision D4) + **Files**: `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- [x] 2.3 Split Code Quality list (5 items) into sub-grouped lists of 2-3 items under descriptive sub-headers. Total rule count MUST equal original 5. (design decision D6) + **Files**: `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- [x] 2.4 Split Error Handling list (4 items) into sub-grouped lists of 2-3 items under descriptive sub-headers. Total rule count MUST equal original 4. (design decision D6) + **Files**: `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- [x] 2.5 Split Testing list (5 items) into sub-grouped lists of 2-3 items under descriptive sub-headers. Total rule count MUST equal original 5. (design decision D6) + **Files**: `internal/agentkit/content/skills/always-on-guidance/SKILL.md` + +## 3. Verification + +- [x] 3.1 [P] Run `make build` to verify embedded skill files compile without errors +- [x] 3.2 [P] Run `make test` to verify no regressions (existing agentkit tests verify file existence, count, and frontmatter) +- [x] 3.3 Verify semantic preservation: diff before/after content of both skill files and confirm every rule present in the original appears in the restructured version with equivalent meaning. No rules added, removed, or weakened. +- [x] 3.4 Verify constitution alignment: confirm no new imports added, no Go source files modified outside `internal/agentkit/content/skills/`, and `go.sum` unchanged. Verify AGENTS.md and README.md do not require updates (skill names and descriptions unchanged). + + From ee4fea3827ab3b82c1aa5c5914f44526327aacea Mon Sep 17 00:00:00 2001 From: Jay Flowers Date: Fri, 7 Aug 2026 15:36:20 -0400 Subject: [PATCH 2/3] fix: use RFC 2119 keyword NEVER and add TTL explanation - always-on-guidance: Never -> NEVER force push to main (DR-002) - forge-global: add (5-minute auto-release) parenthetical to TTL Co-authored-by: claude-opus-4-6 --- .opencode/skills/always-on-guidance/SKILL.md | 2 +- .opencode/skills/forge-global/SKILL.md | 2 +- internal/agentkit/content/skills/always-on-guidance/SKILL.md | 2 +- internal/agentkit/content/skills/forge-global/SKILL.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.opencode/skills/always-on-guidance/SKILL.md b/.opencode/skills/always-on-guidance/SKILL.md index 79a7418..3a9ebe6 100644 --- a/.opencode/skills/always-on-guidance/SKILL.md +++ b/.opencode/skills/always-on-guidance/SKILL.md @@ -10,7 +10,7 @@ Rules that apply to every coding session. ## Critical Safety -- Never force push to main +- NEVER force push to main ## Tool Usage Discipline diff --git a/.opencode/skills/forge-global/SKILL.md b/.opencode/skills/forge-global/SKILL.md index f19b4d8..a898240 100644 --- a/.opencode/skills/forge-global/SKILL.md +++ b/.opencode/skills/forge-global/SKILL.md @@ -18,7 +18,7 @@ Patterns for forge coordination that apply across projects. ## File Reservation Protocol -1. FIRST, workers MUST call `comms_reserve(paths=[...], ttl_seconds=300)` before editing any files — reservations are exclusive by default +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()` diff --git a/internal/agentkit/content/skills/always-on-guidance/SKILL.md b/internal/agentkit/content/skills/always-on-guidance/SKILL.md index 79a7418..3a9ebe6 100644 --- a/internal/agentkit/content/skills/always-on-guidance/SKILL.md +++ b/internal/agentkit/content/skills/always-on-guidance/SKILL.md @@ -10,7 +10,7 @@ Rules that apply to every coding session. ## Critical Safety -- Never force push to main +- NEVER force push to main ## Tool Usage Discipline diff --git a/internal/agentkit/content/skills/forge-global/SKILL.md b/internal/agentkit/content/skills/forge-global/SKILL.md index f19b4d8..a898240 100644 --- a/internal/agentkit/content/skills/forge-global/SKILL.md +++ b/internal/agentkit/content/skills/forge-global/SKILL.md @@ -18,7 +18,7 @@ Patterns for forge coordination that apply across projects. ## File Reservation Protocol -1. FIRST, workers MUST call `comms_reserve(paths=[...], ttl_seconds=300)` before editing any files — reservations are exclusive by default +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()` From 0074d7cc299c77c58d233ba08c90502594977968 Mon Sep 17 00:00:00 2001 From: Jay Flowers Date: Fri, 7 Aug 2026 15:36:26 -0400 Subject: [PATCH 3/3] test: add structural hardening and drift detection tests for skills - TestAlwaysOnGuidance_StructuralHardening: verifies Critical Safety section positioning, RFC 2119 keywords, hivemind_find priority, and sub-header splits - TestForgeGlobal_StructuralHardening: verifies decision table format, temporal markers, inlined TTL, and no standalone TTL step - TestSkillFiles_DriftDetection: TC-007 drift detection comparing embedded files against .opencode/ scaffolded copies Co-authored-by: claude-opus-4-6 --- internal/agentkit/agentkit_test.go | 157 +++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/internal/agentkit/agentkit_test.go b/internal/agentkit/agentkit_test.go index aae3e30..b7ed7c6 100644 --- a/internal/agentkit/agentkit_test.go +++ b/internal/agentkit/agentkit_test.go @@ -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") @@ -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") { + t.Error("forge-global: standalone TTL step should be removed (inlined into step 1)") + } + } +} + +func TestSkillFiles_DriftDetection(t *testing.T) { + // 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) + } + + 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: " field in its YAML frontmatter.