@1aboveio/skills 1.0.17 → 1.0.18

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.
Files changed (91) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +167 -139
  3. package/README.md +1 -1
  4. package/package.json +5 -4
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/android-development/SKILL.md +14 -12
  7. package/skills/app-dev/android-development/references/diagnosis.md +4 -6
  8. package/skills/app-dev/android-development/references/gotchas.md +3 -9
  9. package/skills/app-dev/android-development/references/init.md +5 -2
  10. package/skills/app-dev/android-development/references/setup.md +2 -1
  11. package/skills/app-dev/android-development/references/ui-journeys.md +12 -127
  12. package/skills/app-dev/android-development/scripts/failure_capture.py +23 -84
  13. package/skills/app-dev/android-development/scripts/init.mjs +2 -1
  14. package/skills/app-dev/android-development/scripts/maestro_failure.py +43 -387
  15. package/skills/app-dev/android-development/scripts/test_maestro_failure.py +113 -424
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/better-auth/SKILL.md +1 -1
  18. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  19. package/skills/app-dev/data-quality/SKILL.md +1 -1
  20. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  21. package/skills/app-dev/maestro/LICENSE +3 -0
  22. package/skills/app-dev/maestro/SKILL.md +112 -0
  23. package/skills/app-dev/maestro/references/android-connection.md +54 -0
  24. package/skills/app-dev/maestro/references/authoring.md +113 -0
  25. package/skills/app-dev/maestro/references/diagnosis.md +50 -0
  26. package/skills/app-dev/maestro/references/evidence.md +74 -0
  27. package/skills/app-dev/maestro/references/journeys.md +100 -0
  28. package/skills/app-dev/maestro/references/migration.md +35 -0
  29. package/skills/app-dev/maestro/release-check.mjs +95 -0
  30. package/skills/app-dev/maestro/scripts/failure_record.py +110 -0
  31. package/skills/app-dev/maestro/scripts/maestro_failure.py +481 -0
  32. package/skills/app-dev/maestro/scripts/test_maestro_failure.py +710 -0
  33. package/skills/app-dev/monorepo/SKILL.md +1 -1
  34. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  35. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  36. package/skills/app-dev/pyspark/SKILL.md +1 -1
  37. package/skills/app-dev/python-backend/SKILL.md +1 -1
  38. package/skills/app-dev/shadcn/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  42. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  43. package/skills/cicd/docker/SKILL.md +1 -1
  44. package/skills/cicd/github-actions/SKILL.md +1 -1
  45. package/skills/cicd/github-flow/SKILL.md +1 -1
  46. package/skills/cicd/google-cloud/SKILL.md +1 -1
  47. package/skills/cicd/mergify/SKILL.md +1 -1
  48. package/skills/cicd/podman/SKILL.md +1 -1
  49. package/skills/content/article-review/SKILL.md +1 -1
  50. package/skills/content/article-to-image/SKILL.md +1 -1
  51. package/skills/content/flowchart-svg/SKILL.md +1 -1
  52. package/skills/content/image-gen/SKILL.md +1 -1
  53. package/skills/content/mermaid/SKILL.md +1 -1
  54. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  55. package/skills/content/wechat-publisher/SKILL.md +1 -1
  56. package/skills/content/wechat-search/SKILL.md +1 -1
  57. package/skills/content/xai-search/SKILL.md +1 -1
  58. package/skills/core/distill-writing/SKILL.md +1 -1
  59. package/skills/core/doc-to-md/SKILL.md +1 -1
  60. package/skills/core/editorial-review/SKILL.md +1 -1
  61. package/skills/core/house-style/SKILL.md +1 -1
  62. package/skills/core/plain-language/SKILL.md +1 -1
  63. package/skills/core/polish-cn/SKILL.md +1 -1
  64. package/skills/core/reg-parser/SKILL.md +1 -1
  65. package/skills/core/terminology/SKILL.md +1 -1
  66. package/skills/core/write-doc/SKILL.md +1 -1
  67. package/skills/core/write-report/SKILL.md +1 -1
  68. package/skills/engineering/better-review/SKILL.md +1 -1
  69. package/skills/engineering/e2e-test/SKILL.md +1 -1
  70. package/skills/engineering/engineering/SKILL.md +1 -1
  71. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  72. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  73. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  74. package/skills/engineering/golden-path/SKILL.md +1 -1
  75. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  76. package/skills/engineering/harness-runtime/agent-catalog.mjs +44 -11
  77. package/skills/engineering/harness-runtime/contracts.md +1 -1
  78. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  79. package/skills/engineering/resolve-release/SKILL.md +1 -1
  80. package/skills/engineering/rush-issues/SKILL.md +1 -1
  81. package/skills/engineering/rush-release/SKILL.md +1 -1
  82. package/skills/engineering/test-quality/SKILL.md +1 -1
  83. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  84. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  85. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  86. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  87. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  88. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  89. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  90. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  91. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
@@ -1,400 +1,56 @@
1
- """Convert a Maestro test-output session or Flow folder into the device failure record.
1
+ """Compatibility entry point for ``maestro_failure.py``.
2
2
 
3
- Maestro's ``--test-output-dir`` session holds one self-contained folder per Flow
4
- (`test reports and artifacts <https://docs.maestro.dev/maestro-flows/workspace-management/test-reports-and-artifacts>`_):
5
-
6
- <output>/2026-07-31_173617/ # session folder
7
- ├── maestro.log
8
- └── checkout_flow/ # one folder per Flow
9
- ├── manifest.json
10
- ├── commands.json # one entry per executed step
11
- ├── screenshots/ # step-<NNN>-<type>.png, failing step only
12
- ├── screen-hierarchy/ # step-<NNN>-<type>.json
13
- └── logs/device-logcat.txt
14
-
15
- ``--maestro-dir`` takes the session folder or one Flow folder. The converter picks the
16
- first failed step, reuses ``failure_capture`` to write ``failure.json`` and prints the one
17
- ``NATIVE_FAILURE {...}`` line, so the record shape cannot drift from the other layers:
3
+ The Maestro converter now lives with the ``maestro`` skill, in
4
+ ``skills/fullstack/maestro/scripts/maestro_failure.py``. This file forwards to it so an
5
+ existing reference to ``android-development/scripts/maestro_failure.py`` keeps working,
6
+ through the command line and through the Python import API:
18
7
 
19
8
  python3 maestro_failure.py --maestro-dir "$LOG_DIR/2026-07-31_173617" --case checkout
20
9
 
21
- A passing Flow writes nothing. An existing ``failure.json`` is never replaced, so the first
22
- failure keeps its evidence. ``screenshot``, ``uiDump`` and ``logcatTail`` are paths relative
23
- to ``evidenceDir``: the Flow folder itself, unless ``--evidence-dir`` names another run
24
- folder, in which case the artifacts are copied there. A missing artifact is ``null`` and the
25
- reason goes to ``cleanupErrors``; it never replaces ``error``.
26
-
27
- Field mapping, with what could not be confirmed from the docs
28
- -------------------------------------------------------------
29
-
30
- Confirmed from Maestro's source (``TestOutputWriter.saveCommands`` and
31
- ``CommandDebugMetadata``), because the docs name the fields but show no example:
32
-
33
- - ``commands.json`` is an array of ``{"command": <command object>, "metadata": {...}}``.
34
- - ``metadata.status`` is one of PENDING, RUNNING, COMPLETED, FAILED, WARNED, SKIPPED;
35
- ``metadata.timestamp`` is epoch milliseconds; ``sequenceNumber`` starts at 0.
36
- - ``metadata.error`` is the exception serialized as ``{"message", "debugMessage"}``; keys
37
- are omitted when empty.
38
- - ``metadata.artifacts`` is a list of ``{"type": "SCREENSHOT"|"SCREEN_HIERARCHY"|...,
39
- "path": "<flow-folder-relative>"}``.
40
-
41
- Confirmed from the docs: ``screenshots/``, ``screen-hierarchy/``, ``logs/maestro.log``,
42
- ``logs/device-logcat.txt`` (Android), ``manifest.json``, and that only the failing step is
43
- captured outside ``--analyze``.
44
-
45
- Assumed, and not confirmed from the docs:
46
-
47
- - The *step description*: ``commands.json`` stores the command object, not a rendered
48
- description (Maestro's ``originalDescription`` is not serialized). ``step`` is the
49
- command's own ``label`` when it has one, otherwise a mechanical reading of the command
50
- type plus its first target (``text``/``textRegex``/``id``/``point``/``appId``/``link``/
51
- ``path``). It is deliberately not a re-implementation of Maestro's phrasing.
52
- - Artifact file names ``step-<NNN>-<type>`` with ``NNN`` = ``sequenceNumber`` + 1. This was
53
- observed in Maestro's own tests, not in the docs, so it is only the fallback when
54
- ``metadata.artifacts`` does not name the file.
55
- - ``logs/device-simulator.log`` as the iOS fallback for the device log; the Android target
56
- is ``logs/device-logcat.txt``.
57
- - Which Flow failed first in a multi-Flow session: the earliest failing step's
58
- ``timestamp`` decides, with the folder name as the tie-break.
10
+ The sibling script directory is resolved from this file's own location, so the pair is
11
+ found beside each other in the repository and in a staged package, never through the
12
+ working directory or an installed package path. The converter's own folder has to be on
13
+ ``sys.path`` for its ``failure_record`` import; the module is then loaded by path under a
14
+ private name, because a plain ``import maestro_failure`` from here would resolve to this
15
+ forwarder.
59
16
  """
60
17
 
61
- from __future__ import annotations
62
-
63
- import argparse
64
- import json
18
+ import importlib.util
65
19
  import os
66
- import re
67
- import shutil
68
20
  import sys
69
21
 
70
- from failure_capture import Failure, read_record, write_record
71
-
72
- COMMANDS_JSON = "commands.json"
73
- SCREENSHOT_DIR = "screenshots"
74
- HIERARCHY_DIR = "screen-hierarchy"
75
- DEVICE_LOGS = ("logs/device-logcat.txt", "logs/device-simulator.log")
76
- FAILED_STATUS = "FAILED"
77
- NO_ERROR_MESSAGE = "Maestro marked the step FAILED and gave no error message"
78
- TARGET_KEYS = ("text", "textRegex", "id", "point", "appId", "link", "path")
79
- # The record attribute, the Maestro artifact kind, and the reason when it is absent.
80
- RECORD_ARTIFACTS = (
81
- ("screenshot", "SCREENSHOT", "no failing-step screenshot in the Maestro output"),
82
- ("uiDump", "SCREEN_HIERARCHY", "no screen hierarchy in the Maestro output"),
83
- ("logcatTail", "LOGCAT", "no device log in the Maestro output"),
22
+ CONVERTER_DIR = os.path.normpath(
23
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "maestro", "scripts")
84
24
  )
85
-
86
- _CAMEL_WORD = re.compile(r"(?<=[a-z0-9])([A-Z])")
87
-
88
-
89
- class MaestroOutputError(RuntimeError):
90
- """The given folder holds no usable Maestro commands.json."""
91
-
92
-
93
- def _text(value):
94
- if value is None:
95
- return ""
96
- return str(value).strip()
97
-
98
-
99
- def load_commands(flow_dir):
100
- """The executed steps of `flow_dir`, or None when its commands.json is unusable."""
101
- try:
102
- with open(os.path.join(flow_dir, COMMANDS_JSON), "r", encoding="utf-8") as stream:
103
- payload = json.load(stream)
104
- except (OSError, ValueError):
105
- return None
106
- if not isinstance(payload, list):
107
- return None
108
- return [entry for entry in payload if isinstance(entry, dict)]
109
-
110
-
111
- def _pieces(entry):
112
- """(command, metadata) for one entry, in the current or the documented flat shape."""
113
- metadata = entry.get("metadata")
114
- if isinstance(metadata, dict):
115
- return entry.get("command"), metadata
116
- return entry.get("command"), entry
117
-
118
-
119
- def _status(metadata):
120
- return _text(metadata.get("status")).upper()
121
-
122
-
123
- def failed_steps(entries):
124
- """(command, metadata) for each step Maestro marked FAILED, in execution order."""
125
- steps = []
126
- for entry in entries:
127
- command, metadata = _pieces(entry)
128
- if _status(metadata) == FAILED_STATUS:
129
- steps.append((command, metadata))
130
- return steps
131
-
132
-
133
- def _timestamp(metadata):
134
- """The step's epoch-millisecond timestamp, or infinity when Maestro did not record one."""
135
- value = metadata.get("timestamp")
136
- if isinstance(value, bool) or not isinstance(value, (int, float)):
137
- return float("inf")
138
- return float(value)
139
-
140
-
141
- def flow_dirs(root, max_depth=3):
142
- """Folders holding a commands.json, `root` included, at most `max_depth` levels down."""
143
- if os.path.isfile(os.path.join(root, COMMANDS_JSON)):
144
- return [root]
145
- found = []
146
- base_depth = os.path.abspath(root).rstrip(os.sep).count(os.sep)
147
- for current, directories, files in os.walk(root):
148
- depth = os.path.abspath(current).rstrip(os.sep).count(os.sep) - base_depth
149
- directories.sort()
150
- if COMMANDS_JSON in files:
151
- found.append(current)
152
- # A Flow folder's own subfolders hold artifacts, never another Flow.
153
- directories[:] = []
154
- elif depth >= max_depth:
155
- directories[:] = []
156
- return found
157
-
158
-
159
- def first_failure(root):
160
- """(flow dir, command, metadata) for the earliest failing step under `root`, or None."""
161
- directories = flow_dirs(root)
162
- if not directories:
163
- raise MaestroOutputError("no %s under %s" % (COMMANDS_JSON, root))
164
- unreadable = []
165
- failures = []
166
- for directory in directories:
167
- entries = load_commands(directory)
168
- if entries is None:
169
- unreadable.append(directory)
170
- continue
171
- steps = failed_steps(entries)
172
- if steps:
173
- command, metadata = steps[0]
174
- failures.append((_timestamp(metadata), directory, command, metadata))
175
- if not failures:
176
- if unreadable:
177
- raise MaestroOutputError(
178
- "no readable %s in %s" % (COMMANDS_JSON, ", ".join(sorted(unreadable)))
179
- )
180
- return None
181
- failures.sort(key=lambda failure: (failure[0], failure[1]))
182
- _, directory, command, metadata = failures[0]
183
- return directory, command, metadata
184
-
185
-
186
- def humanize(command_type):
187
- """`tapOnElement` -> `Tap on element`, `assertScreenshotCommand` -> `Assert screenshot`."""
188
- name = command_type[: -len("Command")] if command_type.endswith("Command") else command_type
189
- spaced = _CAMEL_WORD.sub(r" \1", name).lower()
190
- return spaced[:1].upper() + spaced[1:]
191
-
192
-
193
- def _target(value, depth=5):
194
- """The first selector or target text inside a command, for a readable step."""
195
- if depth <= 0:
196
- return None
197
- if isinstance(value, dict):
198
- for key in TARGET_KEYS:
199
- candidate = value.get(key)
200
- if isinstance(candidate, bool):
201
- continue
202
- if isinstance(candidate, (str, int, float)) and _text(candidate):
203
- return candidate
204
- children = list(value.values())
205
- elif isinstance(value, list):
206
- children = value
207
- else:
208
- return None
209
- for child in children:
210
- found = _target(child, depth - 1)
211
- if found is not None:
212
- return found
213
- return None
214
-
215
-
216
- def describe_command(command):
217
- """The step recorded for the failing command, or None when Maestro stored none."""
218
- if isinstance(command, str):
219
- return _text(command) or None
220
- if not isinstance(command, dict):
221
- return None
222
- for command_type, value in command.items():
223
- if value is None:
224
- continue
225
- label = value.get("label") if isinstance(value, dict) else None
226
- if _text(label):
227
- return _text(label)
228
- reading = humanize(command_type)
229
- target = _target(value)
230
- return reading if target is None else "%s %s" % (reading, json.dumps(target, ensure_ascii=False))
231
- return None
232
-
233
-
234
- def error_text(metadata):
235
- """The step's error message, keeping Maestro's debug detail when it adds anything."""
236
- error = metadata.get("error")
237
- if isinstance(error, dict):
238
- message = _text(error.get("message"))
239
- detail = _text(error.get("debugMessage"))
240
- if message and detail and detail not in message:
241
- return "%s: %s" % (message, detail)
242
- return message or detail or NO_ERROR_MESSAGE
243
- text = _text(error)
244
- return text or NO_ERROR_MESSAGE
245
-
246
-
247
- def _listed_artifacts(metadata):
248
- """{kind: relative path} from the step's artifacts list, in either shape."""
249
- artifacts = metadata.get("artifacts")
250
- if not isinstance(artifacts, list):
251
- return {}
252
- found = {}
253
- for artifact in artifacts:
254
- kind = ""
255
- if isinstance(artifact, dict):
256
- kind = _text(artifact.get("type")).upper()
257
- path = artifact.get("path")
258
- else:
259
- path = artifact
260
- if not isinstance(path, str) or not path:
261
- continue
262
- if not kind:
263
- # The documented flat shape names the file without a kind.
264
- lowered = path.lower()
265
- if lowered.endswith(".png"):
266
- kind = "SCREENSHOT"
267
- elif lowered.endswith(".json") and "hierarch" in lowered:
268
- kind = "SCREEN_HIERARCHY"
269
- if kind:
270
- found.setdefault(kind, path)
271
- return found
272
-
273
-
274
- def _step_named_artifacts(flow_dir, metadata):
275
- """Fallback: the files Maestro names after the step, step-<NNN>-<type>.(png|json)."""
276
- number = metadata.get("sequenceNumber")
277
- prefix = "step-%03d-" % (number + 1) if isinstance(number, int) and not isinstance(number, bool) else "step-"
278
- found = {}
279
- for kind, directory, suffix in (
280
- ("SCREENSHOT", SCREENSHOT_DIR, ".png"),
281
- ("SCREEN_HIERARCHY", HIERARCHY_DIR, ".json"),
282
- ):
283
- try:
284
- names = sorted(os.listdir(os.path.join(flow_dir, directory)))
285
- except OSError:
286
- continue
287
- for name in names:
288
- if name.startswith(prefix) and name.endswith(suffix):
289
- found[kind] = "%s/%s" % (directory, name)
290
- break
291
- return found
292
-
293
-
294
- def step_artifacts(flow_dir, metadata):
295
- """{kind: relative path} for the failing step, as `commands.json` names it.
296
-
297
- A path Maestro listed is kept even when the file is absent, so the record names the
298
- artifact it expected instead of a generic gap. The step-named fallback only fills a
299
- kind Maestro did not list, and only with a file that is actually there.
300
- """
301
- found = {}
302
- listed = _listed_artifacts(metadata)
303
- named = _step_named_artifacts(flow_dir, metadata)
304
- for kind in ("SCREENSHOT", "SCREEN_HIERARCHY"):
305
- relative = listed.get(kind) or named.get(kind)
306
- if relative:
307
- found[kind] = relative
308
- for relative in DEVICE_LOGS:
309
- if os.path.isfile(os.path.join(flow_dir, relative)):
310
- found["LOGCAT"] = relative
311
- break
312
- return found
313
-
314
-
315
- def _store(flow_dir, evidence_dir, relative, failure, attribute):
316
- """Copy `relative` into `evidence_dir` when they differ; record the failure otherwise."""
317
- source = os.path.join(flow_dir, relative)
318
- if not os.path.isfile(source):
319
- failure.cleanupErrors.append("%s: %s is not in the Maestro output" % (attribute, relative))
320
- return None
321
- if os.path.abspath(evidence_dir) == os.path.abspath(flow_dir):
322
- return relative
323
- destination = os.path.join(evidence_dir, relative)
324
- try:
325
- os.makedirs(os.path.dirname(destination) or evidence_dir, exist_ok=True)
326
- shutil.copyfile(source, destination)
327
- except OSError as problem:
328
- failure.cleanupErrors.append("%s: copy of %s failed: %s" % (attribute, relative, problem))
329
- return None
330
- return relative
331
-
332
-
333
- def build_failure(flow_dir, command, metadata, case, evidence_dir):
334
- """The `Failure` for one failed Maestro step, with the evidence found in `flow_dir`."""
335
- failure = Failure(
336
- case=case or os.path.basename(os.path.abspath(flow_dir)),
337
- step=describe_command(command),
338
- error=error_text(metadata),
339
- evidenceDir=evidence_dir,
340
- )
341
- found = step_artifacts(flow_dir, metadata)
342
- for attribute, kind, reason in RECORD_ARTIFACTS:
343
- relative = found.get(kind)
344
- if relative is None:
345
- failure.cleanupErrors.append("%s: %s" % (attribute, reason))
346
- continue
347
- setattr(failure, attribute, _store(flow_dir, evidence_dir, relative, failure, attribute))
348
- return failure
349
-
350
-
351
- def convert(maestro_dir, case=None, evidence_dir=None, stream=None):
352
- """Write the failure record for the first failed Flow under `maestro_dir`.
353
-
354
- Returns the record written, or the earlier record kept in `evidence_dir`, or None when
355
- no Flow failed. Raises `MaestroOutputError` when no readable `commands.json` is there.
356
- """
357
- stream = stream if stream is not None else sys.stdout
358
- selected = first_failure(maestro_dir)
359
- if selected is None:
360
- return None
361
- flow_dir, command, metadata = selected
362
- evidence = os.path.abspath(evidence_dir or flow_dir)
363
- kept = read_record(evidence)
364
- if kept is not None:
365
- # The first failure owns this evidence folder; a later conversion only repeats it.
366
- print(kept.native_failure_line(), file=stream, flush=True)
367
- return kept
368
- failure = build_failure(flow_dir, command, metadata, case, evidence)
369
- failure = write_record(failure, evidence)
370
- print(failure.native_failure_line(), file=stream, flush=True)
371
- return failure
372
-
373
-
374
- def main(argv=None):
375
- parser = argparse.ArgumentParser(
376
- description="Convert a Maestro test-output session or Flow folder into the device failure record."
377
- )
378
- parser.add_argument(
379
- "--maestro-dir",
380
- required=True,
381
- help="Maestro --test-output-dir session folder, or one Flow folder holding commands.json",
382
- )
383
- parser.add_argument("--case", help="case name for the record; defaults to the Flow folder name")
384
- parser.add_argument(
385
- "--evidence-dir",
386
- help="run evidence folder that holds failure.json; defaults to the Flow folder",
387
- )
388
- options = parser.parse_args(argv)
389
- if not os.path.isdir(options.maestro_dir):
390
- print("maestro folder not found: %s" % options.maestro_dir, file=sys.stderr)
391
- return 2
392
- try:
393
- convert(options.maestro_dir, case=options.case, evidence_dir=options.evidence_dir)
394
- except MaestroOutputError as problem:
395
- print("not converted: %s" % problem, file=sys.stderr)
396
- return 2
397
- return 0
25
+ CONVERTER_PATH = os.path.join(CONVERTER_DIR, "maestro_failure.py")
26
+ if CONVERTER_DIR not in sys.path:
27
+ sys.path.insert(0, CONVERTER_DIR)
28
+
29
+
30
+ def _load_converter():
31
+ spec = importlib.util.spec_from_file_location("maestro_skill_failure", CONVERTER_PATH)
32
+ if spec is None or spec.loader is None:
33
+ raise ImportError("maestro converter not found at %s" % CONVERTER_PATH)
34
+ module = importlib.util.module_from_spec(spec)
35
+ sys.modules[spec.name] = module
36
+ spec.loader.exec_module(module)
37
+ return module
38
+
39
+
40
+ _converter = _load_converter()
41
+
42
+ MaestroOutputError = _converter.MaestroOutputError
43
+ build_failure = _converter.build_failure
44
+ convert = _converter.convert
45
+ describe_command = _converter.describe_command
46
+ error_text = _converter.error_text
47
+ failed_steps = _converter.failed_steps
48
+ first_failure = _converter.first_failure
49
+ flow_dirs = _converter.flow_dirs
50
+ humanize = _converter.humanize
51
+ load_commands = _converter.load_commands
52
+ main = _converter.main
53
+ step_artifacts = _converter.step_artifacts
398
54
 
399
55
 
400
56
  if __name__ == "__main__":