askyara 0.1.0__py3-none-any.whl
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.
- askyara/__init__.py +3 -0
- askyara/cli.py +77 -0
- askyara/core/__init__.py +1 -0
- askyara/core/command_registry.py +27 -0
- askyara/core/finding_model.py +92 -0
- askyara/core/policy_engine.py +63 -0
- askyara/core/report_formatter.py +36 -0
- askyara/installer/__init__.py +1 -0
- askyara/installer/doctor.py +50 -0
- askyara/installer/manager.py +49 -0
- askyara/tools/__init__.py +1 -0
- askyara/tools/git_diff.py +46 -0
- askyara/tools/project_detector.py +154 -0
- askyara/tools/redaction.py +35 -0
- askyara/tools/tool_registry.py +40 -0
- askyara-0.1.0.dist-info/METADATA +145 -0
- askyara-0.1.0.dist-info/RECORD +20 -0
- askyara-0.1.0.dist-info/WHEEL +4 -0
- askyara-0.1.0.dist-info/entry_points.txt +2 -0
- askyara-0.1.0.dist-info/licenses/LICENSE +100 -0
askyara/__init__.py
ADDED
askyara/cli.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""AskYARA Command Line Interface."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from askyara.installer.doctor import run_doctor
|
|
8
|
+
from askyara.tools.project_detector import detect_salesforce_project
|
|
9
|
+
from askyara.tools.git_diff import get_git_diff_summary
|
|
10
|
+
from askyara.core.policy_engine import PolicyEngine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(argv: list[str] | None = None) -> int:
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="askyara",
|
|
16
|
+
description="AskYARA Salesforce Engineering — AI Companion & Validator",
|
|
17
|
+
)
|
|
18
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
19
|
+
|
|
20
|
+
# doctor
|
|
21
|
+
doctor_parser = subparsers.add_parser("doctor", help="Inspect local environment and tools")
|
|
22
|
+
doctor_parser.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
23
|
+
|
|
24
|
+
# review
|
|
25
|
+
review_parser = subparsers.add_parser("review", help="Review local Salesforce changes")
|
|
26
|
+
review_parser.add_argument("--scope", default="git-diff", help="Review scope (git-diff, staged)")
|
|
27
|
+
review_parser.add_argument("--pack", help="Specific industry rule pack (e.g. omnistudio, fsc, health)")
|
|
28
|
+
review_parser.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
|
29
|
+
|
|
30
|
+
# audit
|
|
31
|
+
audit_parser = subparsers.add_parser("audit", help="Audit the current Salesforce project")
|
|
32
|
+
audit_parser.add_argument("--path", default=".", help="Root path of Salesforce project")
|
|
33
|
+
audit_parser.add_argument("--pack", help="Specific industry rule pack (e.g. omnistudio, fsc, health)")
|
|
34
|
+
|
|
35
|
+
args = parser.parse_args(argv)
|
|
36
|
+
|
|
37
|
+
repo_root = Path(__file__).resolve().parent.parent.parent
|
|
38
|
+
defaults_file = repo_root / "rules" / "defaults.yaml"
|
|
39
|
+
packs_dir = repo_root / "rules" / "packs"
|
|
40
|
+
|
|
41
|
+
if args.command == "doctor":
|
|
42
|
+
return run_doctor(as_json=args.json)
|
|
43
|
+
elif args.command == "review":
|
|
44
|
+
project = detect_salesforce_project(Path("."))
|
|
45
|
+
active_packs = list(project.industry_packs)
|
|
46
|
+
if args.pack and args.pack not in active_packs:
|
|
47
|
+
active_packs.append(args.pack)
|
|
48
|
+
|
|
49
|
+
policy = PolicyEngine(defaults_file, packs_dir=packs_dir, active_packs=active_packs)
|
|
50
|
+
print(f"[*] Detected Salesforce Project: {project.root} (Confidence: {project.confidence})")
|
|
51
|
+
print(f"[*] Active Rule Packs: {', '.join(policy.loaded_packs)}")
|
|
52
|
+
diff = get_git_diff_summary(Path("."))
|
|
53
|
+
print(f"[*] Git Status: {len(diff.changed_files)} changed files detected.")
|
|
54
|
+
print("[*] Local deterministic review complete. Ready for agent synthesis.")
|
|
55
|
+
return 0
|
|
56
|
+
elif args.command == "audit":
|
|
57
|
+
project = detect_salesforce_project(Path(args.path))
|
|
58
|
+
active_packs = list(project.industry_packs)
|
|
59
|
+
if args.pack and args.pack not in active_packs:
|
|
60
|
+
active_packs.append(args.pack)
|
|
61
|
+
|
|
62
|
+
policy = PolicyEngine(defaults_file, packs_dir=packs_dir, active_packs=active_packs)
|
|
63
|
+
print(f"[*] Auditing Salesforce Project at {project.root}")
|
|
64
|
+
print(f"[*] Active Rule Packs: {', '.join(policy.loaded_packs)}")
|
|
65
|
+
print(f"[*] Total Active Rules: {len(policy.get_enabled_rules())}")
|
|
66
|
+
for asset, count in project.asset_counts.items():
|
|
67
|
+
if count > 0:
|
|
68
|
+
print(f"[*] {asset.replace('_', ' ').title()}: {count}")
|
|
69
|
+
print("[*] Project audit complete.")
|
|
70
|
+
return 0
|
|
71
|
+
else:
|
|
72
|
+
parser.print_help()
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
sys.exit(main())
|
askyara/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core Domain Package."""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Command Registry Loader."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CommandRegistry:
|
|
9
|
+
def __init__(self, commands_dir: Path) -> None:
|
|
10
|
+
self.commands_dir = commands_dir
|
|
11
|
+
self.commands: dict[str, dict[str, Any]] = {}
|
|
12
|
+
self.load_all()
|
|
13
|
+
|
|
14
|
+
def load_all(self) -> None:
|
|
15
|
+
if not self.commands_dir.exists():
|
|
16
|
+
return
|
|
17
|
+
for file in self.commands_dir.glob("*.yaml"):
|
|
18
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
19
|
+
data = yaml.safe_load(f)
|
|
20
|
+
if isinstance(data, dict) and "id" in data:
|
|
21
|
+
self.commands[data["id"]] = data
|
|
22
|
+
|
|
23
|
+
def get(self, command_id: str) -> dict[str, Any] | None:
|
|
24
|
+
return self.commands.get(command_id)
|
|
25
|
+
|
|
26
|
+
def list_commands(self) -> list[dict[str, Any]]:
|
|
27
|
+
return list(self.commands.values())
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Finding and Evidence Data Models."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Severity(str, Enum):
|
|
9
|
+
CRITICAL = "critical"
|
|
10
|
+
HIGH = "high"
|
|
11
|
+
MEDIUM = "medium"
|
|
12
|
+
LOW = "low"
|
|
13
|
+
INFO = "info"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FindingStatus(str, Enum):
|
|
17
|
+
CONFIRMED = "confirmed"
|
|
18
|
+
LIKELY = "likely"
|
|
19
|
+
NEEDS_REVIEW = "needs_review"
|
|
20
|
+
DISMISSED = "dismissed"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ValidationStatus(str, Enum):
|
|
24
|
+
PASSED = "passed"
|
|
25
|
+
FAILED = "failed"
|
|
26
|
+
SKIPPED = "skipped"
|
|
27
|
+
UNAVAILABLE = "unavailable"
|
|
28
|
+
NOT_RUN = "not_run"
|
|
29
|
+
NOT_APPLICABLE = "not_applicable"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class EvidenceItem:
|
|
34
|
+
path: str
|
|
35
|
+
start_line: int
|
|
36
|
+
end_line: int | None = None
|
|
37
|
+
excerpt_hash: str | None = None
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> dict[str, Any]:
|
|
40
|
+
data: dict[str, Any] = {
|
|
41
|
+
"path": self.path,
|
|
42
|
+
"start_line": self.start_line,
|
|
43
|
+
}
|
|
44
|
+
if self.end_line is not None:
|
|
45
|
+
data["end_line"] = self.end_line
|
|
46
|
+
if self.excerpt_hash:
|
|
47
|
+
data["excerpt_hash"] = self.excerpt_hash
|
|
48
|
+
return data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Finding:
|
|
53
|
+
id: str
|
|
54
|
+
title: str
|
|
55
|
+
severity: Severity
|
|
56
|
+
category: str
|
|
57
|
+
status: FindingStatus
|
|
58
|
+
evidence: list[EvidenceItem]
|
|
59
|
+
description: str
|
|
60
|
+
recommendation: str
|
|
61
|
+
validation_status: ValidationStatus = ValidationStatus.NOT_RUN
|
|
62
|
+
validation_method: str = "static-analysis-and-review"
|
|
63
|
+
validation_details: str | None = None
|
|
64
|
+
impact: str | None = None
|
|
65
|
+
confidence: float = 1.0
|
|
66
|
+
schema_version: str = "1.0"
|
|
67
|
+
rule_id: str | None = None
|
|
68
|
+
rule_pack: str = "default-salesforce"
|
|
69
|
+
rule_pack_version: str = "1.0.0"
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"schema_version": self.schema_version,
|
|
74
|
+
"id": self.id,
|
|
75
|
+
"title": self.title,
|
|
76
|
+
"severity": self.severity.value,
|
|
77
|
+
"category": self.category,
|
|
78
|
+
"confidence": self.confidence,
|
|
79
|
+
"status": self.status.value,
|
|
80
|
+
"evidence": [e.to_dict() for e in self.evidence],
|
|
81
|
+
"description": self.description,
|
|
82
|
+
"impact": self.impact or "",
|
|
83
|
+
"recommendation": self.recommendation,
|
|
84
|
+
"validation": {
|
|
85
|
+
"method": self.validation_method,
|
|
86
|
+
"status": self.validation_status.value,
|
|
87
|
+
"details": self.validation_details or "",
|
|
88
|
+
},
|
|
89
|
+
"rule_id": self.rule_id or "",
|
|
90
|
+
"rule_pack": self.rule_pack,
|
|
91
|
+
"rule_pack_version": self.rule_pack_version,
|
|
92
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Policy Engine & Precedence Resolver supporting Modular Industry Packs."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PolicyEngine:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
defaults_file: Path,
|
|
12
|
+
packs_dir: Path | None = None,
|
|
13
|
+
active_packs: list[str] | None = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
self.defaults_file = defaults_file
|
|
16
|
+
self.packs_dir = packs_dir
|
|
17
|
+
self.rules: dict[str, dict[str, Any]] = {}
|
|
18
|
+
self.loaded_packs: list[str] = ["default-salesforce"]
|
|
19
|
+
self.load_defaults()
|
|
20
|
+
|
|
21
|
+
if packs_dir and active_packs:
|
|
22
|
+
for pack in active_packs:
|
|
23
|
+
self.load_pack(pack)
|
|
24
|
+
|
|
25
|
+
def load_defaults(self) -> None:
|
|
26
|
+
if not self.defaults_file.exists():
|
|
27
|
+
return
|
|
28
|
+
with open(self.defaults_file, "r", encoding="utf-8") as f:
|
|
29
|
+
data = yaml.safe_load(f)
|
|
30
|
+
if isinstance(data, dict) and "rules" in data:
|
|
31
|
+
for rule in data["rules"]:
|
|
32
|
+
if isinstance(rule, dict) and "id" in rule:
|
|
33
|
+
self.rules[rule["id"]] = rule
|
|
34
|
+
|
|
35
|
+
def load_pack(self, pack_name: str) -> bool:
|
|
36
|
+
if not self.packs_dir or not self.packs_dir.exists():
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
# Support name as "omnistudio" or "omnistudio.yaml"
|
|
40
|
+
clean_name = pack_name[:-5] if pack_name.endswith(".yaml") else pack_name
|
|
41
|
+
pack_file = self.packs_dir / f"{clean_name}.yaml"
|
|
42
|
+
if not pack_file.exists():
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
with open(pack_file, "r", encoding="utf-8") as f:
|
|
46
|
+
data = yaml.safe_load(f)
|
|
47
|
+
if isinstance(data, dict) and "rules" in data:
|
|
48
|
+
for rule in data["rules"]:
|
|
49
|
+
if isinstance(rule, dict) and "id" in rule:
|
|
50
|
+
self.rules[rule["id"]] = rule
|
|
51
|
+
if clean_name not in self.loaded_packs:
|
|
52
|
+
self.loaded_packs.append(clean_name)
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def get_rule(self, rule_id: str) -> dict[str, Any] | None:
|
|
57
|
+
return self.rules.get(rule_id)
|
|
58
|
+
|
|
59
|
+
def get_enabled_rules(self) -> list[dict[str, Any]]:
|
|
60
|
+
return [r for r in self.rules.values() if r.get("enabled", True)]
|
|
61
|
+
|
|
62
|
+
def get_rules_by_category(self, category: str) -> list[dict[str, Any]]:
|
|
63
|
+
return [r for r in self.rules.values() if r.get("category") == category]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Markdown and JSON Report Formatter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
from askyara.core.finding_model import Finding
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReportFormatter:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def to_json(findings: list[Finding], metadata: dict[str, Any] | None = None) -> str:
|
|
11
|
+
payload = {
|
|
12
|
+
"metadata": metadata or {},
|
|
13
|
+
"total_findings": len(findings),
|
|
14
|
+
"findings": [f.to_dict() for f in findings],
|
|
15
|
+
}
|
|
16
|
+
return json.dumps(payload, indent=2)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def to_markdown(findings: list[Finding], title: str = "AskYARA Review Report") -> str:
|
|
20
|
+
lines: list[str] = [f"# {title}", "", f"**Total Findings:** {len(findings)}", ""]
|
|
21
|
+
if not findings:
|
|
22
|
+
lines.append("Looks good. No issues found. Ship.")
|
|
23
|
+
return "\n".join(lines)
|
|
24
|
+
|
|
25
|
+
for i, f in enumerate(findings, 1):
|
|
26
|
+
lines.append(f"### {i}. [{f.severity.value.upper()}] {f.title}")
|
|
27
|
+
loc_str = ", ".join(f"`{e.path}:{e.start_line}`" for e in f.evidence)
|
|
28
|
+
lines.append(f"- **Location:** {loc_str}")
|
|
29
|
+
lines.append(f"- **Problem:** {f.description}")
|
|
30
|
+
if f.impact:
|
|
31
|
+
lines.append(f"- **Impact:** {f.impact}")
|
|
32
|
+
lines.append(f"- **Recommendation:** {f.recommendation}")
|
|
33
|
+
lines.append(f"- **Validation Status:** `{f.validation_status.value}`")
|
|
34
|
+
lines.append("")
|
|
35
|
+
|
|
36
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Installer Package."""
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Environment and Diagnostic Health Check (`askyara doctor`)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from askyara.tools.tool_registry import discover_tools
|
|
6
|
+
from askyara.tools.project_detector import detect_salesforce_project
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_doctor(as_json: bool = False) -> int:
|
|
10
|
+
tools = discover_tools()
|
|
11
|
+
project = detect_salesforce_project(Path("."))
|
|
12
|
+
|
|
13
|
+
report = {
|
|
14
|
+
"status": "healthy",
|
|
15
|
+
"project": {
|
|
16
|
+
"root": project.root,
|
|
17
|
+
"confidence": project.confidence,
|
|
18
|
+
"markers": project.detected_markers,
|
|
19
|
+
"package_directories": project.package_directories,
|
|
20
|
+
"asset_counts": project.asset_counts,
|
|
21
|
+
"industry_packs": project.industry_packs,
|
|
22
|
+
},
|
|
23
|
+
"tooling": {name: {"available": t.available, "version": t.version} for name, t in tools.items()},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if as_json:
|
|
27
|
+
print(json.dumps(report, indent=2))
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
print("==================================================")
|
|
31
|
+
print(" AskYARA Environment Doctor ")
|
|
32
|
+
print("==================================================")
|
|
33
|
+
print(f"Project Root : {project.root}")
|
|
34
|
+
print(f"Salesforce Project : {'Yes' if project.confidence > 0.5 else 'Not detected'}")
|
|
35
|
+
print(f"Detected Markers : {', '.join(project.detected_markers) if project.detected_markers else 'None'}")
|
|
36
|
+
print(f"Industry Clouds : {', '.join(project.industry_packs) if project.industry_packs else 'None (Core Salesforce)'}")
|
|
37
|
+
print(f"Package Dirs : {', '.join(project.package_directories) if project.package_directories else 'None'}")
|
|
38
|
+
print("--------------------------------------------------")
|
|
39
|
+
print("Asset Counts:")
|
|
40
|
+
for asset, count in project.asset_counts.items():
|
|
41
|
+
if count > 0:
|
|
42
|
+
print(f" - {asset:<26}: {count}")
|
|
43
|
+
print("--------------------------------------------------")
|
|
44
|
+
print("Tooling Discovery:")
|
|
45
|
+
for name, t in tools.items():
|
|
46
|
+
status_str = f"Found ({t.version})" if t.available else "Missing / Not Installed"
|
|
47
|
+
print(f" - {name:<26}: {status_str}")
|
|
48
|
+
print("==================================================")
|
|
49
|
+
|
|
50
|
+
return 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Adapter Installation and Lifecycle Manager."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AdapterManager:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def install_to_project(target_dir: Path, agent: str) -> bool:
|
|
10
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
11
|
+
target_dir = target_dir.resolve()
|
|
12
|
+
|
|
13
|
+
if agent == "cursor":
|
|
14
|
+
dest = target_dir / ".cursor" / "rules"
|
|
15
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
src = repo_root / ".cursor" / "rules" / "askyara.mdc"
|
|
17
|
+
if src.exists():
|
|
18
|
+
shutil.copy2(src, dest / "askyara.mdc")
|
|
19
|
+
return True
|
|
20
|
+
elif agent == "windsurf":
|
|
21
|
+
dest = target_dir / ".windsurf" / "rules"
|
|
22
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
src = repo_root / ".windsurf" / "rules" / "askyara.md"
|
|
24
|
+
if src.exists():
|
|
25
|
+
shutil.copy2(src, dest / "askyara.md")
|
|
26
|
+
return True
|
|
27
|
+
elif agent == "cline":
|
|
28
|
+
dest = target_dir / ".clinerules"
|
|
29
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
src = repo_root / ".clinerules" / "askyara.md"
|
|
31
|
+
if src.exists():
|
|
32
|
+
shutil.copy2(src, dest / "askyara.md")
|
|
33
|
+
return True
|
|
34
|
+
elif agent == "kiro":
|
|
35
|
+
dest = target_dir / ".kiro" / "steering"
|
|
36
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
src = repo_root / ".kiro" / "steering" / "askyara.md"
|
|
38
|
+
if src.exists():
|
|
39
|
+
shutil.copy2(src, dest / "askyara.md")
|
|
40
|
+
return True
|
|
41
|
+
elif agent == "copilot":
|
|
42
|
+
dest = target_dir / ".github"
|
|
43
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
src = repo_root / ".github" / "copilot-instructions.md"
|
|
45
|
+
if src.exists():
|
|
46
|
+
shutil.copy2(src, dest / "copilot-instructions.md")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Local Inspection Tools Package."""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Git Diff & Status Utility."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class GitDiffSummary:
|
|
10
|
+
is_git_repo: bool
|
|
11
|
+
changed_files: list[str] = field(default_factory=list)
|
|
12
|
+
diff_text: str = ""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_git_diff_summary(repo_root: Path) -> GitDiffSummary:
|
|
16
|
+
try:
|
|
17
|
+
status_proc = subprocess.run(
|
|
18
|
+
["git", "status", "--porcelain"],
|
|
19
|
+
cwd=str(repo_root),
|
|
20
|
+
capture_output=True,
|
|
21
|
+
text=True,
|
|
22
|
+
check=False,
|
|
23
|
+
timeout=10,
|
|
24
|
+
)
|
|
25
|
+
if status_proc.returncode != 0:
|
|
26
|
+
return GitDiffSummary(is_git_repo=False)
|
|
27
|
+
|
|
28
|
+
changed: list[str] = []
|
|
29
|
+
for line in status_proc.stdout.splitlines():
|
|
30
|
+
line = line.strip()
|
|
31
|
+
if line and len(line) > 3:
|
|
32
|
+
changed.append(line[3:])
|
|
33
|
+
|
|
34
|
+
diff_proc = subprocess.run(
|
|
35
|
+
["git", "diff", "HEAD"],
|
|
36
|
+
cwd=str(repo_root),
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
check=False,
|
|
40
|
+
timeout=15,
|
|
41
|
+
)
|
|
42
|
+
diff_text = diff_proc.stdout if diff_proc.returncode == 0 else ""
|
|
43
|
+
|
|
44
|
+
return GitDiffSummary(is_git_repo=True, changed_files=changed, diff_text=diff_text)
|
|
45
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
46
|
+
return GitDiffSummary(is_git_repo=False)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Salesforce Project & Industry Cloud Detector."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SalesforceProjectProfile:
|
|
10
|
+
id: str
|
|
11
|
+
root: str
|
|
12
|
+
detected_markers: list[str] = field(default_factory=list)
|
|
13
|
+
confidence: float = 0.0
|
|
14
|
+
package_directories: list[str] = field(default_factory=list)
|
|
15
|
+
asset_counts: dict[str, int] = field(default_factory=dict)
|
|
16
|
+
industry_packs: list[str] = field(default_factory=list)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def detect_salesforce_project(target_dir: Path) -> SalesforceProjectProfile:
|
|
20
|
+
markers: list[str] = []
|
|
21
|
+
package_dirs: list[str] = []
|
|
22
|
+
confidence = 0.0
|
|
23
|
+
asset_counts: dict[str, int] = {
|
|
24
|
+
"apex_class": 0,
|
|
25
|
+
"apex_trigger": 0,
|
|
26
|
+
"lwc_bundle": 0,
|
|
27
|
+
"flow": 0,
|
|
28
|
+
"omni_data_transform": 0,
|
|
29
|
+
"omni_integration_procedure": 0,
|
|
30
|
+
"flex_card": 0,
|
|
31
|
+
}
|
|
32
|
+
industry_packs: list[str] = []
|
|
33
|
+
|
|
34
|
+
sfdx_config = target_dir / "sfdx-project.json"
|
|
35
|
+
if sfdx_config.is_file():
|
|
36
|
+
markers.append("sfdx-project.json")
|
|
37
|
+
confidence += 0.7
|
|
38
|
+
try:
|
|
39
|
+
with open(sfdx_config, "r", encoding="utf-8") as f:
|
|
40
|
+
data = json.load(f)
|
|
41
|
+
for entry in data.get("packageDirectories", []):
|
|
42
|
+
if "path" in entry:
|
|
43
|
+
package_dirs.append(entry["path"])
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
force_app = target_dir / "force-app"
|
|
48
|
+
if force_app.is_dir():
|
|
49
|
+
markers.append("force-app")
|
|
50
|
+
confidence = min(1.0, confidence + 0.3)
|
|
51
|
+
if "force-app" not in package_dirs:
|
|
52
|
+
package_dirs.append("force-app")
|
|
53
|
+
|
|
54
|
+
# Search paths
|
|
55
|
+
search_dirs = [target_dir / p for p in package_dirs] if package_dirs else [target_dir]
|
|
56
|
+
|
|
57
|
+
for base in search_dirs:
|
|
58
|
+
if not base.exists():
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Standard Core Assets
|
|
62
|
+
all_cls = list(base.glob("**/*.cls"))
|
|
63
|
+
all_triggers = list(base.glob("**/*.trigger"))
|
|
64
|
+
all_flows = list(base.glob("**/*.flow-meta.xml"))
|
|
65
|
+
|
|
66
|
+
asset_counts["apex_class"] += len(all_cls)
|
|
67
|
+
asset_counts["apex_trigger"] += len(all_triggers)
|
|
68
|
+
asset_counts["flow"] += len(all_flows)
|
|
69
|
+
|
|
70
|
+
lwc_dir = base / "main" / "default" / "lwc"
|
|
71
|
+
if lwc_dir.is_dir():
|
|
72
|
+
asset_counts["lwc_bundle"] += len([d for d in lwc_dir.iterdir() if d.is_dir()])
|
|
73
|
+
|
|
74
|
+
# OmniStudio Metadata Detection
|
|
75
|
+
odt_count = len(list(base.glob("**/omniDataTransforms*/**/*.json"))) + len(list(base.glob("**/omniDataTransforms*/**/*.xml")))
|
|
76
|
+
oip_count = len(list(base.glob("**/omniIntegrationProcedures*/**/*.json"))) + len(list(base.glob("**/omniIntegrationProcedures*/**/*.xml")))
|
|
77
|
+
fc_count = len(list(base.glob("**/flexCards*/**/*.json"))) + len(list(base.glob("**/flexCards*/**/*.xml")))
|
|
78
|
+
|
|
79
|
+
# Also check for sfdx-omnistudio in json files
|
|
80
|
+
omni_json_hits = 0
|
|
81
|
+
for jf in list(base.glob("**/*.json"))[:20]:
|
|
82
|
+
try:
|
|
83
|
+
txt = jf.read_text(encoding="utf-8", errors="ignore")
|
|
84
|
+
if "sfdx-omnistudio" in txt or "omniDataTransforms" in str(jf):
|
|
85
|
+
omni_json_hits += 1
|
|
86
|
+
except Exception:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
asset_counts["omni_data_transform"] += odt_count + (1 if omni_json_hits > 0 and odt_count == 0 else 0)
|
|
90
|
+
asset_counts["omni_integration_procedure"] += oip_count
|
|
91
|
+
asset_counts["flex_card"] += fc_count
|
|
92
|
+
|
|
93
|
+
if odt_count > 0 or oip_count > 0 or fc_count > 0 or omni_json_hits > 0:
|
|
94
|
+
if "omnistudio" not in industry_packs:
|
|
95
|
+
industry_packs.append("omnistudio")
|
|
96
|
+
markers.append("omnistudio-metadata")
|
|
97
|
+
|
|
98
|
+
# Industry Cloud Namespaces & Object Signature Scanning
|
|
99
|
+
all_paths_str = " ".join([str(p) for p in base.glob("**/*")])
|
|
100
|
+
|
|
101
|
+
# Scan code text of Apex classes for industry namespace signatures
|
|
102
|
+
sample_code = ""
|
|
103
|
+
for code_file in (all_cls + all_triggers)[:25]:
|
|
104
|
+
try:
|
|
105
|
+
sample_code += " " + code_file.read_text(encoding="utf-8", errors="ignore")[:2000]
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
combined_search_space = all_paths_str + " " + sample_code
|
|
110
|
+
|
|
111
|
+
if "FinServ" in combined_search_space or any(base.glob("**/FinServ__*")):
|
|
112
|
+
if "financial-services-cloud" not in industry_packs:
|
|
113
|
+
industry_packs.append("financial-services-cloud")
|
|
114
|
+
markers.append("fsc-namespace")
|
|
115
|
+
|
|
116
|
+
if "HealthCloud" in combined_search_space or any(base.glob("**/HealthCloudGA__*")):
|
|
117
|
+
if "health-cloud" not in industry_packs:
|
|
118
|
+
industry_packs.append("health-cloud")
|
|
119
|
+
markers.append("health-cloud-namespace")
|
|
120
|
+
|
|
121
|
+
if "SalesAgreement" in combined_search_space or "AdvAcctForecast" in combined_search_space:
|
|
122
|
+
if "manufacturing-cloud" not in industry_packs:
|
|
123
|
+
industry_packs.append("manufacturing-cloud")
|
|
124
|
+
markers.append("manufacturing-cloud-markers")
|
|
125
|
+
|
|
126
|
+
if "RetailStore" in combined_search_space or "AssessmentTask" in combined_search_space:
|
|
127
|
+
if "consumer-goods-cloud" not in industry_packs:
|
|
128
|
+
industry_packs.append("consumer-goods-cloud")
|
|
129
|
+
markers.append("consumer-goods-markers")
|
|
130
|
+
|
|
131
|
+
if "Vehicle" in combined_search_space:
|
|
132
|
+
if "automotive-cloud" not in industry_packs:
|
|
133
|
+
industry_packs.append("automotive-cloud")
|
|
134
|
+
markers.append("automotive-markers")
|
|
135
|
+
|
|
136
|
+
if "Premise" in combined_search_space or "ServicePoint" in combined_search_space:
|
|
137
|
+
if "energy-utilities-cloud" not in industry_packs:
|
|
138
|
+
industry_packs.append("energy-utilities-cloud")
|
|
139
|
+
markers.append("energy-utilities-markers")
|
|
140
|
+
|
|
141
|
+
if "BusinessLicense" in combined_search_space or "RegulatoryTransaction" in combined_search_space:
|
|
142
|
+
if "public-sector-solutions" not in industry_packs:
|
|
143
|
+
industry_packs.append("public-sector-solutions")
|
|
144
|
+
markers.append("public-sector-markers")
|
|
145
|
+
|
|
146
|
+
return SalesforceProjectProfile(
|
|
147
|
+
id=f"project:local:{target_dir.resolve().name}",
|
|
148
|
+
root=str(target_dir.resolve()),
|
|
149
|
+
detected_markers=markers,
|
|
150
|
+
confidence=confidence,
|
|
151
|
+
package_directories=package_dirs,
|
|
152
|
+
asset_counts=asset_counts,
|
|
153
|
+
industry_packs=industry_packs,
|
|
154
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Secret, Regulatory, and Industry-Sensitive Token Redactor."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
SECRET_PATTERNS = [
|
|
6
|
+
re.compile(r"(?i)(bearer\s+[a-zA-Z0-9_\-\.]{20,})"),
|
|
7
|
+
re.compile(r"(?i)(password\s*[:=]\s*['\"][^'\"]+['\"])"),
|
|
8
|
+
re.compile(r"(?i)(client_secret\s*[:=]\s*['\"][^'\"]+['\"])"),
|
|
9
|
+
re.compile(r"(?i)(api[_-]?key\s*[:=]\s*['\"][^'\"]+['\"])"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
# Healthcare HIPAA / PHI Patterns
|
|
13
|
+
PHI_PATTERNS = [
|
|
14
|
+
re.compile(r"(?i)(mrn\s*[:=]\s*['\"]?[A-Z0-9\-]{6,12}['\"]?)"),
|
|
15
|
+
re.compile(r"(?i)(npi\s*[:=]\s*['\"]?\d{10}['\"]?)"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Financial Services PCI-DSS / PII Patterns
|
|
19
|
+
PCI_PII_PATTERNS = [
|
|
20
|
+
# 13-19 digit credit card numbers (Visa, MC, Amex)
|
|
21
|
+
re.compile(r"\b(?:\d{4}[ -]?){3}\d{4}\b"),
|
|
22
|
+
# SSN (999-99-9999)
|
|
23
|
+
re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def redact_secrets(text: str) -> str:
|
|
28
|
+
redacted = text
|
|
29
|
+
for pattern in SECRET_PATTERNS:
|
|
30
|
+
redacted = pattern.sub("[REDACTED_SECRET]", redacted)
|
|
31
|
+
for pattern in PHI_PATTERNS:
|
|
32
|
+
redacted = pattern.sub("[REDACTED_PHI]", redacted)
|
|
33
|
+
for pattern in PCI_PII_PATTERNS:
|
|
34
|
+
redacted = pattern.sub("[REDACTED_PCI_PII]", redacted)
|
|
35
|
+
return redacted
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""External Tool Discovery."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ToolStatus:
|
|
10
|
+
name: str
|
|
11
|
+
available: bool
|
|
12
|
+
version: str | None = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_tool(name: str, version_arg: str = "--version") -> ToolStatus:
|
|
16
|
+
path = shutil.which(name)
|
|
17
|
+
if not path:
|
|
18
|
+
return ToolStatus(name=name, available=False)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
proc = subprocess.run(
|
|
22
|
+
[name, version_arg],
|
|
23
|
+
capture_output=True,
|
|
24
|
+
text=True,
|
|
25
|
+
timeout=5,
|
|
26
|
+
check=False,
|
|
27
|
+
)
|
|
28
|
+
ver = proc.stdout.splitlines()[0].strip() if proc.stdout else None
|
|
29
|
+
return ToolStatus(name=name, available=True, version=ver)
|
|
30
|
+
except Exception:
|
|
31
|
+
return ToolStatus(name=name, available=True, version="unknown")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def discover_tools() -> dict[str, ToolStatus]:
|
|
35
|
+
return {
|
|
36
|
+
"git": check_tool("git"),
|
|
37
|
+
"sf": check_tool("sf", "--version"),
|
|
38
|
+
"sfdx": check_tool("sfdx", "--version"),
|
|
39
|
+
"python": check_tool("python3", "--version"),
|
|
40
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: askyara
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Salesforce-native AI engineering companion and multi-agent toolkit
|
|
5
|
+
Author: AskYARA Engineering Team
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: jsonschema>=4.20.0
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# AskYARA Salesforce Engineering
|
|
25
|
+
|
|
26
|
+
> **Salesforce-native AI engineering companion and multi-agent toolkit.**
|
|
27
|
+
> Understands Apex, LWC, Flows, Salesforce metadata, governor limits, security, and project architecture.
|
|
28
|
+
|
|
29
|
+
AskYARA brings Salesforce engineering intelligence directly into your favorite coding agent—whether you use **Cursor**, **Claude Code**, **GitHub Copilot**, **Windsurf**, **Cline**, **Kiro**, or **Codex**.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Key Capabilities
|
|
34
|
+
|
|
35
|
+
- **Platform-Aware Review (`/askyara-review`):** Inspects changed files, surrounding callers, governor limits (SOQL/DML in loops), CRUD/FLS access, and asynchronous boundaries.
|
|
36
|
+
- **Repository Audit (`/askyara-audit`):** Scans the whole Salesforce DX project for anti-patterns, missing tests, and architectural technical debt.
|
|
37
|
+
- **Evidence-Backed Findings:** Every reported issue includes the file path, exact line numbers, severity, rationale, and recommended fix.
|
|
38
|
+
- **Deterministic Local Tools:** Interfaces with Git, Salesforce CLI (`sf`), and Salesforce Code Analyzer / PMD when installed.
|
|
39
|
+
- **Zero Cloud Requirement:** Local-first, privacy-preserving, and read-only by default.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
### 1. Install into Your Coding Agent
|
|
46
|
+
|
|
47
|
+
See [INSTALL.md](INSTALL.md) for agent-specific instructions.
|
|
48
|
+
|
|
49
|
+
For **Claude Code**:
|
|
50
|
+
```bash
|
|
51
|
+
# In Claude Code CLI or Desktop
|
|
52
|
+
/plugin install askyara
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For **GitHub Copilot**:
|
|
56
|
+
```bash
|
|
57
|
+
# Workspace instruction file
|
|
58
|
+
cp .github/copilot-instructions.md your-project/.github/copilot-instructions.md
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For **Cursor / Windsurf / Cline / Kiro**:
|
|
62
|
+
Copy the matching rule into your workspace:
|
|
63
|
+
```bash
|
|
64
|
+
# Cursor
|
|
65
|
+
cp .cursor/rules/askyara.mdc your-project/.cursor/rules/
|
|
66
|
+
|
|
67
|
+
# Windsurf
|
|
68
|
+
cp .windsurf/rules/askyara.md your-project/.windsurf/rules/
|
|
69
|
+
|
|
70
|
+
# Cline
|
|
71
|
+
cp .clinerules/askyara.md your-project/.clinerules/
|
|
72
|
+
|
|
73
|
+
# Kiro
|
|
74
|
+
cp .kiro/steering/askyara.md your-project/.kiro/steering/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Run AskYARA CLI Directly
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Verify installation and discover local Salesforce tools
|
|
81
|
+
askyara doctor
|
|
82
|
+
|
|
83
|
+
# Review local changes in an SFDX project
|
|
84
|
+
askyara review
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Commands
|
|
90
|
+
|
|
91
|
+
| Command | What it does |
|
|
92
|
+
| :--- | :--- |
|
|
93
|
+
| `/askyara [query]` | Ask questions about the current Salesforce project: architecture, Apex classes, LWC components, Flows, and metadata structure. |
|
|
94
|
+
| `/askyara-review` | Review the current diff like a Salesforce Technical Architect: governor limits (SOQL/DML in loops), CRUD/FLS security, sharing context, bulkification, and test quality. Name a target to narrow or widen: `uncommitted`, `staged`, `branch`, or a PR link. |
|
|
95
|
+
| `/askyara-audit` | Comprehensive quality, security, and architecture audit of the whole Salesforce DX repository, ranked by what to fix first. |
|
|
96
|
+
| `/askyara-security` | Focused security audit: sharing models (`with sharing`), CRUD/FLS enforcement, dynamic SOQL injection, and secret/credential exposure. |
|
|
97
|
+
| `/askyara-test` | Identify missing test coverage, test quality flaws (missing assertions), and generate bulk test scenarios. |
|
|
98
|
+
| `/askyara-help` | Quick reference for available commands, installed skills, and diagnostic status. |
|
|
99
|
+
|
|
100
|
+
Commands need a skill-capable host (Claude Code, Codex, Devin CLI, Github Copilot, Kiro, Cline, Windsurf). In Claude Code and hosts with slash-command support, invoke them directly as `/askyara-review`. In namespaced plugin environments, they are available as `/askyara:askyara-review`. The instruction-only adapters (Cursor's rule file, Windsurf, Cline, Copilot and Kiro `AGENTS.md`) apply the always-on Salesforce engineering rules across all prompts without requiring slash commands.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## Salesforce Industries Support
|
|
106
|
+
|
|
107
|
+
AskYARA includes modular **Industry Capability Packs** that bring domain-aware architecture and review rules to specialized Salesforce clouds:
|
|
108
|
+
|
|
109
|
+
- **OmniStudio (`omnistudio`):** DataRaptor Turbo Extract enforcement, unindexed filter warnings, and Integration Procedure Loop Block limits.
|
|
110
|
+
- **Financial Services Cloud (`financial-services-cloud`):** Synchronous RBL rollup limits, Compliant Information Barriers (CIB), Person Account handling.
|
|
111
|
+
- **Health Cloud (`health-cloud`):** HIPAA/PHI debug log masking, FHIR CodeSet standard mappings, and Care Team data boundaries.
|
|
112
|
+
- **Manufacturing Cloud (`manufacturing-cloud`):** Data Processing Engine (DPE) forecast delegation, Sales Agreement row-locking prevention.
|
|
113
|
+
- **Consumer Goods / CPG (`consumer-goods-cloud`):** Mobile in-store sync conflict resolution, Trade Promotion Management (TPM) budget calculation boundaries.
|
|
114
|
+
- **Automotive Cloud (`automotive-cloud`):** Telematics anti-patterns (Data Cloud vs sObjects), warranty claim idempotency.
|
|
115
|
+
- **Energy & Utilities (`energy-utilities-cloud`):** Tariff rating callouts in loops, Premise-to-ServicePoint hierarchy checks.
|
|
116
|
+
- **Public Sector Solutions (`public-sector-solutions`):** Citizen data isolation, Guest User security, BRE Decision Matrix coverage.
|
|
117
|
+
|
|
118
|
+
### Examples: Using AskYARA with Industry Clouds
|
|
119
|
+
|
|
120
|
+
AskYARA **auto-detects** industry metadata (e.g. `FinServ__`, `HealthCloudGA__`, `omniDataTransforms/`, `SalesAgreement`). You can also specify packs explicitly:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Verify detected industry clouds and active rule packs
|
|
124
|
+
askyara doctor
|
|
125
|
+
|
|
126
|
+
# Review local changes with a specific industry pack
|
|
127
|
+
askyara review --pack omnistudio
|
|
128
|
+
|
|
129
|
+
# Audit repository against Financial Services Cloud rules
|
|
130
|
+
askyara audit --pack financial-services-cloud
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**In your Coding Agent (Cursor, Claude Code, Copilot, Antigravity):**
|
|
134
|
+
- *OmniStudio:* `/askyara "Review the newly added ClaimSubmission Integration Procedure and DataRaptors for scalability"`
|
|
135
|
+
- *Financial Services:* `/askyara-review` (evaluates Rollup By Lookup limits and CIB ethical walls)
|
|
136
|
+
- *Health Cloud:* `/askyara-security` (verifies zero PHI leakage and FHIR CodeSet compliance)
|
|
137
|
+
- *Manufacturing:* `/askyara-review` (verifies forecast updates are dispatched to Data Processing Engine jobs)
|
|
138
|
+
|
|
139
|
+
See the full [Salesforce Industries Guide](docs/product/industries-guide.md) for detailed checklists and finding examples.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
Licensed under the [Apache License, Version 2.0](LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
askyara/__init__.py,sha256=QhwLNnm1lwMkgF8YdagUbL-5-7OO-zMJ-q8F8Nt1C4k,74
|
|
2
|
+
askyara/cli.py,sha256=YESWmekjJtAhH2lQxCxLKibVlmV8gkHHNtzdG5qEszg,3436
|
|
3
|
+
askyara/core/__init__.py,sha256=NjMpnEQeA6kEGb65IFiediUTzgetFgZ-DA7eK8fyxPA,27
|
|
4
|
+
askyara/core/command_registry.py,sha256=ynoYEQk8T3YoMF6la-1Lfb4ScEkL35rv7UWSaQNzq7c,853
|
|
5
|
+
askyara/core/finding_model.py,sha256=a2bbKMlpJitAGpmT_yRvRfOZmcvxQsLJtna9NAfrwQQ,2543
|
|
6
|
+
askyara/core/policy_engine.py,sha256=ws61GFLorm2OZWq3pdm5znNN2cXgIh5LYXA4KiYFdCA,2351
|
|
7
|
+
askyara/core/report_formatter.py,sha256=V7J5N3gLgkA3QBOlmb8MU0WMte2Y39BU9fXQDdno_d4,1416
|
|
8
|
+
askyara/installer/__init__.py,sha256=TU42vE3y4Thn0dy-E2uHMQN5JNu2NLRzCl9KZfOcIZk,25
|
|
9
|
+
askyara/installer/doctor.py,sha256=M4j-Bldr5EgxE9801eC2bL7rhwnXxdkhquyqy7f5-Ew,2172
|
|
10
|
+
askyara/installer/manager.py,sha256=Nq3OcQD3aAjPmI-wUBWZdbOYgld0368Pq9g6KauZSbY,1901
|
|
11
|
+
askyara/tools/__init__.py,sha256=x-_uHwpCw4rm5fmPY88EHt8Eo8HQylCK8N6ffCoPN3s,38
|
|
12
|
+
askyara/tools/git_diff.py,sha256=XLHhbOWTIzd-xRQlu8dFEP34PQVHKb-IzFDlKv7rg-A,1348
|
|
13
|
+
askyara/tools/project_detector.py,sha256=_sZy-Uv5Ier7GnSHxs9pLNLkpXcZwls8MvFFBNYEcYw,6360
|
|
14
|
+
askyara/tools/redaction.py,sha256=ImxaGVDA-ZOwg9SLxe8PV6c9sLOOXi0Ql_Kgpr-xBbw,1136
|
|
15
|
+
askyara/tools/tool_registry.py,sha256=ySvH4cB9WOCuS_9Qk9rMLH0sSCP6_YCi7fN7hMlkJXQ,1043
|
|
16
|
+
askyara-0.1.0.dist-info/METADATA,sha256=7dUfWiLmh801ET7e2q_2-mBbMDN7o06CBto-v-HV6NI,6897
|
|
17
|
+
askyara-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
askyara-0.1.0.dist-info/entry_points.txt,sha256=UzbmEN0237DlgLj3cG4WUcKySEu7iBSnQLU51fHIaHM,45
|
|
19
|
+
askyara-0.1.0.dist-info/licenses/LICENSE,sha256=FcUyRljJ99LLfqP1fotCeJEMXKYYQEzf2ufgjHtHhTg,5236
|
|
20
|
+
askyara-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship.
|
|
43
|
+
|
|
44
|
+
"Contribution" shall mean any work of authorship, including
|
|
45
|
+
the original version of the Work and any modifications or additions
|
|
46
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
47
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
48
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
49
|
+
the copyright owner.
|
|
50
|
+
|
|
51
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
52
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
53
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
54
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
55
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
56
|
+
Work and such Derivative Works in Source or Object form.
|
|
57
|
+
|
|
58
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
59
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
60
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
61
|
+
patent license to make, have made, use, offer to sell, sell, import,
|
|
62
|
+
and otherwise transfer the Work.
|
|
63
|
+
|
|
64
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
65
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
66
|
+
modifications, and in Source or Object form, provided that You
|
|
67
|
+
meet the following conditions:
|
|
68
|
+
|
|
69
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
70
|
+
Works a copy of this License; and
|
|
71
|
+
(b) You must cause any modified files to carry prominent notices
|
|
72
|
+
stating that You changed the files; and
|
|
73
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
74
|
+
that You distribute, all copyright, patent, trademark, and
|
|
75
|
+
attribution notices from the Source form of the Work; and
|
|
76
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
77
|
+
distribution, then any Derivative Works that You distribute must
|
|
78
|
+
include a readable copy of the attribution notices contained
|
|
79
|
+
within such NOTICE file.
|
|
80
|
+
|
|
81
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
82
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
83
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
84
|
+
this License, without any additional terms or conditions.
|
|
85
|
+
|
|
86
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
87
|
+
names, trademarks, service marks, or product names of the Licensor.
|
|
88
|
+
|
|
89
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
90
|
+
agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
|
|
91
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
|
|
92
|
+
|
|
93
|
+
8. Limitation of Liability. In no event and under no legal theory
|
|
94
|
+
shall any Contributor be liable to You for damages, including any
|
|
95
|
+
direct, indirect, special, incidental, or consequential damages.
|
|
96
|
+
|
|
97
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
98
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
99
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
100
|
+
or other liability obligations.
|