traceforge-toolkit 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.
- traceforge/__init__.py +72 -0
- traceforge/__main__.py +5 -0
- traceforge/_generated.py +254 -0
- traceforge/adapters/__init__.py +12 -0
- traceforge/adapters/base.py +82 -0
- traceforge/adapters/genai_otel.py +164 -0
- traceforge/adapters/mapped_json.py +297 -0
- traceforge/adapters/otel.py +220 -0
- traceforge/boundary/__init__.py +46 -0
- traceforge/boundary/data/boundary-model.joblib +0 -0
- traceforge/boundary/decode.py +87 -0
- traceforge/boundary/features.py +137 -0
- traceforge/boundary/inference.py +267 -0
- traceforge/boundary/inferencer.py +146 -0
- traceforge/classify/__init__.py +95 -0
- traceforge/classify/cmd.py +109 -0
- traceforge/classify/coding.py +213 -0
- traceforge/classify/config.py +554 -0
- traceforge/classify/core.py +266 -0
- traceforge/classify/data/binary_info.yaml +358 -0
- traceforge/classify/data/canonical_tools.yaml +251 -0
- traceforge/classify/data/effect_overrides.yaml +480 -0
- traceforge/classify/data/mcp_profiles.yaml +711 -0
- traceforge/classify/data/recommendation_rules.yaml +111 -0
- traceforge/classify/data/risk.yaml +534 -0
- traceforge/classify/data/shell_defaults.yaml +95 -0
- traceforge/classify/data/shell_rules.yaml +1192 -0
- traceforge/classify/data/tool_classifications.yaml +215 -0
- traceforge/classify/data/verb_inference.yaml +218 -0
- traceforge/classify/mcp.py +181 -0
- traceforge/classify/phases.py +75 -0
- traceforge/classify/powershell.py +93 -0
- traceforge/classify/registry.py +138 -0
- traceforge/classify/risk.py +553 -0
- traceforge/classify/rules.py +215 -0
- traceforge/classify/schema.yaml +282 -0
- traceforge/classify/shell.py +503 -0
- traceforge/classify/tools.py +91 -0
- traceforge/classify/workflow.py +32 -0
- traceforge/cli/__init__.py +42 -0
- traceforge/cli/config_cmd.py +130 -0
- traceforge/cli/detect.py +46 -0
- traceforge/cli/download_cmd.py +74 -0
- traceforge/cli/factory.py +60 -0
- traceforge/cli/gate_cmd.py +30 -0
- traceforge/cli/init_cmd.py +86 -0
- traceforge/cli/replay.py +130 -0
- traceforge/cli/runner.py +181 -0
- traceforge/cli/score.py +217 -0
- traceforge/cli/status.py +65 -0
- traceforge/cli/watch.py +297 -0
- traceforge/config/__init__.py +80 -0
- traceforge/config/defaults.py +149 -0
- traceforge/config/loader.py +239 -0
- traceforge/config/mappings.py +104 -0
- traceforge/config/models.py +579 -0
- traceforge/enricher.py +576 -0
- traceforge/formatting/__init__.py +12 -0
- traceforge/formatting/budget.py +92 -0
- traceforge/formatting/density.py +154 -0
- traceforge/gate/__init__.py +17 -0
- traceforge/gate/client.py +145 -0
- traceforge/gate/external.py +305 -0
- traceforge/gate/registry.py +108 -0
- traceforge/gate/server.py +174 -0
- traceforge/gates/__init__.py +8 -0
- traceforge/gates/pii.py +352 -0
- traceforge/gates/pii_patterns.yaml +228 -0
- traceforge/governance/__init__.py +121 -0
- traceforge/governance/assessor.py +441 -0
- traceforge/governance/budget.py +59 -0
- traceforge/governance/canonical.py +56 -0
- traceforge/governance/codec.py +414 -0
- traceforge/governance/context.py +249 -0
- traceforge/governance/drift.py +196 -0
- traceforge/governance/emitter.py +234 -0
- traceforge/governance/envelope.py +138 -0
- traceforge/governance/ifc.py +364 -0
- traceforge/governance/integrity.py +162 -0
- traceforge/governance/labeler.py +250 -0
- traceforge/governance/mcp_drift.py +328 -0
- traceforge/governance/monitor.py +539 -0
- traceforge/governance/observer.py +276 -0
- traceforge/governance/persistence.py +401 -0
- traceforge/governance/phase1.py +77 -0
- traceforge/governance/pii.py +276 -0
- traceforge/governance/pipeline.py +840 -0
- traceforge/governance/registry.py +110 -0
- traceforge/governance/results.py +183 -0
- traceforge/governance/risk_wrapper.py +113 -0
- traceforge/governance/rules.py +368 -0
- traceforge/governance/scorer.py +262 -0
- traceforge/governance/shield.py +318 -0
- traceforge/governance/state.py +466 -0
- traceforge/governance/types.py +176 -0
- traceforge/mappings/__init__.py +1 -0
- traceforge/mappings/aider.yaml +216 -0
- traceforge/mappings/aider_markdown.yaml +113 -0
- traceforge/mappings/amazonq.yaml +56 -0
- traceforge/mappings/antigravity.yaml +88 -0
- traceforge/mappings/claude.yaml +93 -0
- traceforge/mappings/cline.yaml +286 -0
- traceforge/mappings/codex.yaml +158 -0
- traceforge/mappings/continue_dev.yaml +57 -0
- traceforge/mappings/copilot.yaml +266 -0
- traceforge/mappings/copilot_markdown.yaml +72 -0
- traceforge/mappings/copilot_vscode.yaml +181 -0
- traceforge/mappings/crewai.yaml +592 -0
- traceforge/mappings/goose.yaml +128 -0
- traceforge/mappings/langgraph.yaml +165 -0
- traceforge/mappings/maf.yaml +90 -0
- traceforge/mappings/maf_transcript.yaml +99 -0
- traceforge/mappings/openai_agents.yaml +144 -0
- traceforge/mappings/opencode.yaml +473 -0
- traceforge/mappings/openhands.yaml +320 -0
- traceforge/mappings/pydantic_ai.yaml +183 -0
- traceforge/mappings/smolagents.yaml +89 -0
- traceforge/mappings/sweagent.yaml +82 -0
- traceforge/migrations/__init__.py +1 -0
- traceforge/migrations/env.py +60 -0
- traceforge/migrations/models.py +145 -0
- traceforge/migrations/runner.py +44 -0
- traceforge/migrations/script.py.mako +25 -0
- traceforge/migrations/versions/0001_initial.py +176 -0
- traceforge/migrations/versions/__init__.py +4 -0
- traceforge/models.py +29 -0
- traceforge/parsers/__init__.py +21 -0
- traceforge/parsers/aider.py +416 -0
- traceforge/parsers/base.py +226 -0
- traceforge/parsers/copilot.py +314 -0
- traceforge/phase/__init__.py +50 -0
- traceforge/phase/data/phase-model.joblib +0 -0
- traceforge/phase/data/potion-base-8M/README.md +99 -0
- traceforge/phase/data/potion-base-8M/config.json +13 -0
- traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
- traceforge/phase/data/potion-base-8M/modules.json +14 -0
- traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
- traceforge/phase/event_rows.py +107 -0
- traceforge/phase/features.py +468 -0
- traceforge/phase/inference.py +279 -0
- traceforge/phase/inferencer.py +171 -0
- traceforge/phase/segmentation.py +258 -0
- traceforge/pipeline.py +891 -0
- traceforge/preprocessors/__init__.py +34 -0
- traceforge/preprocessors/amazonq.py +224 -0
- traceforge/preprocessors/antigravity.py +116 -0
- traceforge/preprocessors/claude.py +95 -0
- traceforge/preprocessors/cline.py +36 -0
- traceforge/preprocessors/codex.py +311 -0
- traceforge/preprocessors/continue_dev.py +119 -0
- traceforge/preprocessors/copilot_vscode.py +171 -0
- traceforge/preprocessors/goose.py +156 -0
- traceforge/preprocessors/maf_transcript.py +84 -0
- traceforge/preprocessors/openai_agents.py +86 -0
- traceforge/preprocessors/opencode.py +85 -0
- traceforge/preprocessors/openhands.py +36 -0
- traceforge/preprocessors/pydantic_ai.py +62 -0
- traceforge/preprocessors/registry.py +24 -0
- traceforge/preprocessors/smolagents.py +90 -0
- traceforge/py.typed +0 -0
- traceforge/sdk/__init__.py +59 -0
- traceforge/sdk/gate_policy.py +63 -0
- traceforge/sdk/gate_types.py +140 -0
- traceforge/sdk/pipeline.py +265 -0
- traceforge/sdk/verdict.py +81 -0
- traceforge/sinks/__init__.py +23 -0
- traceforge/sinks/base.py +95 -0
- traceforge/sinks/callback.py +132 -0
- traceforge/sinks/console.py +112 -0
- traceforge/sinks/factory.py +94 -0
- traceforge/sinks/jsonl.py +125 -0
- traceforge/sinks/otel_exporter.py +212 -0
- traceforge/sinks/parquet.py +260 -0
- traceforge/sinks/s3.py +206 -0
- traceforge/sinks/sqlite_output.py +234 -0
- traceforge/sinks/webhook.py +136 -0
- traceforge/sources/__init__.py +18 -0
- traceforge/sources/auto_detect.py +173 -0
- traceforge/sources/base.py +45 -0
- traceforge/sources/file_poll.py +136 -0
- traceforge/sources/file_watch.py +221 -0
- traceforge/sources/http_poll.py +141 -0
- traceforge/sources/replay.py +63 -0
- traceforge/sources/sqlite.py +187 -0
- traceforge/sources/sse.py +198 -0
- traceforge/telemetry/__init__.py +192 -0
- traceforge/title/__init__.py +23 -0
- traceforge/title/_resolve.py +79 -0
- traceforge/title/context.py +295 -0
- traceforge/title/data/boilerplate_files.json +14 -0
- traceforge/title/heuristics.py +429 -0
- traceforge/title/hygiene.py +90 -0
- traceforge/title/inference.py +314 -0
- traceforge/title/inferencer.py +477 -0
- traceforge/title/naming.py +398 -0
- traceforge/trace.py +291 -0
- traceforge/tracking/__init__.py +30 -0
- traceforge/tracking/models.py +115 -0
- traceforge/tracking/phase_tracker.py +288 -0
- traceforge/types.py +315 -0
- traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
- traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
- traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
- traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
- traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""PowerShell command classification via tree-sitter AST analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import tree_sitter as ts
|
|
10
|
+
import tree_sitter_powershell as tsps
|
|
11
|
+
|
|
12
|
+
from traceforge.classify.core import Classification
|
|
13
|
+
from traceforge.classify.coding import CodingMechanism
|
|
14
|
+
from traceforge.classify.shell import (
|
|
15
|
+
_CommandClassification,
|
|
16
|
+
build_classification_from_commands,
|
|
17
|
+
classify_single_command,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from traceforge.classify.config import ClassificationEngine
|
|
22
|
+
|
|
23
|
+
_PS_LANGUAGE = ts.Language(tsps.language())
|
|
24
|
+
_parser = ts.Parser(_PS_LANGUAGE)
|
|
25
|
+
_Q_COMMANDS = ts.Query(_PS_LANGUAGE, "(command) @cmd")
|
|
26
|
+
_STRIP_BINARY_EXT = re.compile(r"\.(exe|cmd|bat|ps1|sh)$", re.IGNORECASE)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _extract_from_command_node(node: ts.Node) -> tuple[str, str | None, list[str]]:
|
|
30
|
+
"""Extract cmdlet/binary name, first positional arg, and parameters from a command node."""
|
|
31
|
+
name = ""
|
|
32
|
+
positionals: list[str] = []
|
|
33
|
+
parameters: list[str] = []
|
|
34
|
+
|
|
35
|
+
for child in node.children:
|
|
36
|
+
if child.type == "command_name" and child.text:
|
|
37
|
+
name = child.text.decode("utf-8").strip()
|
|
38
|
+
elif child.type == "command_elements":
|
|
39
|
+
for sub in child.children:
|
|
40
|
+
if sub.type == "generic_token" and sub.text:
|
|
41
|
+
positionals.append(sub.text.decode("utf-8"))
|
|
42
|
+
elif sub.type == "command_parameter" and sub.text:
|
|
43
|
+
parameters.append(sub.text.decode("utf-8"))
|
|
44
|
+
|
|
45
|
+
subcmd = positionals[0] if positionals else None
|
|
46
|
+
return name, subcmd, parameters
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def classify_powershell_command(
|
|
50
|
+
command: str,
|
|
51
|
+
*,
|
|
52
|
+
engine: ClassificationEngine,
|
|
53
|
+
) -> Classification:
|
|
54
|
+
"""Classify a PowerShell command string into a full Classification."""
|
|
55
|
+
if not command or not command.strip():
|
|
56
|
+
return Classification(
|
|
57
|
+
mechanism=CodingMechanism.PROCESS_SHELL,
|
|
58
|
+
effect=None,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
tree = _parser.parse(command.encode("utf-8"))
|
|
62
|
+
cursor = ts.QueryCursor(_Q_COMMANDS)
|
|
63
|
+
matches = cursor.matches(tree.root_node)
|
|
64
|
+
|
|
65
|
+
if not matches:
|
|
66
|
+
return Classification(
|
|
67
|
+
mechanism=CodingMechanism.PROCESS_SHELL,
|
|
68
|
+
effect=None,
|
|
69
|
+
capability=frozenset({"subprocess"}),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
command_results: list[_CommandClassification] = []
|
|
73
|
+
|
|
74
|
+
for _pat, captures in matches:
|
|
75
|
+
for node in captures.get("cmd", []):
|
|
76
|
+
name, subcmd, parameters = _extract_from_command_node(node)
|
|
77
|
+
if not name:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Normalize: strip path/extension for non-cmdlet binaries
|
|
81
|
+
binary = _STRIP_BINARY_EXT.sub("", os.path.basename(name).lower())
|
|
82
|
+
|
|
83
|
+
cmd_cls = classify_single_command(binary, subcmd, parameters, engine=engine)
|
|
84
|
+
command_results.append(cmd_cls)
|
|
85
|
+
|
|
86
|
+
if not command_results:
|
|
87
|
+
return Classification(
|
|
88
|
+
mechanism=CodingMechanism.PROCESS_SHELL,
|
|
89
|
+
effect=None,
|
|
90
|
+
capability=frozenset({"subprocess"}),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return build_classification_from_commands(command_results, shell_dialect="powershell")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Dimension registry — validates, extends, and queries hierarchical classification values."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Iterator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DimensionRegistry:
|
|
10
|
+
"""Registry for classification dimension values.
|
|
11
|
+
|
|
12
|
+
Manages hierarchical dot-path values across dimensions.
|
|
13
|
+
Supports validation, extension, and hierarchy queries.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self._dimensions: dict[str, set[str]] = {}
|
|
18
|
+
|
|
19
|
+
def register_dimension(self, name: str, enum_cls: type[StrEnum]) -> None:
|
|
20
|
+
"""Register a new dimension with its root enum values."""
|
|
21
|
+
if name not in self._dimensions:
|
|
22
|
+
self._dimensions[name] = set()
|
|
23
|
+
self._dimensions[name].update(v.value for v in enum_cls)
|
|
24
|
+
|
|
25
|
+
def extend_dimension(self, name: str, enum_cls: type[StrEnum]) -> None:
|
|
26
|
+
"""Extend an existing dimension with subtype values.
|
|
27
|
+
|
|
28
|
+
Validates that each subtype's parent segment is already registered.
|
|
29
|
+
"""
|
|
30
|
+
if name not in self._dimensions:
|
|
31
|
+
self._dimensions[name] = set()
|
|
32
|
+
|
|
33
|
+
for v in enum_cls:
|
|
34
|
+
value = v.value
|
|
35
|
+
parent = self.parent_of(value)
|
|
36
|
+
if parent is not None and parent not in self._dimensions[name]:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"Cannot register '{value}' in dimension '{name}': "
|
|
39
|
+
f"parent '{parent}' is not registered"
|
|
40
|
+
)
|
|
41
|
+
self._dimensions[name].add(value)
|
|
42
|
+
|
|
43
|
+
def validate(self, dimension: str, value: str) -> bool:
|
|
44
|
+
"""Check if a value is registered in a dimension."""
|
|
45
|
+
if dimension not in self._dimensions:
|
|
46
|
+
return False
|
|
47
|
+
return value in self._dimensions[dimension]
|
|
48
|
+
|
|
49
|
+
def values(self, dimension: str) -> frozenset[str]:
|
|
50
|
+
"""Get all registered values for a dimension."""
|
|
51
|
+
return frozenset(self._dimensions.get(dimension, set()))
|
|
52
|
+
|
|
53
|
+
def roots(self, dimension: str) -> frozenset[str]:
|
|
54
|
+
"""Get root (non-dotted) values for a dimension."""
|
|
55
|
+
return frozenset(v for v in self._dimensions.get(dimension, set()) if "." not in v)
|
|
56
|
+
|
|
57
|
+
def children(self, dimension: str, parent: str) -> frozenset[str]:
|
|
58
|
+
"""Get direct children of a value in a dimension."""
|
|
59
|
+
prefix = parent + "."
|
|
60
|
+
return frozenset(
|
|
61
|
+
v
|
|
62
|
+
for v in self._dimensions.get(dimension, set())
|
|
63
|
+
if v.startswith(prefix) and "." not in v[len(prefix) :]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def descendants(self, dimension: str, ancestor: str) -> frozenset[str]:
|
|
67
|
+
"""Get all descendants (any depth) of a value."""
|
|
68
|
+
prefix = ancestor + "."
|
|
69
|
+
return frozenset(v for v in self._dimensions.get(dimension, set()) if v.startswith(prefix))
|
|
70
|
+
|
|
71
|
+
def is_descendant(self, dimension: str, value: str, ancestor: str) -> bool:
|
|
72
|
+
"""Check if value descends from ancestor in the hierarchy."""
|
|
73
|
+
if value == ancestor:
|
|
74
|
+
return True
|
|
75
|
+
return value.startswith(ancestor + ".")
|
|
76
|
+
|
|
77
|
+
def dimensions(self) -> Iterator[str]:
|
|
78
|
+
"""Iterate over registered dimension names."""
|
|
79
|
+
return iter(self._dimensions)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def parent_of(value: str) -> str | None:
|
|
83
|
+
"""Get the parent segment of a dot-path value."""
|
|
84
|
+
if "." not in value:
|
|
85
|
+
return None
|
|
86
|
+
return value.rsplit(".", 1)[0]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def build_default_registry() -> DimensionRegistry:
|
|
90
|
+
"""Build the default registry with core + coding dimensions."""
|
|
91
|
+
from traceforge.classify.coding import (
|
|
92
|
+
CodingAction,
|
|
93
|
+
CodingMechanism,
|
|
94
|
+
CodingRole,
|
|
95
|
+
CodingScope,
|
|
96
|
+
ShellDialect,
|
|
97
|
+
)
|
|
98
|
+
from traceforge.classify.core import (
|
|
99
|
+
Action,
|
|
100
|
+
Capability,
|
|
101
|
+
Effect,
|
|
102
|
+
Mechanism,
|
|
103
|
+
Role,
|
|
104
|
+
Scope,
|
|
105
|
+
Structure,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
reg = DimensionRegistry()
|
|
109
|
+
|
|
110
|
+
# Core roots
|
|
111
|
+
reg.register_dimension("mechanism", Mechanism)
|
|
112
|
+
reg.register_dimension("effect", Effect)
|
|
113
|
+
reg.register_dimension("scope", Scope)
|
|
114
|
+
reg.register_dimension("role", Role)
|
|
115
|
+
reg.register_dimension("action", Action)
|
|
116
|
+
reg.register_dimension("capability", Capability)
|
|
117
|
+
reg.register_dimension("structure", Structure)
|
|
118
|
+
|
|
119
|
+
# Coding extensions
|
|
120
|
+
reg.extend_dimension("mechanism", CodingMechanism)
|
|
121
|
+
reg.extend_dimension("scope", CodingScope)
|
|
122
|
+
reg.extend_dimension("role", CodingRole)
|
|
123
|
+
reg.extend_dimension("action", CodingAction)
|
|
124
|
+
reg.register_dimension("shell_dialect", ShellDialect)
|
|
125
|
+
|
|
126
|
+
return reg
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Module-level default registry (built on first access)
|
|
130
|
+
_default_registry: DimensionRegistry | None = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_default_registry() -> DimensionRegistry:
|
|
134
|
+
"""Get the default registry (lazily built, cached)."""
|
|
135
|
+
global _default_registry
|
|
136
|
+
if _default_registry is None:
|
|
137
|
+
_default_registry = build_default_registry()
|
|
138
|
+
return _default_registry
|