maestro-case-kit 0.1.0__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.
- maestro_case_kit-0.1.0/.gitignore +111 -0
- maestro_case_kit-0.1.0/CONTRIBUTING.md +44 -0
- maestro_case_kit-0.1.0/PKG-INFO +65 -0
- maestro_case_kit-0.1.0/README.md +56 -0
- maestro_case_kit-0.1.0/SKILL.md +89 -0
- maestro_case_kit-0.1.0/pyproject.toml +26 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/__init__.py +9 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/cli.py +178 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/contribution.py +83 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/data/knowledge.json +187 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/knowledge.py +150 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/mcp_server.py +94 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/tools.py +106 -0
- maestro_case_kit-0.1.0/src/maestro_case_kit/validators.py +264 -0
- maestro_case_kit-0.1.0/tests/test_check_df.py +45 -0
- maestro_case_kit-0.1.0/tests/test_check_spawn.py +47 -0
- maestro_case_kit-0.1.0/tests/test_cli_explain.py +53 -0
- maestro_case_kit-0.1.0/tests/test_cli_lint.py +50 -0
- maestro_case_kit-0.1.0/tests/test_contribution.py +64 -0
- maestro_case_kit-0.1.0/tests/test_knowledge.py +84 -0
- maestro_case_kit-0.1.0/tests/test_mcp_server.py +75 -0
- maestro_case_kit-0.1.0/tests/test_tools.py +45 -0
- maestro_case_kit-0.1.0/tests/test_validators.py +134 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
ENV/
|
|
14
|
+
|
|
15
|
+
# Environment variables
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
!.env.example
|
|
19
|
+
|
|
20
|
+
# IDE
|
|
21
|
+
.idea/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
*~
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# Testing
|
|
31
|
+
.pytest_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
htmlcov/
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
|
|
36
|
+
# Databases (local dev)
|
|
37
|
+
*.db-shm
|
|
38
|
+
*.db-wal
|
|
39
|
+
ruvector.db
|
|
40
|
+
|
|
41
|
+
# Swarm state (ephemeral)
|
|
42
|
+
.swarm/
|
|
43
|
+
|
|
44
|
+
# Claude memory (session-specific)
|
|
45
|
+
.claude/memory.db
|
|
46
|
+
|
|
47
|
+
# Agent-OS session-persistence DB (churns per checkpoint; dir tracked via .keep)
|
|
48
|
+
.agent-os/memory/project_memory.db
|
|
49
|
+
__pycache__/
|
|
50
|
+
|
|
51
|
+
# UiPath solution per-user debug-overwrite state (generated by `solution project add`)
|
|
52
|
+
maestro_case/clearflow-solution/userProfile/
|
|
53
|
+
|
|
54
|
+
# Node
|
|
55
|
+
node_modules/
|
|
56
|
+
ui/node_modules/
|
|
57
|
+
ui/.next/
|
|
58
|
+
ui/out/
|
|
59
|
+
|
|
60
|
+
# Build artifacts
|
|
61
|
+
*.nupkg
|
|
62
|
+
.uipath/
|
|
63
|
+
|
|
64
|
+
# Coded-agent `uip codedagent init` scaffolding (regenerated per dir; not source)
|
|
65
|
+
agents/*/.agent/
|
|
66
|
+
agents/*/.claude/
|
|
67
|
+
agents/*/AGENTS.md
|
|
68
|
+
agents/*/CLAUDE.md
|
|
69
|
+
agents/*/main.mermaid
|
|
70
|
+
|
|
71
|
+
# UiPath skills repo (cloned locally, symlinked into .claude/skills etc.)
|
|
72
|
+
.uipath-skills/
|
|
73
|
+
|
|
74
|
+
# Installed UiPath skills (upstream content, sync via scripts/sync-uipath-skills.sh)
|
|
75
|
+
.claude/skills/uipath-*
|
|
76
|
+
.agents/skills/uipath-*
|
|
77
|
+
.github/skills/uipath-*
|
|
78
|
+
|
|
79
|
+
# Claude-flow runtime state
|
|
80
|
+
.claude-flow/
|
|
81
|
+
|
|
82
|
+
# Caches
|
|
83
|
+
.cache/
|
|
84
|
+
|
|
85
|
+
# Logs
|
|
86
|
+
*.log
|
|
87
|
+
scripts/logs/
|
|
88
|
+
.claude-flow/logs/
|
|
89
|
+
|
|
90
|
+
# Reference-only assets — never committed (images, PDFs, captured docs, archives).
|
|
91
|
+
# These live under knowledge/ for local reference only.
|
|
92
|
+
*.pdf
|
|
93
|
+
*.png
|
|
94
|
+
*.jpg
|
|
95
|
+
*.jpeg
|
|
96
|
+
*.gif
|
|
97
|
+
*.webp
|
|
98
|
+
# Exception: the architecture diagram is a deliberate committed submission asset
|
|
99
|
+
# (rendered from docs/images/architecture.svg), embedded in README + Devpost.
|
|
100
|
+
!docs/images/architecture.png
|
|
101
|
+
!docs/images/architecture.gif
|
|
102
|
+
*.zip
|
|
103
|
+
knowledge/new_knowledge/
|
|
104
|
+
knowledge/screenshots/studio_web_errors/
|
|
105
|
+
knowledge/agent-harness.zip
|
|
106
|
+
|
|
107
|
+
# Secret-bearing config — NEVER committed (MCP server API keys, test configs).
|
|
108
|
+
# Tracked previously and purged from history 2026-05-31; keys rotated.
|
|
109
|
+
.mcp.json
|
|
110
|
+
**/test_config.py
|
|
111
|
+
tests/unit/test_config.py
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Contributing to Maestro Case Kit
|
|
2
|
+
|
|
3
|
+
The knowledge layer is the moat — it stays valuable only if entries are accurate,
|
|
4
|
+
version-stamped, and free of any real-world identifiers. Every contribution runs
|
|
5
|
+
through an automated **schema + IP-safety gate** before it can land.
|
|
6
|
+
|
|
7
|
+
## Add or update a knowledge entry
|
|
8
|
+
|
|
9
|
+
Entries live in [`src/maestro_case_kit/data/knowledge.json`](src/maestro_case_kit/data/knowledge.json).
|
|
10
|
+
Each entry has this shape:
|
|
11
|
+
|
|
12
|
+
| Field | Required | Notes |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `id` | yes | Stable, unique, UPPER-KEBAB (e.g. `MC-SPAWN-QEM-400300`). |
|
|
15
|
+
| `kind` | yes | `runtime` / `data-fabric` / `hitl` / `cli` / `packaging` / `context-grounding` / ... |
|
|
16
|
+
| `title` | yes | One line. |
|
|
17
|
+
| `surface` | yes | Where it bites (e.g. `Maestro Case spawn JobArguments`). |
|
|
18
|
+
| `symptom` | yes | What the author observes. |
|
|
19
|
+
| `error_signatures` | list | Raw signatures `explain` matches on (`400300`, `still being indexed`); `[]` if silent. |
|
|
20
|
+
| `cause` | yes | Why it happens. |
|
|
21
|
+
| `fix` | yes | The proven workaround. |
|
|
22
|
+
| `proven_on` | yes | Platform/CLI version the behavior was confirmed on (e.g. `1.0.21`). |
|
|
23
|
+
| `resolved_in` | optional | Set when UiPath ships a fix — the entry then drops from active guidance. |
|
|
24
|
+
| `severity` | yes | `high` / `medium` / `low`. |
|
|
25
|
+
| `references` | list | Repo-relative paths or doc URLs. |
|
|
26
|
+
|
|
27
|
+
**Freshness is a feature.** Do not delete a fixed entry — set `resolved_in` so the
|
|
28
|
+
history survives and `explain --include-resolved` can still surface it.
|
|
29
|
+
|
|
30
|
+
**IP-safety is zero-tolerance.** No real company, product, patient, or claim names in
|
|
31
|
+
any field. Use generic or fictional placeholders.
|
|
32
|
+
|
|
33
|
+
## Run the gate locally
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Validate the bundled knowledge layer (schema + duplicate ids):
|
|
37
|
+
maestro-case validate-knowledge
|
|
38
|
+
|
|
39
|
+
# Validate a file against your own denylist (newline-delimited tokens, # comments ok):
|
|
40
|
+
maestro-case validate-knowledge --file path/to/knowledge.json --denylist-file denylist.txt
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A non-zero exit means the gate found problems — fix them before opening a PR. Add a
|
|
44
|
+
test for any new validator rule or behavior; `pytest` must be green.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maestro-case-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-native knowledge layer + offline, credential-free static validators for UiPath Maestro Case footguns.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: agent-skills,case-management,linter,maestro,mcp,uipath
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Maestro Case Kit
|
|
11
|
+
|
|
12
|
+
Offline, credential-free **knowledge + static validators** for UiPath **Maestro Case /
|
|
13
|
+
Data Fabric / Action Center** footguns. One define-once source, four artifacts: a Go-free
|
|
14
|
+
Python **CLI**, an **MCP server**, a Claude Code **skill**, and an OpenClaw **skill**.
|
|
15
|
+
|
|
16
|
+
> Built from behaviors discovered while running a multi-stakeholder crisis case end-to-end
|
|
17
|
+
> on UiPath Automation Cloud. The orchestration tier *above* the canvas is unserved by
|
|
18
|
+
> official tooling; this kit makes the hard-won knowledge installable and agent-native.
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
- UiPath's coding-agent MCP is a single catch-all `run_command` shell — not typed tools.
|
|
23
|
+
- Maestro Case error codes (`400300`, `160009`, `170015`, ...) return zero search results.
|
|
24
|
+
- Caseplan edits can be silently inert; Data Fabric fields can silently vanish on insert.
|
|
25
|
+
|
|
26
|
+
This kit encodes those footguns as a **version-stamped knowledge layer** + **CI linters**
|
|
27
|
+
that run with **no UiPath login**.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pipx install maestro-case-kit # CLI: maestro-case ; MCP: maestro-case-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Use
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
maestro-case explain 400300 # error code -> proven cause + fix (offline)
|
|
39
|
+
maestro-case lint path/to/caseplan-dir # static V20 lint (stale .bpmn, no start event, ...)
|
|
40
|
+
maestro-case check-spawn path/to/caseplan-dir # =datafabric.qem in spawn inputs (400300)
|
|
41
|
+
maestro-case check-df entity-spec.json # Data Fabric underscore-drop / reserved id
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Every command takes `--json` and exits non-zero when it has a finding, so it drops
|
|
45
|
+
straight into CI. See [`SKILL.md`](SKILL.md) for agent-host usage and recipes.
|
|
46
|
+
|
|
47
|
+
## MCP server
|
|
48
|
+
|
|
49
|
+
`maestro-case-mcp` speaks newline-delimited JSON-RPC over stdio (no third-party MCP SDK
|
|
50
|
+
dependency) and exposes typed tools: `maestro_case_explain`, `maestro_case_lint`,
|
|
51
|
+
`maestro_case_check_spawn`, `maestro_case_check_df`. Register it with any MCP host.
|
|
52
|
+
|
|
53
|
+
## One source, many harnesses
|
|
54
|
+
|
|
55
|
+
`SKILL.md` is the define-once skill source. Fan it out to other coding-agent runtimes
|
|
56
|
+
(Cursor, Codex, Gemini, Copilot, OpenClaw/ClawHub) with a skills converter — e.g.
|
|
57
|
+
`/polyskill` or `npx skills add`. The CLI and MCP server are generated from the same
|
|
58
|
+
shared tool registry (`maestro_case_kit/tools.py`), so a fix lands once and every surface
|
|
59
|
+
inherits it.
|
|
60
|
+
|
|
61
|
+
## Knowledge entries & contributions
|
|
62
|
+
|
|
63
|
+
Entries live in `maestro_case_kit/data/knowledge.json`, each stamped with the version it
|
|
64
|
+
was proven on and an optional `resolved_in`. Contributions run through an automated
|
|
65
|
+
IP-safety + schema gate (see `CONTRIBUTING.md`). License: MIT.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Maestro Case Kit
|
|
2
|
+
|
|
3
|
+
Offline, credential-free **knowledge + static validators** for UiPath **Maestro Case /
|
|
4
|
+
Data Fabric / Action Center** footguns. One define-once source, four artifacts: a Go-free
|
|
5
|
+
Python **CLI**, an **MCP server**, a Claude Code **skill**, and an OpenClaw **skill**.
|
|
6
|
+
|
|
7
|
+
> Built from behaviors discovered while running a multi-stakeholder crisis case end-to-end
|
|
8
|
+
> on UiPath Automation Cloud. The orchestration tier *above* the canvas is unserved by
|
|
9
|
+
> official tooling; this kit makes the hard-won knowledge installable and agent-native.
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
- UiPath's coding-agent MCP is a single catch-all `run_command` shell — not typed tools.
|
|
14
|
+
- Maestro Case error codes (`400300`, `160009`, `170015`, ...) return zero search results.
|
|
15
|
+
- Caseplan edits can be silently inert; Data Fabric fields can silently vanish on insert.
|
|
16
|
+
|
|
17
|
+
This kit encodes those footguns as a **version-stamped knowledge layer** + **CI linters**
|
|
18
|
+
that run with **no UiPath login**.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pipx install maestro-case-kit # CLI: maestro-case ; MCP: maestro-case-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Use
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
maestro-case explain 400300 # error code -> proven cause + fix (offline)
|
|
30
|
+
maestro-case lint path/to/caseplan-dir # static V20 lint (stale .bpmn, no start event, ...)
|
|
31
|
+
maestro-case check-spawn path/to/caseplan-dir # =datafabric.qem in spawn inputs (400300)
|
|
32
|
+
maestro-case check-df entity-spec.json # Data Fabric underscore-drop / reserved id
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Every command takes `--json` and exits non-zero when it has a finding, so it drops
|
|
36
|
+
straight into CI. See [`SKILL.md`](SKILL.md) for agent-host usage and recipes.
|
|
37
|
+
|
|
38
|
+
## MCP server
|
|
39
|
+
|
|
40
|
+
`maestro-case-mcp` speaks newline-delimited JSON-RPC over stdio (no third-party MCP SDK
|
|
41
|
+
dependency) and exposes typed tools: `maestro_case_explain`, `maestro_case_lint`,
|
|
42
|
+
`maestro_case_check_spawn`, `maestro_case_check_df`. Register it with any MCP host.
|
|
43
|
+
|
|
44
|
+
## One source, many harnesses
|
|
45
|
+
|
|
46
|
+
`SKILL.md` is the define-once skill source. Fan it out to other coding-agent runtimes
|
|
47
|
+
(Cursor, Codex, Gemini, Copilot, OpenClaw/ClawHub) with a skills converter — e.g.
|
|
48
|
+
`/polyskill` or `npx skills add`. The CLI and MCP server are generated from the same
|
|
49
|
+
shared tool registry (`maestro_case_kit/tools.py`), so a fix lands once and every surface
|
|
50
|
+
inherits it.
|
|
51
|
+
|
|
52
|
+
## Knowledge entries & contributions
|
|
53
|
+
|
|
54
|
+
Entries live in `maestro_case_kit/data/knowledge.json`, each stamped with the version it
|
|
55
|
+
was proven on and an optional `resolved_in`. Contributions run through an automated
|
|
56
|
+
IP-safety + schema gate (see `CONTRIBUTING.md`). License: MIT.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maestro-case-kit
|
|
3
|
+
description: >-
|
|
4
|
+
Offline, credential-free knowledge + static validators for UiPath Maestro Case
|
|
5
|
+
footguns. Explain cryptic error codes (400300, 160009, ...) and lint caseplans,
|
|
6
|
+
spawn inputs, and Data Fabric specs in CI — no UiPath login. Use when authoring,
|
|
7
|
+
debugging, or reviewing UiPath Maestro Case / Data Fabric / Action Center work.
|
|
8
|
+
license: MIT
|
|
9
|
+
metadata:
|
|
10
|
+
homepage: https://github.com/mlbrilliance/cascadecare-network-command
|
|
11
|
+
openclaw: true
|
|
12
|
+
allowed-tools:
|
|
13
|
+
- Bash
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Maestro Case Kit
|
|
17
|
+
|
|
18
|
+
A living, version-stamped knowledge layer over undocumented UiPath **Maestro Case /
|
|
19
|
+
Data Fabric / Action Center** footguns, plus credential-free static validators. The
|
|
20
|
+
v1 surface needs **no UiPath login** — it runs offline and in CI. The same surface
|
|
21
|
+
is exposed three ways from one source: a `maestro-case` CLI, an MCP server
|
|
22
|
+
(`maestro-case-mcp`, stdio), and this skill.
|
|
23
|
+
|
|
24
|
+
## When to use
|
|
25
|
+
|
|
26
|
+
- An agent or developer hits a cryptic UiPath error code and search returns nothing.
|
|
27
|
+
- Before packing/deploying a Maestro Case, to catch inert-edit footguns in CI.
|
|
28
|
+
- Authoring Data Fabric entities, to avoid silent field-drops.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install maestro-case-kit # or: pip install maestro-case-kit
|
|
34
|
+
# MCP server (stdio) — register with your agent host:
|
|
35
|
+
# command: maestro-case-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands (agent-native — every command takes --json and exits non-zero on findings)
|
|
39
|
+
|
|
40
|
+
- **Explain an error or footgun** (offline knowledge oracle):
|
|
41
|
+
```bash
|
|
42
|
+
maestro-case explain 400300 # error code
|
|
43
|
+
maestro-case explain underscore # keyword
|
|
44
|
+
maestro-case explain 160009 --json # structured, for agents/CI
|
|
45
|
+
```
|
|
46
|
+
- **Lint a caseplan directory** (stale .bpmn, missing start event, dup output vars, bad V20 expressions):
|
|
47
|
+
```bash
|
|
48
|
+
maestro-case lint path/to/caseplan-dir --json
|
|
49
|
+
```
|
|
50
|
+
- **Check spawn fan-out** (=datafabric.qem in spawn inputs → runtime 400300):
|
|
51
|
+
```bash
|
|
52
|
+
maestro-case check-spawn path/to/caseplan-dir
|
|
53
|
+
```
|
|
54
|
+
- **Check a Data Fabric entity spec** (underscore silent-drop, reserved `id`):
|
|
55
|
+
```bash
|
|
56
|
+
maestro-case check-df entity-spec.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Recipe — wire the validators into CI
|
|
60
|
+
|
|
61
|
+
Run the three validators in a pre-deploy job; non-zero exit fails the build before a
|
|
62
|
+
broken caseplan or a data-losing entity ships:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
maestro-case lint "$CASEPLAN_DIR" \
|
|
66
|
+
&& maestro-case check-spawn "$CASEPLAN_DIR" \
|
|
67
|
+
&& maestro-case check-df "$ENTITY_SPEC"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Recipe — explain a lint finding
|
|
71
|
+
|
|
72
|
+
Lint findings carry a rule id that maps to a knowledge entry; pass it to `explain`
|
|
73
|
+
for the full cause + fix:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
maestro-case lint "$CASEPLAN_DIR" --json | jq -r '.[].entry_id' | sort -u \
|
|
77
|
+
| xargs -I{} maestro-case explain {}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Freshness
|
|
81
|
+
|
|
82
|
+
Every knowledge entry is stamped with the platform/CLI version it was proven on. When
|
|
83
|
+
UiPath fixes a footgun, the entry is marked `resolved_in` and drops from active
|
|
84
|
+
guidance — pass `--include-resolved` to `explain` to see history.
|
|
85
|
+
|
|
86
|
+
## MCP tools
|
|
87
|
+
|
|
88
|
+
The MCP server exposes the same surface as typed tools: `maestro_case_explain`,
|
|
89
|
+
`maestro_case_lint`, `maestro_case_check_spawn`, `maestro_case_check_df`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "maestro-case-kit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agent-native knowledge layer + offline, credential-free static validators for UiPath Maestro Case footguns."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["uipath", "maestro", "case-management", "linter", "mcp", "agent-skills"]
|
|
9
|
+
dependencies = []
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
maestro-case = "maestro_case_kit.cli:main"
|
|
13
|
+
maestro-case-mcp = "maestro_case_kit.mcp_server:main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/maestro_case_kit"]
|
|
21
|
+
|
|
22
|
+
# Standalone test config (when the package is run on its own, post-extraction).
|
|
23
|
+
# The repo-level gate wires this package in via the root pyproject's pythonpath.
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
pythonpath = ["src"]
|
|
26
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Maestro Case Kit — agent-native knowledge + offline validators for UiPath Maestro Case.
|
|
2
|
+
|
|
3
|
+
A living, version-stamped knowledge layer over the undocumented Maestro Case /
|
|
4
|
+
Data Fabric / Action Center footguns surfaced while building a multi-stakeholder
|
|
5
|
+
crisis case, plus credential-free static validators that run in CI. No UiPath
|
|
6
|
+
login required for the v1 surface.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""`maestro-case` CLI — agent-native, offline access to the knowledge layer.
|
|
2
|
+
|
|
3
|
+
v1 surface: `explain`. Validators (`lint`, `check-spawn`, `check-df`) attach to the
|
|
4
|
+
same parser in later slices. Every subcommand supports ``--json`` for agent/CI use
|
|
5
|
+
and exits non-zero when it has a finding to report.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import IO
|
|
15
|
+
|
|
16
|
+
from . import __version__, contribution, knowledge, validators
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _print_human(entries: list[knowledge.KnowledgeEntry], stream: IO[str]) -> None:
|
|
20
|
+
for e in entries:
|
|
21
|
+
signals = ", ".join(e.error_signatures) or "(no error code — silent behavior)"
|
|
22
|
+
resolved = f" resolved_in: {e.resolved_in}" if e.resolved_in else ""
|
|
23
|
+
print(f"[{e.id}] {e.title}", file=stream)
|
|
24
|
+
print(f" surface: {e.surface}", file=stream)
|
|
25
|
+
print(f" signals: {signals}", file=stream)
|
|
26
|
+
print(f" cause: {e.cause}", file=stream)
|
|
27
|
+
print(f" fix: {e.fix}", file=stream)
|
|
28
|
+
print(f" proven_on: {e.proven_on}{resolved}", file=stream)
|
|
29
|
+
if e.references:
|
|
30
|
+
print(f" refs: {', '.join(e.references)}", file=stream)
|
|
31
|
+
print("", file=stream)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cmd_explain(args: argparse.Namespace) -> int:
|
|
35
|
+
hits = knowledge.find(args.query, include_resolved=args.include_resolved)
|
|
36
|
+
if args.json:
|
|
37
|
+
print(json.dumps([e.to_dict() for e in hits], indent=2))
|
|
38
|
+
return 0 if hits else 1
|
|
39
|
+
if not hits:
|
|
40
|
+
print(
|
|
41
|
+
f"No known Maestro Case footgun matches {args.query!r}. "
|
|
42
|
+
f"Try an error code (e.g. 400300) or a keyword (e.g. underscore, gate, deploy).",
|
|
43
|
+
file=sys.stderr,
|
|
44
|
+
)
|
|
45
|
+
return 1
|
|
46
|
+
_print_human(hits, sys.stdout)
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _emit_findings(findings: list[validators.Finding], as_json: bool, ok_label: str) -> int:
|
|
51
|
+
if as_json:
|
|
52
|
+
print(json.dumps([f.to_dict() for f in findings], indent=2))
|
|
53
|
+
return 1 if findings else 0
|
|
54
|
+
if not findings:
|
|
55
|
+
print(f"OK — {ok_label}")
|
|
56
|
+
return 0
|
|
57
|
+
for f in findings:
|
|
58
|
+
suffix = f" (explain: {f.entry_id})" if f.entry_id else ""
|
|
59
|
+
where = f" @ {f.location}" if f.location else ""
|
|
60
|
+
print(f"[{f.severity.upper()}] {f.rule_id}: {f.message}{where}{suffix}")
|
|
61
|
+
print(f"\n{len(findings)} finding(s).")
|
|
62
|
+
return 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _cmd_lint(args: argparse.Namespace) -> int:
|
|
66
|
+
findings = validators.lint_caseplan(args.caseplan_dir)
|
|
67
|
+
return _emit_findings(findings, args.json, f"no Maestro Case footguns in {args.caseplan_dir}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _cmd_check_spawn(args: argparse.Namespace) -> int:
|
|
71
|
+
findings = validators.check_spawn_fanout(args.caseplan_dir)
|
|
72
|
+
return _emit_findings(findings, args.json, f"no qem spawn-fanout issues in {args.caseplan_dir}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _cmd_check_df(args: argparse.Namespace) -> int:
|
|
76
|
+
findings = validators.validate_df_entity(args.spec)
|
|
77
|
+
return _emit_findings(findings, args.json, f"no Data Fabric field-name traps in {args.spec}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _cmd_validate_knowledge(args: argparse.Namespace) -> int:
|
|
81
|
+
denylist: list[str] = []
|
|
82
|
+
if args.denylist_file:
|
|
83
|
+
for line in Path(args.denylist_file).read_text(encoding="utf-8").splitlines():
|
|
84
|
+
token = line.strip()
|
|
85
|
+
if token and not token.startswith("#"):
|
|
86
|
+
denylist.append(token)
|
|
87
|
+
data: object = args.file or {"entries": [e.to_dict() for e in knowledge.load_entries()]}
|
|
88
|
+
problems = contribution.validate_knowledge(data, denylist)
|
|
89
|
+
if args.json:
|
|
90
|
+
print(json.dumps(problems, indent=2))
|
|
91
|
+
return 1 if problems else 0
|
|
92
|
+
if not problems:
|
|
93
|
+
print("OK — knowledge passes the contribution gate.")
|
|
94
|
+
return 0
|
|
95
|
+
for problem in problems:
|
|
96
|
+
print(f"[GATE] {problem}")
|
|
97
|
+
print(f"\n{len(problems)} problem(s).")
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
102
|
+
parser = argparse.ArgumentParser(
|
|
103
|
+
prog="maestro-case",
|
|
104
|
+
description="Knowledge + offline validators for UiPath Maestro Case footguns.",
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
107
|
+
sub = parser.add_subparsers(dest="command")
|
|
108
|
+
|
|
109
|
+
explain = sub.add_parser(
|
|
110
|
+
"explain",
|
|
111
|
+
help="Explain a UiPath Maestro Case error code or footgun from the knowledge layer.",
|
|
112
|
+
)
|
|
113
|
+
explain.add_argument(
|
|
114
|
+
"query",
|
|
115
|
+
help="An error code/signature (e.g. 400300, 160009) or a keyword (e.g. underscore, deploy).",
|
|
116
|
+
)
|
|
117
|
+
explain.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
118
|
+
explain.add_argument(
|
|
119
|
+
"--include-resolved",
|
|
120
|
+
action="store_true",
|
|
121
|
+
help="Include entries marked resolved in a later platform version.",
|
|
122
|
+
)
|
|
123
|
+
explain.set_defaults(func=_cmd_explain)
|
|
124
|
+
|
|
125
|
+
lint = sub.add_parser(
|
|
126
|
+
"lint",
|
|
127
|
+
help="Statically lint a caseplan directory for known footguns (offline, no login).",
|
|
128
|
+
)
|
|
129
|
+
lint.add_argument(
|
|
130
|
+
"caseplan_dir",
|
|
131
|
+
help="Path to a directory containing caseplan.json (and ideally caseplan.json.bpmn).",
|
|
132
|
+
)
|
|
133
|
+
lint.add_argument("--json", action="store_true", help="Emit structured JSON findings.")
|
|
134
|
+
lint.set_defaults(func=_cmd_lint)
|
|
135
|
+
|
|
136
|
+
check_spawn = sub.add_parser(
|
|
137
|
+
"check-spawn",
|
|
138
|
+
help="Flag =datafabric.qem expressions in spawn inputs (fail at runtime, 400300).",
|
|
139
|
+
)
|
|
140
|
+
check_spawn.add_argument("caseplan_dir", help="Path to a directory containing caseplan.json.")
|
|
141
|
+
check_spawn.add_argument("--json", action="store_true", help="Emit structured JSON findings.")
|
|
142
|
+
check_spawn.set_defaults(func=_cmd_check_spawn)
|
|
143
|
+
|
|
144
|
+
check_df = sub.add_parser(
|
|
145
|
+
"check-df",
|
|
146
|
+
help="Lint a Data Fabric entity/field spec for silent-drop and reserved-name traps.",
|
|
147
|
+
)
|
|
148
|
+
check_df.add_argument("spec", help="Path to a JSON entity spec ({\"fields\": [...]}).")
|
|
149
|
+
check_df.add_argument("--json", action="store_true", help="Emit structured JSON findings.")
|
|
150
|
+
check_df.set_defaults(func=_cmd_check_df)
|
|
151
|
+
|
|
152
|
+
vk = sub.add_parser(
|
|
153
|
+
"validate-knowledge",
|
|
154
|
+
help="Validate a knowledge file against the schema + an optional IP-safety denylist.",
|
|
155
|
+
)
|
|
156
|
+
vk.add_argument("--file", help="Path to a knowledge JSON file (default: the bundled layer).")
|
|
157
|
+
vk.add_argument(
|
|
158
|
+
"--denylist-file",
|
|
159
|
+
help="Optional newline-delimited file of forbidden tokens (# comments allowed).",
|
|
160
|
+
)
|
|
161
|
+
vk.add_argument("--json", action="store_true", help="Emit structured JSON problems.")
|
|
162
|
+
vk.set_defaults(func=_cmd_validate_knowledge)
|
|
163
|
+
return parser
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main(argv: list[str] | None = None) -> int:
|
|
167
|
+
parser = build_parser()
|
|
168
|
+
args = parser.parse_args(argv)
|
|
169
|
+
if not getattr(args, "command", None):
|
|
170
|
+
parser.print_help(sys.stderr)
|
|
171
|
+
return 2
|
|
172
|
+
func = args.func
|
|
173
|
+
assert callable(func)
|
|
174
|
+
return int(func(args))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__": # pragma: no cover
|
|
178
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Contribution gate — validates knowledge entries against the schema and an
|
|
2
|
+
optional IP-safety denylist, so curation (not the distribution channel) is the moat.
|
|
3
|
+
|
|
4
|
+
A contributed entry must be well-formed and free of any caller-supplied forbidden
|
|
5
|
+
tokens (e.g. real company names). The denylist is a parameter, not hardcoded, so the
|
|
6
|
+
toolkit stays a general public artifact; a host repo passes its own denylist in CI.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
REQUIRED_FIELDS: tuple[str, ...] = (
|
|
16
|
+
"id",
|
|
17
|
+
"kind",
|
|
18
|
+
"title",
|
|
19
|
+
"surface",
|
|
20
|
+
"symptom",
|
|
21
|
+
"cause",
|
|
22
|
+
"fix",
|
|
23
|
+
"proven_on",
|
|
24
|
+
"severity",
|
|
25
|
+
)
|
|
26
|
+
VALID_SEVERITY: frozenset[str] = frozenset({"high", "medium", "low"})
|
|
27
|
+
_LIST_FIELDS: tuple[str, ...] = ("error_signatures", "references")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _entry_text(entry: dict) -> str:
|
|
31
|
+
parts: list[str] = []
|
|
32
|
+
for value in entry.values():
|
|
33
|
+
if isinstance(value, str):
|
|
34
|
+
parts.append(value)
|
|
35
|
+
elif isinstance(value, list):
|
|
36
|
+
parts.extend(str(item) for item in value)
|
|
37
|
+
return " ".join(parts).lower()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_entry(entry: dict, denylist: Iterable[str] = ()) -> list[str]:
|
|
41
|
+
"""Return a list of problems with one entry; empty means it passes the gate."""
|
|
42
|
+
problems: list[str] = []
|
|
43
|
+
for field in REQUIRED_FIELDS:
|
|
44
|
+
if not entry.get(field):
|
|
45
|
+
problems.append(f"missing or empty required field: {field}")
|
|
46
|
+
severity = entry.get("severity")
|
|
47
|
+
if severity is not None and severity not in VALID_SEVERITY:
|
|
48
|
+
problems.append(f"invalid severity {severity!r} (expected one of {sorted(VALID_SEVERITY)})")
|
|
49
|
+
for field in _LIST_FIELDS:
|
|
50
|
+
if field in entry and not isinstance(entry[field], list):
|
|
51
|
+
problems.append(f"{field} must be a list")
|
|
52
|
+
text = _entry_text(entry)
|
|
53
|
+
entry_id = entry.get("id", "<unknown>")
|
|
54
|
+
for token in denylist:
|
|
55
|
+
if token and token.lower() in text:
|
|
56
|
+
problems.append(f"forbidden token {token!r} in entry {entry_id}")
|
|
57
|
+
return problems
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _coerce_entries(data: object) -> list[dict]:
|
|
61
|
+
if isinstance(data, (str, Path)):
|
|
62
|
+
data = json.loads(Path(data).read_text(encoding="utf-8"))
|
|
63
|
+
if isinstance(data, dict):
|
|
64
|
+
entries = data.get("entries", [])
|
|
65
|
+
else:
|
|
66
|
+
entries = data
|
|
67
|
+
return [e for e in entries if isinstance(e, dict)] if isinstance(entries, list) else []
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def validate_knowledge(data: object, denylist: Iterable[str] = ()) -> list[str]:
|
|
71
|
+
"""Validate a knowledge collection (path, {"entries": [...]}, or a list)."""
|
|
72
|
+
entries = _coerce_entries(data)
|
|
73
|
+
denylist = tuple(denylist)
|
|
74
|
+
problems: list[str] = []
|
|
75
|
+
ids: list[str] = []
|
|
76
|
+
for entry in entries:
|
|
77
|
+
problems.extend(validate_entry(entry, denylist))
|
|
78
|
+
entry_id = entry.get("id")
|
|
79
|
+
if isinstance(entry_id, str):
|
|
80
|
+
ids.append(entry_id)
|
|
81
|
+
for dup in sorted({i for i in ids if ids.count(i) > 1}):
|
|
82
|
+
problems.append(f"duplicate id: {dup}")
|
|
83
|
+
return problems
|