Skip to content

Commit 632052f

Browse files
authored
feat : add optimize-simplicite-log skill (#1742)
1 parent b669203 commit 632052f

4 files changed

Lines changed: 348 additions & 0 deletions

File tree

docs/README.skills.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
250250
| [onboard-context-matic](../skills/onboard-context-matic/SKILL.md)<br />`gh skills install github/awesome-copilot onboard-context-matic` | Interactive onboarding tour for the context-matic MCP server. Walks the user through what the server does, shows all available APIs, lets them pick one to explore, explains it in their project language, demonstrates model_search and endpoint_search live, and ends with a menu of things the user can ask the agent to do. USE FOR: first-time setup; "what can this MCP do?"; "show me the available APIs"; "onboard me"; "how do I use the context-matic server"; "give me a tour". DO NOT USE FOR: actually integrating an API end-to-end (use integrate-context-matic instead). | None |
251251
| [oo-component-documentation](../skills/oo-component-documentation/SKILL.md)<br />`gh skills install github/awesome-copilot oo-component-documentation` | Create or update standardized object-oriented component documentation using a shared template plus mode-specific guidance for new and existing docs. | `assets/documentation-template.md`<br />`references/create-mode.md`<br />`references/update-mode.md` |
252252
| [openapi-to-application-code](../skills/openapi-to-application-code/SKILL.md)<br />`gh skills install github/awesome-copilot openapi-to-application-code` | Generate a complete, production-ready application from an OpenAPI specification | None |
253+
| [optimize-simplicite-logs](../skills/optimize-simplicite-logs/SKILL.md)<br />`gh skills install github/awesome-copilot optimize-simplicite-logs` | capability to parse Simplicité logs from a raw `.txt` file, filter fields to reduce noise, and output the result as structured JSON. | `scripts/SimpliciteLog2Json.ps1`<br />`scripts/simplicite-log2json.py` |
253254
| [pdftk-server](../skills/pdftk-server/SKILL.md)<br />`gh skills install github/awesome-copilot pdftk-server` | Skill for using the command-line tool pdftk (PDFtk Server) for working with PDF files. Use when asked to merge PDFs, split PDFs, rotate pages, encrypt or decrypt PDFs, fill PDF forms, apply watermarks, stamp overlays, extract metadata, burst documents into pages, repair corrupted PDFs, attach or extract files, or perform any PDF manipulation from the command line. | `references/download.md`<br />`references/pdftk-cli-examples.md`<br />`references/pdftk-man-page.md`<br />`references/pdftk-server-license.md`<br />`references/third-party-materials.md` |
254255
| [penpot-uiux-design](../skills/penpot-uiux-design/SKILL.md)<br />`gh skills install github/awesome-copilot penpot-uiux-design` | Comprehensive guide for creating professional UI/UX designs in Penpot using MCP tools. Use this skill when: (1) Creating new UI/UX designs for web, mobile, or desktop applications, (2) Building design systems with components and tokens, (3) Designing dashboards, forms, navigation, or landing pages, (4) Applying accessibility standards and best practices, (5) Following platform guidelines (iOS, Android, Material Design), (6) Reviewing or improving existing Penpot designs for usability. Triggers: "design a UI", "create interface", "build layout", "design dashboard", "create form", "design landing page", "make it accessible", "design system", "component library". | `references/accessibility.md`<br />`references/component-patterns.md`<br />`references/platform-guidelines.md`<br />`references/setup-troubleshooting.md` |
255256
| [performance-review-writer](../skills/performance-review-writer/SKILL.md)<br />`gh skills install github/awesome-copilot performance-review-writer` | Draft performance reviews, self-assessments, peer reviews, and upward feedback in your own voice. Analyzes your contributions, emails, and meeting history via WorkIQ, then produces honest, impact-focused drafts using the STAR format. USE FOR: write my performance review, draft self-assessment, peer review, 360 feedback, annual review, mid-year review, upward feedback, write review for colleague, performance appraisal. | None |
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
name: optimize-simplicite-logs
3+
description: capability to parse Simplicité logs from a raw `.txt` file, filter fields to reduce noise, and output the result as structured JSON.
4+
---
5+
6+
# Optimize Simplicite Logs
7+
8+
This skill provides the capability to parse Simplicité logs from a raw `.txt` file, filter fields to reduce noise, and output the result as structured JSON. This is critical for optimizing AI context size (saving ~56% of tokens) and providing structured, predictable data for troubleshooting.
9+
10+
## When to Use This Skill
11+
12+
Use this skill when you need to:
13+
- Analyze user-provided Simplicité log files in `.txt` format.
14+
- Avoid ingesting massive raw log files into your context window.
15+
- Extract structured fields (like `timestamp`, `level`, `body`) from verbose multi-line log output.
16+
17+
**IMPORTANT:** Instead of directly reading a raw `.txt` log file provided by the user using file read tools, you **must** use one of the log converter scripts (PowerShell or Python) to parse the file into a JSON format first, optionally extracting only the fields needed.
18+
19+
## Prerequisites
20+
21+
- Access to either the PowerShell script (`/scripts/SimpliciteLog2Json.ps1`) or the Python script (`/scripts/simplicite-log2json.py`).
22+
23+
## Core Capabilities
24+
25+
### 1. Context Optimization
26+
Reduces the tokens consumed by large Simplicité logs by extracting only relevant log fields (e.g. `body`, `timestamp`, `level`) and discarding non-relevant structural log data (like `app`, `endpoint`, `contextPath`).
27+
28+
### 2. Multi-line Support
29+
Properly captures stack traces and multiline errors inside the `body` field of the JSON structure, which a simple text search might miss.
30+
31+
### 3. Stdout Support
32+
If no output path is provided for the JSON file (e.g. omitting `--output` or `-Output`), the parsed JSON will be printed directly to stdout, allowing you to pipe the output to other tools.
33+
34+
## Output Summary
35+
36+
After processing, the tool prints a summary to stderr (or console):
37+
```
38+
Processed: 123 entries, Skipped: 2 entries
39+
```
40+
41+
## Usage Examples
42+
43+
### Example 1: Python Version (Recommended)
44+
Convert a log file to JSON, keeping only the most important fields:
45+
```sh
46+
python /absolute/path/to/skills/optimize-simplicite-logs/scripts/simplicite-log2json.py <input.txt> --include timestamp,level,body --output <output.json>
47+
```
48+
49+
### Example 2: PowerShell Version
50+
```powershell
51+
/python /absolute/path/to/skills/optimize-simplicite-logs/scripts/SimpliciteLog2Json.ps1 -InputPath "<input.txt>" -Output "<output.json>" -Include "body,timestamp,level"
52+
```
53+
54+
After generating the `<output.json>`, you can safely read the resulting file to perform your analysis.
55+
56+
## Guidelines
57+
58+
1. **Always Convert First:** Never directly read `.txt` log files from Simplicité using standard text reading tools. Always convert them to JSON using the available scripts.
59+
2. **Filter Fields:** Use `--include` (Python) or `-Include` (PowerShell) to restrict fields to what is absolutely necessary to diagnose the issue (usually `timestamp,level,body`).
60+
3. **Available Fields:** The fields you can filter include: `timestamp`, `app`, `level`, `endpoint`, `contextPath`, `event`, `user`, `class`, `function`, `rowId`, `body`.
61+
62+
## Common Patterns
63+
64+
### Pattern: Fast Contextual Troubleshooting
65+
```sh
66+
# 1. Run the script to generate a minified JSON output in the current directory
67+
python /absolute/path/to/skills/optimize-simplicite-logs/scripts/simplicite-log2json.py logs.txt --include timestamp,level,body --output logs_minified.json
68+
69+
# 2. Then read logs_minified.json to understand the context.
70+
```
71+
72+
## Limitations
73+
74+
- The parser depends on a fixed regex pattern that matches the standard Simplicité log output. If the log format has been heavily customized, parsing might fail or degrade.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
param (
2+
[Parameter(Mandatory=$true)]
3+
[string]$InputPath,
4+
5+
[string]$Output,
6+
7+
[string]$Include,
8+
9+
[string]$Exclude
10+
)
11+
12+
# Valid fields
13+
$ValidFields = @("timestamp", "app", "level", "endpoint", "contextPath", "event", "user", "class", "function", "rowId", "body")
14+
15+
# Function to check if a field is valid
16+
function Test-ValidField {
17+
param([string]$Field)
18+
return $ValidFields -contains $Field
19+
}
20+
21+
# Verify that -Include and -Exclude are not both used
22+
if ($Include -and $Exclude) {
23+
Write-Error "Error: -Include and -Exclude cannot be used together."
24+
exit 1
25+
}
26+
27+
# Initialize field variables
28+
$IncludeFields = $null
29+
$ExcludeFields = $null
30+
31+
# Validate fields provided in Include/Exclude
32+
if ($Include) {
33+
$IncludeFields = $Include -split "," | ForEach-Object { $_.Trim() }
34+
foreach ($field in $IncludeFields) {
35+
if (-not (Test-ValidField $field)) {
36+
Write-Error "Error: Invalid field '$field'. Valid fields: $($ValidFields -join ', ')"
37+
exit 1
38+
}
39+
}
40+
}
41+
42+
if ($Exclude) {
43+
$ExcludeFields = $Exclude -split "," | ForEach-Object { $_.Trim() }
44+
foreach ($field in $ExcludeFields) {
45+
if (-not (Test-ValidField $field)) {
46+
Write-Error "Error: Invalid field '$field'. Valid fields: $($ValidFields -join ', ')"
47+
exit 1
48+
}
49+
}
50+
}
51+
52+
# Check that the input file exists
53+
if (-not (Test-Path $InputPath)) {
54+
Write-Error "Error: File $InputPath does not exist."
55+
exit 1
56+
}
57+
# Read the file and normalize line endings
58+
# Group raw lines into log entries where a new entry starts with a timestamp.
59+
$raw = Get-Content -Path $InputPath -Raw
60+
$raw = $raw -replace "`r`n","`n" -replace "`r","`n"
61+
$lines = $raw -split "`n"
62+
63+
$entryTexts = @()
64+
$buffer = ""
65+
$skippedLines = 0
66+
67+
foreach ($line in $lines) {
68+
if ($line -eq $null) { continue }
69+
if ($line.Trim().Length -eq 0) { continue }
70+
71+
if ($line -match '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}') {
72+
if ($buffer -ne "") { $entryTexts += $buffer }
73+
$buffer = $line
74+
} else {
75+
# Continuation line: attach to current buffer if present, otherwise skip
76+
if ($buffer -eq "") {
77+
$skippedLines++
78+
continue
79+
} else {
80+
$buffer += "`n" + $line
81+
}
82+
}
83+
}
84+
85+
if ($buffer -ne "") { $entryTexts += $buffer }
86+
87+
$entries = @()
88+
$processed = 0
89+
$skippedMalformed = 0
90+
91+
foreach ($entryText in $entryTexts) {
92+
$parts = $entryText -split '\|'
93+
if ($parts.Count -ge 12) {
94+
# Trim only the first 11 fields; preserve the body (may contain pipes/newlines)
95+
for ($i=0; $i -le 10; $i++) { $parts[$i] = $parts[$i].Trim() }
96+
97+
$body = ($parts[11..($parts.Count - 1)] -join '|')
98+
99+
$entry = @{
100+
timestamp = $parts[0]
101+
app = $parts[1]
102+
level = $parts[2]
103+
endpoint = $parts[4]
104+
contextPath = $parts[5]
105+
event = $parts[6]
106+
user = $parts[7]
107+
class = $parts[8]
108+
function = $parts[9]
109+
rowId = $parts[10]
110+
body = $body
111+
}
112+
113+
# Apply include/exclude filters
114+
if ($IncludeFields -and $IncludeFields.Count -gt 0) {
115+
$filteredEntry = @{}
116+
foreach ($field in $IncludeFields) {
117+
if ($entry.ContainsKey($field)) {
118+
$filteredEntry[$field] = $entry[$field]
119+
} else {
120+
$filteredEntry[$field] = $null
121+
}
122+
}
123+
$entry = $filteredEntry
124+
}
125+
elseif ($ExcludeFields -and $ExcludeFields.Count -gt 0) {
126+
foreach ($field in $ExcludeFields) {
127+
if ($entry.ContainsKey($field)) {
128+
$entry.PSObject.Properties.Remove($field)
129+
}
130+
}
131+
}
132+
133+
$entries += $entry
134+
$processed++
135+
} else {
136+
$skippedMalformed++
137+
}
138+
}
139+
140+
$skipped = $skippedLines + $skippedMalformed
141+
142+
# Convert to JSON (compact)
143+
$json = $entries | ConvertTo-Json -Depth 10 -Compress
144+
145+
# Write output
146+
if ($Output) {
147+
Set-Content -Path $Output -Value $json -Encoding UTF8
148+
Write-Host "Output written to $Output"
149+
} else {
150+
$json
151+
}
152+
153+
Write-Host "Processed: $processed entries, Skipped: $skipped entries"
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import argparse
2+
import re
3+
import json
4+
import sys
5+
6+
VALID_FIELDS = [
7+
"timestamp", "app", "level", "endpoint", "contextPath", "event",
8+
"user", "class", "function", "rowId", "body"
9+
]
10+
11+
def validate_fields(value):
12+
fields = [f.strip() for f in value.split(",")]
13+
for f in fields:
14+
if f not in VALID_FIELDS:
15+
raise argparse.ArgumentTypeError(f"invalid field: {f}. Available: {', '.join(VALID_FIELDS)}")
16+
return fields
17+
18+
def parse_args():
19+
parser = argparse.ArgumentParser(
20+
prog="simplicite-log2json",
21+
description="Parse Simplicité logs and output JSON."
22+
)
23+
parser.add_argument("input", help="Input .txt log file path")
24+
parser.add_argument("-o", "--output", help="Output file path (default: stdout)", metavar="FILE")
25+
26+
group = parser.add_mutually_exclusive_group()
27+
group.add_argument("--include", help=f"Fields to include (comma-separated). Available: {', '.join(VALID_FIELDS)}", type=validate_fields, metavar="FIELDS", action="append")
28+
group.add_argument("--exclude", help=f"Fields to exclude (comma-separated). Available: {', '.join(VALID_FIELDS)}", type=validate_fields, metavar="FIELDS", action="append")
29+
30+
return parser.parse_args()
31+
32+
def parse_log_entry(text, log_regex):
33+
match = log_regex.match(text)
34+
if match:
35+
return {
36+
"timestamp": match.group("timestamp") or "",
37+
"app": match.group("app") or "",
38+
"level": match.group("level") or "",
39+
"endpoint": match.group("endpoint") or "",
40+
"contextPath": match.group("contextPath") or "",
41+
"event": match.group("event") or "",
42+
"user": match.group("user") or "",
43+
"class": match.group("class") or "",
44+
"function": match.group("function") or "",
45+
"rowId": match.group("rowId") or "",
46+
"body": match.group("body") or "",
47+
}
48+
return None
49+
50+
def filter_entry(entry, include, exclude):
51+
filtered = {}
52+
for k, v in entry.items():
53+
if include is not None and k not in include:
54+
continue
55+
if exclude is not None and k in exclude:
56+
continue
57+
filtered[k] = v
58+
return filtered
59+
60+
def main():
61+
args = parse_args()
62+
63+
include_fields = [item for sublist in args.include for item in sublist] if args.include else None
64+
exclude_fields = [item for sublist in args.exclude for item in sublist] if args.exclude else None
65+
66+
log_regex = re.compile(r"^(?P<timestamp>.*?)\|(?P<app>SIMPLICITE)\|(?P<level>.+?)\|\|(?P<endpoint>.*?)\|(?P<contextPath>.*?)\|(?P<event>.*?)\|(?P<user>.*?)\|(?P<class>.*?)\|(?P<function>.*?)\|(?P<rowId>.*?)\|(?P<body>.*)$", re.DOTALL)
67+
timestamp_re = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}")
68+
69+
entries = []
70+
buffer = []
71+
processed = 0
72+
skipped = 0
73+
74+
try:
75+
with open(args.input, "r", encoding="utf-8") as f:
76+
for line in f:
77+
line_stripped = line.rstrip('\n')
78+
79+
if timestamp_re.match(line_stripped):
80+
if buffer:
81+
entry_text = '\n'.join(buffer)
82+
entry = parse_log_entry(entry_text, log_regex)
83+
if entry:
84+
entries.append(entry)
85+
processed += 1
86+
else:
87+
skipped += 1
88+
buffer = []
89+
90+
buffer.append(line_stripped)
91+
92+
if buffer:
93+
entry_text = '\n'.join(buffer)
94+
entry = parse_log_entry(entry_text, log_regex)
95+
if entry:
96+
entries.append(entry)
97+
processed += 1
98+
else:
99+
skipped += 1
100+
except Exception as e:
101+
sys.stderr.write(f"Failed to open input file: {e}\n")
102+
sys.exit(1)
103+
104+
filtered = [filter_entry(entry, include_fields, exclude_fields) for entry in entries]
105+
json_str = json.dumps(filtered, indent=2)
106+
107+
if args.output:
108+
try:
109+
with open(args.output, "w", encoding="utf-8") as f:
110+
f.write(json_str)
111+
except Exception as e:
112+
sys.stderr.write(f"Failed to create output file: {e}\n")
113+
sys.exit(1)
114+
else:
115+
print(json_str)
116+
117+
sys.stderr.write(f"Processed: {processed} entries, Skipped: {skipped} entries\n")
118+
119+
if __name__ == "__main__":
120+
main()

0 commit comments

Comments
 (0)