skillware 0.2.7__tar.gz → 0.2.8__tar.gz
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.
- {skillware-0.2.7/skillware.egg-info → skillware-0.2.8}/PKG-INFO +2 -2
- {skillware-0.2.7 → skillware-0.2.8}/pyproject.toml +2 -2
- skillware-0.2.8/skills/dev_tools/__init__.py +1 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/__init__.py +1 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/card.json +33 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/instructions.md +104 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/manifest.yaml +55 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/skill.py +138 -0
- skillware-0.2.8/skills/dev_tools/issue_resolver/test_skill.py +185 -0
- {skillware-0.2.7 → skillware-0.2.8/skillware.egg-info}/PKG-INFO +2 -2
- {skillware-0.2.7 → skillware-0.2.8}/skillware.egg-info/SOURCES.txt +7 -0
- {skillware-0.2.7 → skillware-0.2.8}/LICENSE +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/MANIFEST.in +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/README.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/setup.cfg +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/mica_corpus.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/mica_module/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/pii_masker/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/pii_masker/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/pii_masker/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/pii_masker/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/pii_masker/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/compliance/tos_evaluator/test_skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/office/pdf_form_filler/utils.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/prompt_rewriter/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/prompt_rewriter/card.json +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/prompt_rewriter/instructions.md +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/prompt_rewriter/manifest.yaml +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skills/optimization/prompt_rewriter/skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware/core/__init__.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware/core/base_skill.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware/core/env.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware/core/loader.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware.egg-info/dependency_links.txt +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware.egg-info/requires.txt +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/skillware.egg-info/top_level.txt +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/tests/test_loader.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/tests/test_mica_module.py +0 -0
- {skillware-0.2.7 → skillware-0.2.8}/tests/test_skill_issuer.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
|
-
Author-email: ARPA Hellenic
|
|
5
|
+
Author-email: ARPA Hellenic Logical Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
8
8
|
Copyright (c) 2026 ARPA Hellenic Logical Systems
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skillware"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "A framework for modular, self-contained AI skills."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
11
|
-
{ name = "ARPA Hellenic
|
|
11
|
+
{ name = "ARPA Hellenic Logical Systems", email = "skillware-os@arpacorp.net" },
|
|
12
12
|
]
|
|
13
13
|
urls = { "Homepage" = "https://arpacorp.net", "Repository" = "https://github.com/arpahls/skillware" }
|
|
14
14
|
license = { file = "LICENSE" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# dev_tools skill category.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Issue Resolver skill package.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Issue Resolver",
|
|
3
|
+
"description": "Analyzes any GitHub issue against its target repository and produces a structured resolution plan with ranked implementation options, affected files, and caveats.",
|
|
4
|
+
"issuer": {
|
|
5
|
+
"name": "Ross Peili",
|
|
6
|
+
"email": "vpeilivanidis@gmail.com",
|
|
7
|
+
"github": "rosspeili",
|
|
8
|
+
"org": "ARPAHLS"
|
|
9
|
+
},
|
|
10
|
+
"icon": "git-pull-request",
|
|
11
|
+
"color": "#6E57E0",
|
|
12
|
+
"ui_schema": {
|
|
13
|
+
"type": "card",
|
|
14
|
+
"fields": [
|
|
15
|
+
{
|
|
16
|
+
"key": "status",
|
|
17
|
+
"label": "Status"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "issue.number",
|
|
21
|
+
"label": "Issue"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "issue.repo",
|
|
25
|
+
"label": "Repository"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"key": "auth.token_provided",
|
|
29
|
+
"label": "Authenticated"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Issue Resolver
|
|
2
|
+
|
|
3
|
+
You are using the `dev_tools/issue_resolver` skill.
|
|
4
|
+
|
|
5
|
+
Load this skill when the user provides a GitHub issue URL and asks you to understand, plan, or begin resolving it. The skill applies to any public GitHub repository. It is model-agnostic and does not assume any particular project's conventions.
|
|
6
|
+
|
|
7
|
+
## When to use this skill
|
|
8
|
+
|
|
9
|
+
- The user supplies a GitHub issue URL (with or without additional instructions).
|
|
10
|
+
- The user asks you to analyse an issue, understand its scope, or produce an implementation plan.
|
|
11
|
+
- The user wants to know which files will be affected before any code is written.
|
|
12
|
+
- The user wants ranked options and a recommended approach before committing to implementation.
|
|
13
|
+
|
|
14
|
+
## Workflow you must follow after receiving the skill payload
|
|
15
|
+
|
|
16
|
+
The skill's `execute()` call returns a `status: ready` payload containing pre-computed URLs. Use those URLs with your available tools to carry out the following stages in order. Do not skip stages.
|
|
17
|
+
|
|
18
|
+
### Stage 1 — Fetch the issue
|
|
19
|
+
|
|
20
|
+
Using the `issue.api_url` from the payload:
|
|
21
|
+
|
|
22
|
+
1. Fetch the issue via the GitHub API. If `auth.token_provided` is true, include the `Authorization: Bearer <token>` header.
|
|
23
|
+
2. Extract: title, body, labels, state, assignees, milestone.
|
|
24
|
+
3. Fetch any linked comments (`issue.api_url/comments`) and skim for decisions, constraints, or acceptance criteria that are not in the body.
|
|
25
|
+
4. Check for linked pull requests or referenced issues in the body.
|
|
26
|
+
|
|
27
|
+
### Stage 2 — Understand the repository
|
|
28
|
+
|
|
29
|
+
Using the `repository` URLs from the payload:
|
|
30
|
+
|
|
31
|
+
1. Fetch and read `repository.readme_url`. Identify: project purpose, tech stack, install instructions, any stated conventions.
|
|
32
|
+
2. Fetch and read `repository.contributing_url` if it exists (do not fail if it returns 404). Identify: contribution workflow, code style, PR requirements, any skill or module standards.
|
|
33
|
+
3. Fetch the directory tree via `repository.tree_api_url`. Identify the top-level structure: key source directories, test directories, docs directories, CI configuration, and any existing analogues to what the issue requests.
|
|
34
|
+
4. If the issue references specific files or directories, fetch and inspect their current contents.
|
|
35
|
+
|
|
36
|
+
### Stage 3 — Analyse
|
|
37
|
+
|
|
38
|
+
Produce a structured internal analysis covering:
|
|
39
|
+
|
|
40
|
+
- **Problem statement**: what the issue is actually asking for, including unstated implications.
|
|
41
|
+
- **Acceptance criteria**: verifiable bullets extracted or inferred from the issue body and comments.
|
|
42
|
+
- **Affected files**: every path likely to change — source, tests, documentation, CI, changelogs, configuration. Only list paths you have confirmed exist or have strong reason to expect.
|
|
43
|
+
- **Ripple effects**: downstream files, dependent modules, or external consumers that may be affected even if not directly changed.
|
|
44
|
+
- **Options**: up to three distinct implementation approaches with honest trade-off analysis (complexity, risk, alignment with project conventions, reversibility).
|
|
45
|
+
- **Recommendation**: one approach with a clear rationale.
|
|
46
|
+
- **Out of scope**: what you will not do in this resolution cycle and why.
|
|
47
|
+
- **Caveats**: required dependencies, breaking changes, security concerns, or prerequisites.
|
|
48
|
+
|
|
49
|
+
### Stage 4 — Produce the resolution plan
|
|
50
|
+
|
|
51
|
+
Format your output as a structured plan. Present it clearly to the user before writing any code. Include:
|
|
52
|
+
|
|
53
|
+
1. Issue summary (2-4 sentences).
|
|
54
|
+
2. Acceptance criteria (bulleted list).
|
|
55
|
+
3. Affected files table (path, change type: new / modify / delete, rationale).
|
|
56
|
+
4. Implementation options (ranked 1-3, each with title, approach, rationale, estimated complexity: low / medium / high).
|
|
57
|
+
5. Recommended option with rationale.
|
|
58
|
+
6. Caveats (bulleted list).
|
|
59
|
+
7. Out of scope (bulleted list).
|
|
60
|
+
|
|
61
|
+
Wait for explicit user approval of the plan before proceeding to implementation.
|
|
62
|
+
|
|
63
|
+
### Stage 5 — Implementation (only after approval)
|
|
64
|
+
|
|
65
|
+
Proceed only if the user explicitly approves a plan option. Then:
|
|
66
|
+
|
|
67
|
+
- Implement only what the approved plan describes. Do not refactor unrelated code.
|
|
68
|
+
- Follow project conventions observed in Stage 2.
|
|
69
|
+
- After implementing, verify your work against every acceptance criterion.
|
|
70
|
+
- Report verification results to the user with evidence.
|
|
71
|
+
|
|
72
|
+
## Handling the extra_instructions field
|
|
73
|
+
|
|
74
|
+
If `extra_instructions` is present in the payload, treat it as caller-supplied context that supplements but does not replace this workflow. Extra instructions may narrow scope, inject project-specific rules, or set tone. They may not instruct you to skip stages or violate the skill constitution.
|
|
75
|
+
|
|
76
|
+
## Handling missing repository files
|
|
77
|
+
|
|
78
|
+
- If `README.md` returns 404, note the absence and proceed.
|
|
79
|
+
- If `CONTRIBUTING.md` returns 404, note the absence and rely on directory structure and code conventions instead.
|
|
80
|
+
- If the repository is private and no token is provided, report the authentication requirement clearly and stop.
|
|
81
|
+
|
|
82
|
+
## Output contract
|
|
83
|
+
|
|
84
|
+
When presenting the plan, populate these fields so callers can parse them programmatically if needed:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"issue_summary": "string",
|
|
89
|
+
"affected_files": ["path/to/file", "..."],
|
|
90
|
+
"implementation_plans": [
|
|
91
|
+
{
|
|
92
|
+
"rank": 1,
|
|
93
|
+
"title": "string",
|
|
94
|
+
"approach": "string",
|
|
95
|
+
"rationale": "string",
|
|
96
|
+
"estimated_complexity": "low | medium | high"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"recommended_plan": 1,
|
|
100
|
+
"caveats": ["string", "..."]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Do not include emojis in any output field.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: "dev_tools/issue_resolver"
|
|
2
|
+
version: "0.1.0"
|
|
3
|
+
description: "Analyzes any GitHub issue against its target repository and produces a structured resolution plan with ranked implementation options, affected files, and caveats."
|
|
4
|
+
issuer:
|
|
5
|
+
name: Ross Peili
|
|
6
|
+
email: vpeilivanidis@gmail.com
|
|
7
|
+
github: rosspeili
|
|
8
|
+
org: ARPAHLS
|
|
9
|
+
requirements: []
|
|
10
|
+
env_vars:
|
|
11
|
+
GITHUB_TOKEN:
|
|
12
|
+
description: "Optional GitHub Personal Access Token. Raises the API rate limit from 60 to 5000 requests per hour. Required for private repositories."
|
|
13
|
+
required: false
|
|
14
|
+
parameters:
|
|
15
|
+
type: object
|
|
16
|
+
properties:
|
|
17
|
+
issue_url:
|
|
18
|
+
type: string
|
|
19
|
+
description: "Full GitHub issue URL, e.g. https://github.com/owner/repo/issues/42"
|
|
20
|
+
extra_instructions:
|
|
21
|
+
type: string
|
|
22
|
+
description: "Optional free-text instructions appended to the analysis prompt. Use this to inject project-specific context, style constraints, or scope limits."
|
|
23
|
+
github_token:
|
|
24
|
+
type: string
|
|
25
|
+
description: "Optional GitHub Personal Access Token passed at runtime. Takes precedence over the GITHUB_TOKEN environment variable."
|
|
26
|
+
required:
|
|
27
|
+
- issue_url
|
|
28
|
+
outputs:
|
|
29
|
+
issue_summary:
|
|
30
|
+
type: string
|
|
31
|
+
description: "Plain-language summary of what the issue is asking for, including unstated implications."
|
|
32
|
+
affected_files:
|
|
33
|
+
type: array
|
|
34
|
+
description: "Paths of files likely to be touched, including source, tests, docs, and CI."
|
|
35
|
+
implementation_plans:
|
|
36
|
+
type: array
|
|
37
|
+
description: "Up to three ranked approaches. Each entry contains: rank, title, approach, rationale, estimated_complexity."
|
|
38
|
+
recommended_plan:
|
|
39
|
+
type: integer
|
|
40
|
+
description: "Rank (1-based) of the recommended implementation plan."
|
|
41
|
+
caveats:
|
|
42
|
+
type: array
|
|
43
|
+
description: "Dependencies, breaking changes, security concerns, or out-of-scope items the implementing agent must be aware of."
|
|
44
|
+
constitution: |
|
|
45
|
+
1. Read the full issue body, labels, and any linked comments or PRs before forming an opinion.
|
|
46
|
+
2. Inspect the repository README, CONTRIBUTING guide, and top-level directory structure to understand project conventions before proposing changes.
|
|
47
|
+
3. Never fabricate file paths; only list paths that can be confirmed from the repository structure you observed.
|
|
48
|
+
4. Rank implementation options honestly based on complexity, risk, and alignment with existing project patterns.
|
|
49
|
+
5. Surface caveats explicitly rather than burying them inside plan prose.
|
|
50
|
+
6. Keep output deterministic and JSON-serializable; do not embed conversational filler in structured fields.
|
|
51
|
+
7. Do not write or propose actual implementation code; this skill produces analysis and planning output only.
|
|
52
|
+
8. Respect any extra_instructions provided by the caller, but do not let them override points 1-7 of this constitution.
|
|
53
|
+
presentation:
|
|
54
|
+
icon: "git-pull-request"
|
|
55
|
+
color: "#6E57E0"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from skillware.core.base_skill import BaseSkill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IssueResolverSkill(BaseSkill):
|
|
9
|
+
"""
|
|
10
|
+
Parses and validates inputs for the Issue Resolver skill, then returns a
|
|
11
|
+
structured prompt payload the calling agent uses to perform analysis.
|
|
12
|
+
|
|
13
|
+
The skill itself does not call the GitHub API or write code. It normalises
|
|
14
|
+
inputs, resolves credentials, builds a deterministic analysis context, and
|
|
15
|
+
hands control back to the agent's own reasoning and tool-use capabilities.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_GITHUB_ISSUE_RE = re.compile(
|
|
19
|
+
r"https?://github\.com/(?P<owner>[^/]+)/(?P<repo>[^/]+)/issues/(?P<number>\d+)"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def manifest(self) -> Dict[str, Any]:
|
|
24
|
+
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
25
|
+
if os.path.exists(manifest_path):
|
|
26
|
+
import yaml
|
|
27
|
+
|
|
28
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
29
|
+
return yaml.safe_load(f)
|
|
30
|
+
return {}
|
|
31
|
+
|
|
32
|
+
def _parse_issue_url(self, url: str) -> Dict[str, str]:
|
|
33
|
+
"""
|
|
34
|
+
Extracts owner, repo, and issue number from a GitHub issue URL.
|
|
35
|
+
Returns a dict with keys: owner, repo, number, api_url, raw_url.
|
|
36
|
+
Raises ValueError if the URL does not match the expected pattern.
|
|
37
|
+
"""
|
|
38
|
+
match = self._GITHUB_ISSUE_RE.match(url.strip())
|
|
39
|
+
if not match:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"issue_url does not match the expected GitHub issue URL pattern: {url!r}. "
|
|
42
|
+
"Expected format: https://github.com/<owner>/<repo>/issues/<number>"
|
|
43
|
+
)
|
|
44
|
+
owner = match.group("owner")
|
|
45
|
+
repo = match.group("repo")
|
|
46
|
+
number = match.group("number")
|
|
47
|
+
return {
|
|
48
|
+
"owner": owner,
|
|
49
|
+
"repo": repo,
|
|
50
|
+
"number": number,
|
|
51
|
+
"api_url": f"https://api.github.com/repos/{owner}/{repo}/issues/{number}",
|
|
52
|
+
"raw_url": url.strip(),
|
|
53
|
+
"repo_api_url": f"https://api.github.com/repos/{owner}/{repo}",
|
|
54
|
+
"repo_html_url": f"https://github.com/{owner}/{repo}",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def _resolve_token(self, params: Dict[str, Any]) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Returns the GitHub token to use, preferring the runtime parameter over
|
|
60
|
+
the environment variable. Returns an empty string if neither is set.
|
|
61
|
+
"""
|
|
62
|
+
token = (params.get("github_token") or "").strip()
|
|
63
|
+
if not token:
|
|
64
|
+
token = (
|
|
65
|
+
self.config.get("GITHUB_TOKEN") or os.environ.get("GITHUB_TOKEN") or ""
|
|
66
|
+
)
|
|
67
|
+
return token
|
|
68
|
+
|
|
69
|
+
def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
70
|
+
"""
|
|
71
|
+
Validates inputs and returns a structured analysis context.
|
|
72
|
+
|
|
73
|
+
The returned dict contains everything the calling agent needs to begin
|
|
74
|
+
the resolution workflow using its own tools (HTTP fetching, file
|
|
75
|
+
inspection, reasoning). The agent should follow the instructions in
|
|
76
|
+
instructions.md when interpreting this payload.
|
|
77
|
+
"""
|
|
78
|
+
issue_url = (params.get("issue_url") or "").strip()
|
|
79
|
+
if not issue_url:
|
|
80
|
+
return {
|
|
81
|
+
"status": "error",
|
|
82
|
+
"message": "issue_url is required and must not be empty.",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
parsed = self._parse_issue_url(issue_url)
|
|
87
|
+
except ValueError as exc:
|
|
88
|
+
return {"status": "error", "message": str(exc)}
|
|
89
|
+
|
|
90
|
+
token = self._resolve_token(params)
|
|
91
|
+
extra_instructions = (params.get("extra_instructions") or "").strip()
|
|
92
|
+
|
|
93
|
+
auth_header_note = (
|
|
94
|
+
"Include the Authorization header: Bearer <GITHUB_TOKEN>."
|
|
95
|
+
if token
|
|
96
|
+
else (
|
|
97
|
+
"No GITHUB_TOKEN is configured. The GitHub API will apply the "
|
|
98
|
+
"unauthenticated rate limit (60 requests per hour). For private "
|
|
99
|
+
"repositories or high-volume usage, set GITHUB_TOKEN."
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"status": "ready",
|
|
105
|
+
"issue": {
|
|
106
|
+
"url": parsed["raw_url"],
|
|
107
|
+
"api_url": parsed["api_url"],
|
|
108
|
+
"owner": parsed["owner"],
|
|
109
|
+
"repo": parsed["repo"],
|
|
110
|
+
"number": parsed["number"],
|
|
111
|
+
},
|
|
112
|
+
"repository": {
|
|
113
|
+
"html_url": parsed["repo_html_url"],
|
|
114
|
+
"api_url": parsed["repo_api_url"],
|
|
115
|
+
"readme_url": (
|
|
116
|
+
f"https://raw.githubusercontent.com/{parsed['owner']}"
|
|
117
|
+
f"/{parsed['repo']}/HEAD/README.md"
|
|
118
|
+
),
|
|
119
|
+
"contributing_url": (
|
|
120
|
+
f"https://raw.githubusercontent.com/{parsed['owner']}"
|
|
121
|
+
f"/{parsed['repo']}/HEAD/CONTRIBUTING.md"
|
|
122
|
+
),
|
|
123
|
+
"tree_api_url": (
|
|
124
|
+
f"https://api.github.com/repos/{parsed['owner']}/{parsed['repo']}"
|
|
125
|
+
"/git/trees/HEAD?recursive=1"
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
"auth": {
|
|
129
|
+
"token_provided": bool(token),
|
|
130
|
+
"note": auth_header_note,
|
|
131
|
+
},
|
|
132
|
+
"extra_instructions": extra_instructions or None,
|
|
133
|
+
"next_step": (
|
|
134
|
+
"Follow the workflow in instructions.md. Fetch the issue, "
|
|
135
|
+
"read the repository context, then produce the structured "
|
|
136
|
+
"resolution plan as described."
|
|
137
|
+
),
|
|
138
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest.mock as mock
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from .skill import IssueResolverSkill
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def skill():
|
|
13
|
+
"""Initialise the skill with no config (mirrors production load)."""
|
|
14
|
+
return IssueResolverSkill()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def manifest():
|
|
19
|
+
"""Load manifest.yaml for schema validation."""
|
|
20
|
+
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
21
|
+
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
22
|
+
return yaml.safe_load(f)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def card():
|
|
27
|
+
"""Load card.json for issuer consistency checks."""
|
|
28
|
+
card_path = os.path.join(os.path.dirname(__file__), "card.json")
|
|
29
|
+
with open(card_path, "r", encoding="utf-8") as f:
|
|
30
|
+
return json.load(f)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Manifest integrity
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_manifest_name(skill, manifest):
|
|
39
|
+
"""Skill internal manifest name must match manifest.yaml."""
|
|
40
|
+
assert skill.manifest["name"] == manifest["name"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_manifest_version(skill, manifest):
|
|
44
|
+
"""Skill internal manifest version must match manifest.yaml."""
|
|
45
|
+
assert skill.manifest["version"] == manifest["version"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_manifest_has_real_issuer(manifest):
|
|
49
|
+
"""manifest.yaml issuer must have non-placeholder name and email."""
|
|
50
|
+
issuer = manifest.get("issuer", {})
|
|
51
|
+
assert issuer.get("name"), "issuer.name is required"
|
|
52
|
+
assert issuer.get("email"), "issuer.email is required"
|
|
53
|
+
assert issuer["name"].lower() != "your name"
|
|
54
|
+
assert issuer["email"].lower() != "you@example.com"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_card_issuer_matches_manifest(manifest, card):
|
|
58
|
+
"""card.json issuer name and email must match manifest.yaml."""
|
|
59
|
+
m_issuer = manifest.get("issuer", {})
|
|
60
|
+
c_issuer = card.get("issuer", {})
|
|
61
|
+
assert c_issuer.get("name", "").strip() == m_issuer.get("name", "").strip()
|
|
62
|
+
assert c_issuer.get("email", "").strip() == m_issuer.get("email", "").strip()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Input validation
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_missing_issue_url_returns_error(skill):
|
|
71
|
+
"""execute() must return a structured error when issue_url is absent."""
|
|
72
|
+
result = skill.execute({})
|
|
73
|
+
assert result["status"] == "error"
|
|
74
|
+
assert "issue_url" in result["message"].lower()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_empty_issue_url_returns_error(skill):
|
|
78
|
+
"""execute() must return a structured error when issue_url is empty string."""
|
|
79
|
+
result = skill.execute({"issue_url": " "})
|
|
80
|
+
assert result["status"] == "error"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_malformed_url_returns_error(skill):
|
|
84
|
+
"""A URL that is not a GitHub issue URL must produce a structured error."""
|
|
85
|
+
result = skill.execute({"issue_url": "https://example.com/not-an-issue"})
|
|
86
|
+
assert result["status"] == "error"
|
|
87
|
+
assert "issue_url" in result["message"].lower()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_non_issue_github_url_returns_error(skill):
|
|
91
|
+
"""A GitHub URL pointing to a PR or repo root must be rejected."""
|
|
92
|
+
result = skill.execute({"issue_url": "https://github.com/owner/repo/pull/42"})
|
|
93
|
+
assert result["status"] == "error"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Happy-path execution
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
VALID_URL = "https://github.com/ARPAHLS/skillware/issues/56"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_valid_url_returns_ready(skill):
|
|
105
|
+
"""A well-formed GitHub issue URL must produce status: ready."""
|
|
106
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
107
|
+
assert result["status"] == "ready"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_result_contains_issue_fields(skill):
|
|
111
|
+
"""Ready result must include all issue sub-fields."""
|
|
112
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
113
|
+
issue = result["issue"]
|
|
114
|
+
assert issue["owner"] == "ARPAHLS"
|
|
115
|
+
assert issue["repo"] == "skillware"
|
|
116
|
+
assert issue["number"] == "56"
|
|
117
|
+
assert issue["api_url"].startswith(
|
|
118
|
+
"https://api.github.com/repos/ARPAHLS/skillware/issues/56"
|
|
119
|
+
)
|
|
120
|
+
assert issue["url"] == VALID_URL
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_result_contains_repository_fields(skill):
|
|
124
|
+
"""Ready result must include pre-computed repository URL fields."""
|
|
125
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
126
|
+
repo = result["repository"]
|
|
127
|
+
assert repo["html_url"] == "https://github.com/ARPAHLS/skillware"
|
|
128
|
+
assert repo["api_url"].startswith("https://api.github.com/repos/ARPAHLS/skillware")
|
|
129
|
+
assert repo["readme_url"].startswith(
|
|
130
|
+
"https://raw.githubusercontent.com/ARPAHLS/skillware"
|
|
131
|
+
)
|
|
132
|
+
assert repo["readme_url"].endswith("README.md")
|
|
133
|
+
assert repo["tree_api_url"].startswith(
|
|
134
|
+
"https://api.github.com/repos/ARPAHLS/skillware"
|
|
135
|
+
)
|
|
136
|
+
assert "trees" in repo["tree_api_url"]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_no_token_auth_note(skill):
|
|
140
|
+
"""When no token is provided, auth.token_provided must be False."""
|
|
141
|
+
env_without_token = {k: v for k, v in os.environ.items() if k != "GITHUB_TOKEN"}
|
|
142
|
+
with mock.patch.dict(os.environ, env_without_token, clear=True):
|
|
143
|
+
skill.config = {}
|
|
144
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
145
|
+
assert result["auth"]["token_provided"] is False
|
|
146
|
+
assert "rate limit" in result["auth"]["note"].lower()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_runtime_token_takes_precedence(skill):
|
|
150
|
+
"""A token passed in params must be recognised over env vars."""
|
|
151
|
+
result = skill.execute({"issue_url": VALID_URL, "github_token": "ghp_test_token"})
|
|
152
|
+
assert result["auth"]["token_provided"] is True
|
|
153
|
+
assert "Authorization" in result["auth"]["note"]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_extra_instructions_propagated(skill):
|
|
157
|
+
"""extra_instructions must be present in the payload when provided."""
|
|
158
|
+
result = skill.execute(
|
|
159
|
+
{
|
|
160
|
+
"issue_url": VALID_URL,
|
|
161
|
+
"extra_instructions": "Focus only on test coverage gaps.",
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
assert result["extra_instructions"] == "Focus only on test coverage gaps."
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_no_extra_instructions_is_none(skill):
|
|
168
|
+
"""extra_instructions must be None in the payload when not provided."""
|
|
169
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
170
|
+
assert result["extra_instructions"] is None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_result_is_json_serializable(skill):
|
|
174
|
+
"""execute() output must be fully JSON-serializable."""
|
|
175
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
176
|
+
serialized = json.dumps(result)
|
|
177
|
+
assert isinstance(serialized, str)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_next_step_present(skill):
|
|
181
|
+
"""Ready result must include a next_step hint for the calling agent."""
|
|
182
|
+
result = skill.execute({"issue_url": VALID_URL})
|
|
183
|
+
assert "next_step" in result
|
|
184
|
+
assert isinstance(result["next_step"], str)
|
|
185
|
+
assert len(result["next_step"]) > 0
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
|
-
Author-email: ARPA Hellenic
|
|
5
|
+
Author-email: ARPA Hellenic Logical Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
8
8
|
Copyright (c) 2026 ARPA Hellenic Logical Systems
|
|
@@ -27,6 +27,13 @@ skills/data_engineering/synthetic_generator/card.json
|
|
|
27
27
|
skills/data_engineering/synthetic_generator/instructions.md
|
|
28
28
|
skills/data_engineering/synthetic_generator/manifest.yaml
|
|
29
29
|
skills/data_engineering/synthetic_generator/skill.py
|
|
30
|
+
skills/dev_tools/__init__.py
|
|
31
|
+
skills/dev_tools/issue_resolver/__init__.py
|
|
32
|
+
skills/dev_tools/issue_resolver/card.json
|
|
33
|
+
skills/dev_tools/issue_resolver/instructions.md
|
|
34
|
+
skills/dev_tools/issue_resolver/manifest.yaml
|
|
35
|
+
skills/dev_tools/issue_resolver/skill.py
|
|
36
|
+
skills/dev_tools/issue_resolver/test_skill.py
|
|
30
37
|
skills/finance/__init__.py
|
|
31
38
|
skills/finance/wallet_screening/__init__.py
|
|
32
39
|
skills/finance/wallet_screening/card.json
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/instructions.md
RENAMED
|
File without changes
|
{skillware-0.2.7 → skillware-0.2.8}/skills/data_engineering/synthetic_generator/manifest.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/malicious_scs_2025.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.2.7 → skillware-0.2.8}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|