xtrm-tools 2.4.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/cli/dist/index.cjs +738 -239
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/hooks.json +10 -0
- package/config/pi/extensions/core/adapter.ts +2 -14
- package/config/pi/extensions/core/guard-rules.ts +70 -0
- package/config/pi/extensions/core/session-state.ts +59 -0
- package/config/pi/extensions/main-guard.ts +10 -14
- package/config/pi/extensions/plan-mode/README.md +65 -0
- package/config/pi/extensions/plan-mode/index.ts +340 -0
- package/config/pi/extensions/plan-mode/utils.ts +168 -0
- package/config/pi/extensions/service-skills.ts +51 -7
- package/config/pi/extensions/session-flow.ts +117 -0
- package/hooks/beads-claim-sync.mjs +123 -2
- package/hooks/beads-compact-restore.mjs +41 -9
- package/hooks/beads-compact-save.mjs +36 -5
- package/hooks/beads-gate-messages.mjs +27 -1
- package/hooks/beads-stop-gate.mjs +58 -8
- package/hooks/guard-rules.mjs +86 -0
- package/hooks/hooks.json +28 -18
- package/hooks/main-guard.mjs +3 -21
- package/hooks/quality-check.cjs +1286 -0
- package/hooks/quality-check.py +345 -0
- package/hooks/session-state.mjs +138 -0
- package/package.json +2 -1
- package/project-skills/quality-gates/.claude/settings.json +1 -24
- package/skills/creating-service-skills/SKILL.md +433 -0
- package/skills/creating-service-skills/references/script_quality_standards.md +425 -0
- package/skills/creating-service-skills/references/service_skill_system_guide.md +278 -0
- package/skills/creating-service-skills/scripts/bootstrap.py +326 -0
- package/skills/creating-service-skills/scripts/deep_dive.py +304 -0
- package/skills/creating-service-skills/scripts/scaffolder.py +482 -0
- package/skills/scoping-service-skills/SKILL.md +231 -0
- package/skills/scoping-service-skills/scripts/scope.py +74 -0
- package/skills/sync-docs/SKILL.md +235 -0
- package/skills/sync-docs/evals/evals.json +89 -0
- package/skills/sync-docs/references/doc-structure.md +104 -0
- package/skills/sync-docs/references/schema.md +103 -0
- package/skills/sync-docs/scripts/context_gatherer.py +246 -0
- package/skills/sync-docs/scripts/doc_structure_analyzer.py +495 -0
- package/skills/sync-docs/scripts/validate_doc.py +365 -0
- package/skills/sync-docs-workspace/iteration-1/benchmark.json +293 -0
- package/skills/sync-docs-workspace/iteration-1/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/outputs/result.md +210 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/outputs/result.md +101 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/outputs/result.md +198 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/outputs/result.md +94 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/outputs/result.md +237 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/outputs/result.md +134 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/benchmark.json +297 -0
- package/skills/sync-docs-workspace/iteration-2/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/outputs/result.md +137 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/run-1/grading.json +92 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/outputs/result.md +134 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/run-1/grading.json +86 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/outputs/result.md +193 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/run-1/grading.json +72 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/outputs/result.md +211 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/run-1/grading.json +91 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/outputs/result.md +182 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/run-1/grading.json +95 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/outputs/result.md +222 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/run-1/grading.json +88 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/benchmark.json +298 -0
- package/skills/sync-docs-workspace/iteration-3/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/outputs/result.md +125 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/run-1/grading.json +97 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/outputs/result.md +144 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/run-1/grading.json +78 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/outputs/result.md +104 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/run-1/grading.json +91 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/outputs/result.md +79 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/run-1/grading.json +82 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase1_context.json +302 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase2_drift.txt +33 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase3_analysis.json +114 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase4_fix.txt +118 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase5_validate.txt +38 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/result.md +158 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/run-1/grading.json +95 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/outputs/result.md +71 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/run-1/grading.json +90 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/run-1/timing.json +5 -0
- package/skills/updating-service-skills/SKILL.md +136 -0
- package/skills/updating-service-skills/scripts/drift_detector.py +222 -0
- package/skills/using-quality-gates/SKILL.md +254 -0
- package/skills/using-service-skills/SKILL.md +108 -0
- package/skills/using-service-skills/scripts/cataloger.py +74 -0
- package/skills/using-service-skills/scripts/skill_activator.py +152 -0
- package/skills/using-service-skills/scripts/test_skill_activator.py +58 -0
- package/skills/using-xtrm/SKILL.md +34 -38
package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/run-1/grading.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expectations": [
|
|
3
|
+
{
|
|
4
|
+
"text": "Ran context_gatherer.py and reported bd closed issues or merged PRs with specific data",
|
|
5
|
+
"passed": false,
|
|
6
|
+
"evidence": "The agent never ran context_gatherer.py. It gathered context using raw git commands (git log --oneline --merges, git diff --stat 10d6433..HEAD). It did report specific merged PRs (#111, #110, #109) with descriptions, but the script was not used. The expectation requires the specific script to be invoked, not just the outcome data to be present."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"text": "Ran doc_structure_analyzer.py and cited its structured output (STALE, EXTRACTABLE, MISSING, etc.)",
|
|
10
|
+
"passed": false,
|
|
11
|
+
"evidence": "No mention of doc_structure_analyzer.py anywhere in the output. The structured output categories (STALE, EXTRACTABLE, MISSING) never appear. The agent assessed doc staleness manually by reading files and comparing with git history."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"text": "Detected the CHANGELOG version gap (package.json v2.4.0 vs CHANGELOG v2.0.0)",
|
|
15
|
+
"passed": false,
|
|
16
|
+
"evidence": "The output notes 'CHANGELOG.md (contains full history through v2.0.0)' and references the codebase being at v2.4.0, but the agent concluded CHANGELOG was 'accurate' and listed it under 'No Changes Needed'. It did not explicitly frame this as a version gap between package.json (v2.4.0) and CHANGELOG (v2.0.0), and it did not flag it as an issue requiring action. The gap was effectively missed because the agent treated the [Unreleased] section as sufficient coverage."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"text": "Named at least one concrete next step with a specific file or action",
|
|
20
|
+
"passed": true,
|
|
21
|
+
"evidence": "The Observations section states: 'The CHANGELOG [Unreleased] section is still empty \u2014 it should capture the post-v2.4.0 sprint work (global-first arch, guard-rules centralization, Pi drift checks, xtrm init project detection) before the next release.' This identifies a specific file (CHANGELOG.md), a specific section ([Unreleased]), and concrete content items to add."
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"summary": {
|
|
25
|
+
"passed": 1,
|
|
26
|
+
"failed": 3,
|
|
27
|
+
"total": 4,
|
|
28
|
+
"pass_rate": 0.25
|
|
29
|
+
},
|
|
30
|
+
"execution_metrics": {
|
|
31
|
+
"tool_calls": {},
|
|
32
|
+
"total_tool_calls": 0,
|
|
33
|
+
"total_steps": 0,
|
|
34
|
+
"errors_encountered": 0,
|
|
35
|
+
"output_chars": 3172,
|
|
36
|
+
"transcript_chars": 0
|
|
37
|
+
},
|
|
38
|
+
"timing": {
|
|
39
|
+
"executor_duration_seconds": 217.1,
|
|
40
|
+
"grader_duration_seconds": 0.0,
|
|
41
|
+
"total_duration_seconds": 217.1
|
|
42
|
+
},
|
|
43
|
+
"claims": [
|
|
44
|
+
{
|
|
45
|
+
"claim": "3 PRs merged in the most recent sprint: #111, #110, #109",
|
|
46
|
+
"type": "factual",
|
|
47
|
+
"verified": true,
|
|
48
|
+
"evidence": "Consistent with git log output cited in the result and with the repo's commit history (PR #111 referenced in CLAUDE.md recent commits section)"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"claim": "CHANGELOG.md is accurate and no changes are needed to it",
|
|
52
|
+
"type": "quality",
|
|
53
|
+
"verified": false,
|
|
54
|
+
"evidence": "The agent says CHANGELOG 'contains full history through v2.0.0' and the codebase is at v2.4.0. This means v2.1.0 through v2.4.0 entries are missing from CHANGELOG \u2014 a significant gap that contradicts the 'accurate' verdict. The [Unreleased] section does not substitute for missing versioned entries."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"claim": "XTRM-GUIDE.md required no changes as it was updated by sprint commits",
|
|
58
|
+
"type": "quality",
|
|
59
|
+
"verified": false,
|
|
60
|
+
"evidence": "The claim is plausible given commit f8e37f9, but the agent did not run doc_structure_analyzer.py or any systematic staleness check against XTRM-GUIDE.md \u2014 it relied on reading the file and comparing manually. Cannot fully verify without the script output."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"claim": "README was 'about 1.5 versions behind HEAD'",
|
|
64
|
+
"type": "factual",
|
|
65
|
+
"verified": true,
|
|
66
|
+
"evidence": "README said v2.3.0 while codebase was at v2.4.0 with unreleased post-v2.4.0 work on top \u2014 the characterization is reasonable given the 8 changes fixed."
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"user_notes_summary": {
|
|
70
|
+
"uncertainties": [],
|
|
71
|
+
"needs_review": [],
|
|
72
|
+
"workarounds": []
|
|
73
|
+
},
|
|
74
|
+
"eval_feedback": {
|
|
75
|
+
"suggestions": [
|
|
76
|
+
{
|
|
77
|
+
"assertion": "Ran context_gatherer.py and reported bd closed issues or merged PRs with specific data",
|
|
78
|
+
"reason": "This assertion conflates two things: running the specific script AND reporting specific PR data. An agent that skips the script but manually finds the same PR data would fail on process but produce similar outputs. The eval would be stronger if split: one assertion for script invocation (verifiable from transcript tool calls) and one for PR data quality (verifiable from output content)."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"assertion": "Detected the CHANGELOG version gap (package.json v2.4.0 vs CHANGELOG v2.0.0)",
|
|
82
|
+
"reason": "The expectation is well-targeted, but the bar should be higher: not just 'detected' but 'flagged as a problem requiring action'. The agent did notice CHANGELOG goes to v2.0.0 while the code is at v2.4.0, yet concluded it was accurate. An assertion that checks whether the gap was identified as a documentation deficiency (not just noted in passing) would be more discriminating."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"reason": "No assertion covers output quality for the README edits that were actually made \u2014 the primary work product of this run. The agent claims to have fixed 8 categories of README issues, but no expectation checks whether those changes are correct, complete, or even present in the file. This is the largest unguarded outcome."
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"overall": "The evals focus on process steps (run script X, detect gap Y) but miss the primary output (README changes). The CHANGELOG gap assertion is good but needs tighter framing. The script-invocation assertions are fragile without transcript access to verify tool calls."
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: updating-service-skills
|
|
3
|
+
description: >-
|
|
4
|
+
Detect implementation drift and sync expert persona documentation.
|
|
5
|
+
Activates automatically via PostToolUse hook when files in a registered
|
|
6
|
+
service territory are modified. Use when a skill's documentation has
|
|
7
|
+
fallen behind the actual implementation.
|
|
8
|
+
hooks:
|
|
9
|
+
PostToolUse:
|
|
10
|
+
- matcher: "Write|Edit"
|
|
11
|
+
hooks:
|
|
12
|
+
- type: command
|
|
13
|
+
command: "python3 \"$CLAUDE_PROJECT_DIR/.claude/skills/updating-service-skills/scripts/drift_detector.py\" check-hook"
|
|
14
|
+
timeout: 10
|
|
15
|
+
allowed-tools: Read, Write, Grep, Glob
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Updating Service Skills
|
|
19
|
+
|
|
20
|
+
## Role: The Librarian
|
|
21
|
+
|
|
22
|
+
You are the **Service Skills Librarian**. Your job is to keep expert persona
|
|
23
|
+
documentation in sync with the actual implementation as the codebase evolves.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Automatic Drift Detection
|
|
28
|
+
|
|
29
|
+
After any `Write` or `Edit` operation, the `PostToolUse` hook runs
|
|
30
|
+
`drift_detector.py check-hook`. It reads the modified file path from stdin JSON
|
|
31
|
+
and checks whether it falls within a registered service territory.
|
|
32
|
+
|
|
33
|
+
If drift is detected, you will see this in your context:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
[Skill Sync]: Implementation drift detected in 'db-expert'.
|
|
37
|
+
File 'src/db/users.ts' was modified.
|
|
38
|
+
Use '/updating-service-skills' to sync the Database Expert documentation.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Manual Sync Process
|
|
44
|
+
|
|
45
|
+
### Step 1 — Scan for all drift
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python3 "$CLAUDE_PROJECT_DIR/.claude/skills/updating-service-skills/scripts/drift_detector.py" scan
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Step 2 — Read the current skill
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Read: .claude/skills/<service-id>/SKILL.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 3 — Analyze changes using Serena tools
|
|
58
|
+
|
|
59
|
+
Use Serena LSP tools (not raw file reads) to understand what changed:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
get_symbols_overview(<modified-file>, depth=1)
|
|
63
|
+
find_symbol(<changed-function>, include_body=True)
|
|
64
|
+
search_for_pattern("<new-pattern>")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 4 — Update the skill documentation
|
|
68
|
+
|
|
69
|
+
- Add new patterns or conventions discovered
|
|
70
|
+
- Update Failure Modes table if new exception handlers added
|
|
71
|
+
- Update log patterns in `scripts/log_hunter.py` if new log strings found
|
|
72
|
+
- Update territory patterns in `service-registry.json` if scope expanded
|
|
73
|
+
- Preserve `<!-- SEMANTIC_START --> ... <!-- SEMANTIC_END -->` blocks
|
|
74
|
+
|
|
75
|
+
### Step 5 — Mark as synced
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python3 "$CLAUDE_PROJECT_DIR/.claude/skills/updating-service-skills/scripts/drift_detector.py" \
|
|
79
|
+
sync <service-id>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Drift Scenarios
|
|
85
|
+
|
|
86
|
+
### New error pattern added to codebase
|
|
87
|
+
|
|
88
|
+
1. `search_for_pattern("raise.*New.*Error|logger.error.*new")` to find it
|
|
89
|
+
2. Add to `scripts/log_hunter.py` PATTERNS list with correct severity
|
|
90
|
+
3. Update Troubleshooting table in SKILL.md
|
|
91
|
+
|
|
92
|
+
### Territory expanded (new directory added)
|
|
93
|
+
|
|
94
|
+
1. Check if current glob patterns in `service-registry.json` cover new files
|
|
95
|
+
2. If not, update `territory` array in `service-registry.json`
|
|
96
|
+
3. Sync timestamp
|
|
97
|
+
|
|
98
|
+
### Major refactor changes conventions
|
|
99
|
+
|
|
100
|
+
1. `get_symbols_overview` on all changed files
|
|
101
|
+
2. Rewrite relevant Guidelines section in SKILL.md
|
|
102
|
+
3. Update health_probe.py if table structure or ports changed
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Tool Restrictions
|
|
107
|
+
|
|
108
|
+
Write to:
|
|
109
|
+
- ✅ `.claude/skills/*/SKILL.md` — skill documentation updates
|
|
110
|
+
- ✅ `.claude/skills/service-registry.json` — territory and sync timestamp updates
|
|
111
|
+
|
|
112
|
+
Avoid:
|
|
113
|
+
- ❌ Modify source code (read-only access to service territories)
|
|
114
|
+
- ❌ Delete skills or registry entries
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Sync Output Format
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
✅ Skill Synced: `<service-id>`
|
|
122
|
+
|
|
123
|
+
Updated:
|
|
124
|
+
- log_hunter.py: added 2 new patterns from exception handlers
|
|
125
|
+
- SKILL.md: Failure Modes table updated with OAuth expiry scenario
|
|
126
|
+
- Territory: unchanged
|
|
127
|
+
|
|
128
|
+
Next sync: triggers on next modification to <territory-patterns>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Related Skills
|
|
134
|
+
|
|
135
|
+
- `/using-service-skills` — Discover and activate expert personas
|
|
136
|
+
- `/creating-service-skills` — Scaffold new expert personas
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Drift detector — PostToolUse hook for updating-service-skills.
|
|
4
|
+
|
|
5
|
+
Checks whether a modified file belongs to a registered service territory.
|
|
6
|
+
If so, notifies Claude to sync the skill documentation.
|
|
7
|
+
|
|
8
|
+
Configured via updating-service-skills/SKILL.md frontmatter hooks:
|
|
9
|
+
PostToolUse → matcher: "Write|Edit" → command: drift_detector.py check-hook
|
|
10
|
+
|
|
11
|
+
Subcommands:
|
|
12
|
+
check-hook Read file_path from stdin JSON (PostToolUse hook mode)
|
|
13
|
+
check <file> Check a specific file path from CLI
|
|
14
|
+
sync <service-id> Mark service as synced (update registry timestamp)
|
|
15
|
+
scan Scan all territories for drifted services
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Bootstrap lives in creating-service-skills — shared utility
|
|
24
|
+
BOOTSTRAP_DIR = Path(__file__).parent.parent.parent / "creating-service-skills" / "scripts"
|
|
25
|
+
sys.path.insert(0, str(BOOTSTRAP_DIR))
|
|
26
|
+
|
|
27
|
+
from bootstrap import ( # noqa: E402
|
|
28
|
+
RootResolutionError,
|
|
29
|
+
find_service_for_path,
|
|
30
|
+
get_project_root,
|
|
31
|
+
get_service,
|
|
32
|
+
load_registry,
|
|
33
|
+
save_registry,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_drift(file_path: str, project_root: str | None = None) -> dict:
|
|
38
|
+
"""Check if a file change causes drift in any registered service territory."""
|
|
39
|
+
if project_root is None:
|
|
40
|
+
try:
|
|
41
|
+
project_root = get_project_root()
|
|
42
|
+
except RootResolutionError:
|
|
43
|
+
return {"drift": False, "reason": "Cannot resolve project root"}
|
|
44
|
+
|
|
45
|
+
# Normalize to relative path
|
|
46
|
+
fp = Path(file_path)
|
|
47
|
+
if fp.is_absolute():
|
|
48
|
+
try:
|
|
49
|
+
fp = fp.relative_to(project_root)
|
|
50
|
+
file_path = str(fp)
|
|
51
|
+
except ValueError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
service_id = find_service_for_path(file_path, project_root)
|
|
55
|
+
if not service_id:
|
|
56
|
+
return {"drift": False, "reason": "No service owns this file"}
|
|
57
|
+
|
|
58
|
+
service = get_service(service_id, project_root)
|
|
59
|
+
if not service:
|
|
60
|
+
return {"drift": False, "reason": "Service not found in registry"}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"drift": True,
|
|
64
|
+
"service_id": service_id,
|
|
65
|
+
"service_name": service.get("name", service_id),
|
|
66
|
+
"skill_path": service.get("skill_path"),
|
|
67
|
+
"last_sync": service.get("last_sync", ""),
|
|
68
|
+
"file_path": file_path,
|
|
69
|
+
"message": (
|
|
70
|
+
f"[Skill Sync]: Implementation drift detected in '{service_id}'. "
|
|
71
|
+
f"File '{file_path}' was modified. "
|
|
72
|
+
f"Use '/updating-service-skills' to sync the {service.get('name', service_id)} documentation."
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def check_drift_from_hook_stdin() -> None:
|
|
78
|
+
"""
|
|
79
|
+
PostToolUse hook mode: reads file_path from stdin JSON.
|
|
80
|
+
|
|
81
|
+
The PostToolUse hook delivers stdin JSON with shape:
|
|
82
|
+
{"tool_name": "Write", "tool_input": {"file_path": "/abs/path"}, ...}
|
|
83
|
+
|
|
84
|
+
Outputs drift message to stdout (shown to Claude via PostToolUse additionalContext).
|
|
85
|
+
Exits 0 always — drift detection is advisory, never blocking.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
data = json.load(sys.stdin)
|
|
89
|
+
except (json.JSONDecodeError, EOFError):
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
|
|
92
|
+
tool_input = data.get("tool_input", {})
|
|
93
|
+
file_path = tool_input.get("file_path", "")
|
|
94
|
+
|
|
95
|
+
if not file_path:
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
|
|
98
|
+
result = check_drift(file_path)
|
|
99
|
+
|
|
100
|
+
if result.get("drift"):
|
|
101
|
+
# Output additionalContext for Claude via PostToolUse JSON format
|
|
102
|
+
output = {
|
|
103
|
+
"hookSpecificOutput": {
|
|
104
|
+
"hookEventName": "PostToolUse",
|
|
105
|
+
"additionalContext": result["message"],
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
print(json.dumps(output))
|
|
109
|
+
|
|
110
|
+
sys.exit(0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def update_sync_time(service_id: str, project_root: str | None = None) -> bool:
|
|
114
|
+
"""Update last_sync timestamp for a service in the registry."""
|
|
115
|
+
try:
|
|
116
|
+
registry = load_registry(project_root)
|
|
117
|
+
except Exception:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
if "services" not in registry or service_id not in registry["services"]:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
registry["services"][service_id]["last_sync"] = (
|
|
124
|
+
datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
save_registry(registry, project_root)
|
|
129
|
+
return True
|
|
130
|
+
except Exception:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def scan_drift(project_root: str | None = None) -> list[dict]:
|
|
135
|
+
"""Scan all territories and identify services with files modified since last sync."""
|
|
136
|
+
if project_root is None:
|
|
137
|
+
try:
|
|
138
|
+
project_root = get_project_root()
|
|
139
|
+
except RootResolutionError:
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
root = Path(project_root)
|
|
143
|
+
registry = load_registry(project_root)
|
|
144
|
+
drifted: list[dict] = []
|
|
145
|
+
|
|
146
|
+
for service_id, service in registry.get("services", {}).items():
|
|
147
|
+
last_sync_str = service.get("last_sync", "")
|
|
148
|
+
if not last_sync_str:
|
|
149
|
+
continue
|
|
150
|
+
try:
|
|
151
|
+
sync_time = datetime.fromisoformat(last_sync_str.replace("Z", "+00:00"))
|
|
152
|
+
except ValueError:
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
for pattern in service.get("territory", []):
|
|
156
|
+
for fp in root.glob(pattern):
|
|
157
|
+
if fp.is_file():
|
|
158
|
+
try:
|
|
159
|
+
mtime = datetime.fromtimestamp(fp.stat().st_mtime, tz=timezone.utc)
|
|
160
|
+
if mtime > sync_time:
|
|
161
|
+
drifted.append(
|
|
162
|
+
{
|
|
163
|
+
"service_id": service_id,
|
|
164
|
+
"service_name": service.get("name", service_id),
|
|
165
|
+
"file_path": str(fp.relative_to(root)),
|
|
166
|
+
"last_sync": last_sync_str,
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
except (OSError, ValueError):
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
return drifted
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def main() -> None:
|
|
176
|
+
if len(sys.argv) < 2:
|
|
177
|
+
print("Usage: python drift_detector.py <command> [args...]")
|
|
178
|
+
print(" check-hook — Read file from stdin JSON (PostToolUse hook mode)")
|
|
179
|
+
print(" check <file> — Check a specific file path")
|
|
180
|
+
print(" sync <service-id> — Mark service as synced")
|
|
181
|
+
print(" scan — Scan all territories for drift")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
cmd = sys.argv[1]
|
|
185
|
+
|
|
186
|
+
if cmd == "check-hook":
|
|
187
|
+
check_drift_from_hook_stdin()
|
|
188
|
+
|
|
189
|
+
elif cmd == "check" and len(sys.argv) > 2:
|
|
190
|
+
result = check_drift(sys.argv[2])
|
|
191
|
+
if result.get("drift"):
|
|
192
|
+
print(result["message"])
|
|
193
|
+
else:
|
|
194
|
+
print(f"No drift: {result.get('reason', 'OK')}")
|
|
195
|
+
|
|
196
|
+
elif cmd == "sync" and len(sys.argv) > 2:
|
|
197
|
+
service_id = sys.argv[2]
|
|
198
|
+
if update_sync_time(service_id):
|
|
199
|
+
print(f"✓ Synced: {service_id}")
|
|
200
|
+
else:
|
|
201
|
+
print(f"✗ Failed to sync: {service_id}")
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
elif cmd == "scan":
|
|
205
|
+
drifted = scan_drift()
|
|
206
|
+
if drifted:
|
|
207
|
+
print(f"Found {len(drifted)} drifted service(s):")
|
|
208
|
+
for item in drifted:
|
|
209
|
+
print(
|
|
210
|
+
f" {item['service_id']}: {item['file_path']} "
|
|
211
|
+
f"(last sync: {item['last_sync']})"
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
print("No drift detected.")
|
|
215
|
+
|
|
216
|
+
else:
|
|
217
|
+
print(f"Unknown command: {cmd}")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
main()
|