Skip to content

Commit b28430d

Browse files
committed
docs: improve CCG skill routing and contracts
1 parent ec7252f commit b28430d

7 files changed

Lines changed: 327 additions & 126 deletions

File tree

internal/adapters/inbound/cli/skills_contract_test.go

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,17 @@ func TestProjectSkillsAvoidKnownMisleadingContracts(t *testing.T) {
176176
"do not overwrite existing annotations",
177177
"requires `ccg build .` first",
178178
"refresh flows, communities, or fts",
179+
"unknown tags are reported by the parser",
179180
}
180-
paths, err := filepath.Glob(filepath.Join("..", "..", "..", "..", "skills", "*", "SKILL.md"))
181+
paths, err := filepath.Glob(filepath.Join("..", "..", "..", "..", "skills", "*", "**", "*.md"))
181182
if err != nil {
182183
t.Fatal(err)
183184
}
185+
topLevelPaths, err := filepath.Glob(filepath.Join("..", "..", "..", "..", "skills", "*", "SKILL.md"))
186+
if err != nil {
187+
t.Fatal(err)
188+
}
189+
paths = append(paths, topLevelPaths...)
184190
for _, path := range paths {
185191
raw, err := os.ReadFile(path)
186192
if err != nil {
@@ -195,6 +201,135 @@ func TestProjectSkillsAvoidKnownMisleadingContracts(t *testing.T) {
195201
}
196202
}
197203

204+
func TestProjectSkillsCoverOperationalHazards(t *testing.T) {
205+
skillsRoot := filepath.Join("..", "..", "..", "..", "skills")
206+
required := map[string][]string{
207+
"ccg": {
208+
"`replace=false`",
209+
"out-of-scope",
210+
},
211+
"ccg-analyze": {
212+
"`max_depth`",
213+
"`truncated`",
214+
"server-visible",
215+
},
216+
"ccg-annotate": {
217+
"ingestion discards",
218+
"unknown-tag",
219+
},
220+
"ccg-docs": {
221+
"`rag.index_dir`",
222+
"does not make those files readable",
223+
},
224+
"ccg-namespace": {
225+
"single-namespace",
226+
},
227+
}
228+
229+
for name, phrases := range required {
230+
t.Run(name, func(t *testing.T) {
231+
raw, err := os.ReadFile(filepath.Join(skillsRoot, name, "SKILL.md"))
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
text := strings.ToLower(string(raw))
236+
for _, phrase := range phrases {
237+
if !strings.Contains(text, strings.ToLower(phrase)) {
238+
t.Errorf("missing operational contract %q", phrase)
239+
}
240+
}
241+
})
242+
}
243+
}
244+
245+
func TestProjectSkillsCentralizeSharedOperationalGuidance(t *testing.T) {
246+
skillsRoot := filepath.Join("..", "..", "..", "..", "skills")
247+
required := map[string][]string{
248+
"ccg": {
249+
"## Task Routing and Entry",
250+
"## Graph Freshness",
251+
"## Scoped Update Safety",
252+
"## Response Budget Rule",
253+
},
254+
"ccg-analyze": {
255+
"`ccg` skill's Response Budget Rule",
256+
},
257+
"ccg-annotate": {
258+
"`ccg` skill's Graph Freshness workflow",
259+
},
260+
"ccg-docs": {
261+
"`ccg` skill's Graph Freshness workflow",
262+
},
263+
"ccg-namespace": {
264+
"`ccg` skill's Scoped Update Safety",
265+
},
266+
}
267+
forbidden := map[string][]string{
268+
"ccg-analyze": {
269+
"## Pagination Defaults",
270+
},
271+
"ccg-namespace": {
272+
"## Safe Scoped Updates",
273+
"`replace=false`",
274+
},
275+
}
276+
277+
for name, phrases := range required {
278+
t.Run(name, func(t *testing.T) {
279+
raw, err := os.ReadFile(filepath.Join(skillsRoot, name, "SKILL.md"))
280+
if err != nil {
281+
t.Fatal(err)
282+
}
283+
text := string(raw)
284+
for _, phrase := range phrases {
285+
if !strings.Contains(text, phrase) {
286+
t.Errorf("missing centralized-guidance contract %q", phrase)
287+
}
288+
}
289+
for _, phrase := range forbidden[name] {
290+
if strings.Contains(text, phrase) {
291+
t.Errorf("duplicates core-owned guidance %q", phrase)
292+
}
293+
}
294+
})
295+
}
296+
}
297+
298+
func TestProjectSkillsRoutePipelineAnalysisBeforeSourceVerification(t *testing.T) {
299+
skillsRoot := filepath.Join("..", "..", "..", "..", "skills")
300+
required := map[string][]string{
301+
"ccg": {
302+
"algorithm",
303+
"feature pipeline",
304+
"graph-first",
305+
},
306+
"ccg-analyze": {
307+
"pipeline analysis workflow",
308+
"candidate discovery",
309+
"symbol identity",
310+
"relationship and structure evidence",
311+
"call-chain evidence",
312+
"runtime semantics",
313+
"does not prove runtime order",
314+
},
315+
}
316+
317+
for name, phrases := range required {
318+
t.Run(name, func(t *testing.T) {
319+
raw, err := os.ReadFile(filepath.Join(skillsRoot, name, "SKILL.md"))
320+
if err != nil {
321+
t.Fatal(err)
322+
}
323+
text := strings.ToLower(strings.Join(strings.Fields(string(raw)), " "))
324+
for _, phrase := range phrases {
325+
if !strings.Contains(text, phrase) {
326+
t.Errorf("missing pipeline-analysis contract %q", phrase)
327+
}
328+
}
329+
})
330+
}
331+
}
332+
198333
func parseSkillContract(t *testing.T, raw []byte) skillContract {
199334
t.Helper()
200335
text := string(raw)

skills/ccg-analyze/SKILL.md

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
name: ccg-analyze
3-
description: "Analyze code relationships with CCG impact radius, flow tracing, callers/callees, git-diff risk, and affected stored flows. Use when a task asks what a change affects, how a call path flows, who calls a symbol, or which flows recent changes touch. Do not use for simple text lookup, documentation generation, or annotation authoring."
3+
description: "Analyze algorithms, feature pipelines, and code relationships with CCG impact radius, bounded flow tracing, callers/callees, git-diff risk, affected stored flows, and cross-namespace references. Use when a task asks how a feature or algorithm works, how a pipeline flows, what a change affects, who calls a symbol, whether results were truncated, or which flows recent changes touch. Do not use for simple text lookup, documentation generation, or annotation authoring."
44
metadata:
5-
version: 1.1.0
5+
version: 1.3.1
66
openclaw:
77
category: "code-intelligence"
88
domain: "analysis"
@@ -21,14 +21,33 @@ Graph-based analysis for **change impact, call flow, and recent-change risk**.
2121

2222
| User intent | Tool | Notes |
2323
| ------------------------------------ | ------------------------------------------------ | --------------------------------------------- |
24-
| "Impact of changing this function?" | `get_impact_radius` (start with depth 3) | Widen to depth 5 if too narrow |
24+
| "Impact of changing this function?" | `get_impact_radius` (`depth=3`, `max_depth=3`) | Raise both bounds to widen |
25+
| "How does this algorithm or feature pipeline work?" | Pipeline workflow below | Graph-first, then source-verified |
2526
| "Trace call flow from this function" | `trace_flow` | If unexpectedly thin, verify the causes below |
2627
| "Who calls this function?" | `query_graph` (callers_of) | |
2728
| "What does this function call?" | `query_graph` (callees_of) | |
2829
| "Risk of this change" | `detect_changes` + `get_affected_flows` | git diff-based |
2930
| "Which repos depend on this one?" | `list_cross_refs` (direction inbound) | Annotation `ccg://` refs, materialized |
3031
| "Impact across repos?" | `get_impact_radius` with `cross_namespace: true` | Crosses resolved `ccg://` refs both ways |
3132

33+
## Pipeline Analysis Workflow
34+
35+
1. **Candidate discovery**: use `search_docs` for a broad module question or
36+
`search` for focused symbol and annotation candidates.
37+
2. **Symbol identity**: confirm each entry point or major stage with `get_node`;
38+
continue with qualified names rather than display labels.
39+
3. **Relationship and structure evidence**: use `query_graph` with
40+
`callers_of`/`callees_of` for direct call relations, `children_of` for
41+
ownership, and `file_summary` for file composition. This evidence does not
42+
prove runtime order.
43+
4. **Call-chain evidence**: use `trace_flow` from a verified entry point and
44+
inspect truncation plus fallback-edge metadata. Treat the result as a bounded
45+
static chain, not a runtime trace.
46+
5. **Runtime semantics**: read the narrowed source files to verify branch
47+
conditions, loops, callbacks, data transformations, error paths, and actual
48+
ordering. If graph and source disagree, report staleness, unresolved dynamic
49+
behavior, or the remaining uncertainty instead of silently choosing one.
50+
3251
## Thin `trace_flow` Results
3352

3453
One or two returned nodes do not prove an interface-dispatch failure. The start
@@ -48,30 +67,27 @@ Verify in this order:
4867
Report which explanation is supported; do not label a thin trace as an
4968
interface boundary without source or edge evidence.
5069

51-
## get_impact_radius Tips
70+
## `get_impact_radius` Bounds
5271

53-
- **depth 1–2**: direct impact (immediate callers/callees)
54-
- **depth 3**: recommended default — covers most tasks
55-
- **depth 5+**: large monorepo propagation. Watch for noise.
72+
- `depth` requests the BFS hop count; its default is 1.
73+
- `max_depth` caps `depth`; its default is 3. Setting only `depth=5` still
74+
returns at most three hops, so raise both values when widening.
75+
- Start with `depth=3`, `max_depth=3`, and a bounded `max_nodes`.
76+
- Inspect response metadata `truncated`, `max_depth`, `max_nodes`, and
77+
`returned_nodes` before interpreting the radius as complete.
5678

5779
If results are huge, narrow by namespace, starting symbol, depth, or edge mode
5880
before concluding the implementation change itself is too broad. High-fanout
5981
entry points can legitimately have a large radius.
6082

61-
## Pagination Defaults
62-
63-
Use explicit budgets for graph browsing tools when the namespace may be large:
83+
## Analysis Result Bounds
6484

65-
| Tool | Parameters | Default starting point |
66-
| ---- | ---------- | ---------------------- |
67-
| `query_graph` | `limit`, `offset` | `limit=50`, `offset=0` |
68-
| `list_flows` | `limit`, `offset` | `limit=50`, `offset=0` |
69-
| `detect_changes` | `limit`, `offset` | `limit=50`, `offset=0` |
70-
| `get_affected_flows` | `limit`, `offset` | `limit=50`, `offset=0` |
85+
Use the `ccg` skill's Response Budget Rule for paginated graph tools. Preserve
86+
per-page namespace labels and errors when accumulating federated results.
7187

72-
Paginated responses include `has_more`. If true, call again with `next_offset`.
73-
Do not request the max page size first for LLM analysis; use 50 or 100 unless
74-
the user specifically needs a bulk export.
88+
`get_impact_radius` and `trace_flow` are bounded rather than paginated. Follow
89+
their `truncated` metadata by narrowing the start/scope or deliberately raising
90+
`max_nodes`; do not call a truncated response complete.
7591

7692
## Accuracy Limits (use with awareness)
7793

@@ -80,13 +96,17 @@ the user specifically needs a bulk export.
8096
- Build-tag-split files → both registered (noise)
8197
- Fallback call edges improve recall but may add false positives; use strict mode when evidence quality matters more than coverage
8298
- Treat graph results as a static approximation; cross-check important conclusions against source.
99+
- `repo_root` for `detect_changes` and `get_affected_flows` must be a
100+
server-visible, allowed path. If the MCP server cannot see the client path,
101+
report that constraint and use local git/source evidence instead.
83102

84103
## Boundary
85104

86105
- Start from a verified qualified name; do not infer a symbol from a display label alone.
87106
- Scope namespace, path, traversal depth, and result limits before widening a query.
88107
- Separate strict call edges from fallback edges when evidence quality matters.
89108
- Do not treat missing graph edges as proof that runtime behavior is impossible.
109+
- Do not hide per-namespace errors or truncation when federated/cross-namespace evidence is partial.
90110

91111
## Analysis MCP Tools
92112

@@ -103,12 +123,14 @@ For detailed parameters, see MCP schema.
103123

104124
## Prerequisites
105125

106-
Confirm that the selected namespace contains a current graph. Build only when
107-
the graph is missing or a full rebuild is intentional; after ordinary code
108-
changes, prefer `ccg update .`. Stored-flow tools also require flow
109-
postprocessing; an empty flow list is not evidence of no flow until that state
110-
has been checked. Use the `ccg` skill for freshness and postprocessing guidance.
126+
Use the `ccg` skill's Graph Freshness workflow before interpreting graph or
127+
stored-flow results. Stored-flow tools require flow postprocessing; an empty
128+
flow list is not evidence of no flow until that state has been checked.
111129

112130
## Completion
113131

114-
Report the analyzed qualified name, namespace, depth/limits, included edge modes, returned impact or flow evidence, and any source-level cross-check used for an important conclusion.
132+
Report the analyzed qualified names, namespace, discovery query, `query_graph`
133+
patterns, `trace_flow` bounds and truncation, included edge modes,
134+
fallback-edge counts, source files used to verify runtime semantics, any
135+
graph/source disagreement or server-visible path limitation, and the evidence
136+
supporting important conclusions.

skills/ccg-annotate/SKILL.md

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
name: ccg-annotate
3-
description: "Author and refine CCG annotations such as @intent, @domainRule, @sideEffect, @mutates, @index, and @see. Use when adding business meaning to code, improving annotation-aware code or documentation retrieval, fixing annotation lint findings, or documenting operational contracts. Do not use for generated Markdown editing or annotations that merely restate symbol names."
3+
description: "Author, refine, and verify CCG annotations such as @intent, @domainRule, @sideEffect, @mutates, @index, and @see. Use when adding business meaning to code, improving annotation-aware code or documentation retrieval, fixing annotation lint findings, checking supported tag syntax, or documenting operational contracts. Do not use for generated Markdown editing or annotations that merely restate symbol names."
44
metadata:
5-
version: 1.1.0
5+
version: 1.2.1
66
openclaw:
77
category: "code-intelligence"
88
domain: "annotation"
@@ -18,24 +18,6 @@ metadata:
1818
Add structured business metadata to source comments so graph search and
1919
generated documentation can retrieve intent and operational contracts.
2020

21-
## Why Annotations Matter
22-
23-
- Lets full-text retrieval match domain terms recorded in annotations even when symbol names differ
24-
- Enriches generated docs and DB-backed documentation evidence
25-
26-
## Core Tags
27-
28-
| Tag | WHAT vs WHY | Example |
29-
| ------------- | ----------------------------------- | ---------------------------------------- |
30-
| `@intent` | **WHY this function exists** | `verify identity before granting access` |
31-
| `@domainRule` | Specific business rule | `lock account after 5 failures` |
32-
| `@sideEffect` | Real side effects (DB/network/file) | `writes to audit_log` |
33-
| `@mutates` | Receiver or argument state changes | `user.FailedAttempts, session.Token` |
34-
| `@requires` | Precondition | `user.IsActive == true` |
35-
| `@ensures` | Postcondition | `returns valid JWT with 24h expiry` |
36-
| `@index` | One-line file/package summary | `User authentication service` |
37-
| `@see` | Related function or CCG ref | `SessionManager.Create`, `ccg://auth-svc/internal/auth/token.go#ValidateToken` |
38-
3921
## Retrieval-Aware Tag Selection
4022

4123
Annotations are retrieval features, but they must stay truthful. Use the
@@ -59,8 +41,8 @@ and `node focus` in the appropriate `@index`/`@intent`. Do not add unrelated
5941
terms just to raise score; broad terms make the wrong files rank higher.
6042

6143
Read [`references/annotation-reference.md`](references/annotation-reference.md)
62-
when checking the complete tag contract, aliases, multiline behavior, or
63-
language-specific comment syntax.
44+
before using less-common tags, aliases, multiline values, typed JSDoc/YARD
45+
forms, cross-namespace refs, or language-specific comment syntax.
6446

6547
## Annotation Workflow
6648

@@ -105,15 +87,16 @@ For cross-namespace behavior, explain the reason in the semantic tag and put the
10587

10688
### Step 4: Refresh and verify
10789

108-
```bash
109-
ccg update . # ordinary source edits: re-index changed files
110-
ccg lint # verify annotation quality and references
111-
```
90+
Use the `ccg` skill's Graph Freshness workflow to re-index changed source, then
91+
run `ccg lint` to verify annotation quality and references.
11292

113-
Use `ccg build .` instead when the graph does not exist, a full rebuild is
114-
intentional, or incremental recovery is required.
93+
For representative changed symbols, call `get_annotation` and confirm the
94+
expected tags and values were actually stored. The parser returns unknown-tag
95+
warnings to direct callers, but the current ingestion discards that warning
96+
list and `ccg lint` does not surface it. Treat the reference tag list as an
97+
allowlist; a green lint result alone does not prove an unknown tag was indexed.
11598

116-
## Quality Rules (this is what really matters)
99+
## Quality Example
117100

118101
**Bad annotation**:
119102

@@ -164,11 +147,12 @@ expected file is missing, prefer improving the precise `@index`, `@intent`,
164147

165148
| Tool | Use |
166149
| ---------------- | ------------------------------------ |
167-
| `get_annotation` | Fetch annotation/doc tags for a node |
150+
| `get_annotation` | Verify stored annotation/doc tags for a qualified node |
168151

169152
## Completion
170153

171154
List annotated files and meaningful tags added or deliberately revised, report
172-
any existing rationale changed, refresh the graph with update or build as
173-
appropriate, run retrieval probes for the affected concepts, and report
174-
`ccg lint` results without claiming unrelated findings were fixed.
155+
any existing rationale changed, apply the `ccg` Graph Freshness workflow,
156+
verify representative nodes with `get_annotation`, run retrieval probes for
157+
affected concepts, and report lint plus any unknown-tag risk or unrelated
158+
finding without claiming it was fixed.

skills/ccg-annotate/references/annotation-reference.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,8 @@ Text before the first tag becomes the summary and context paragraphs. A tag valu
6666
// @return the authorization decision and audit identifier
6767
```
6868

69-
Unknown tags are reported by the parser and are not stored. Keep annotations truthful and omit tags that merely repeat the declaration name.
69+
Unknown tags are not stored. Direct parser callers receive their names as
70+
warnings, but the normal ingestion binding currently discards those warnings,
71+
and `ccg lint` does not report them. Use the table above as an allowlist, verify
72+
representative stored tags with `get_annotation`, and omit tags that merely
73+
repeat the declaration name.

0 commit comments

Comments
 (0)