@mmerterden/multi-agent-pipeline 20.5.0 → 20.7.0

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 (81) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +10 -0
  3. package/docs/facts.json +5 -4
  4. package/install/_dev-only-files.mjs +1 -0
  5. package/manifest.json +84 -82
  6. package/package.json +4 -3
  7. package/pipeline/lib/confusables.json +100 -0
  8. package/pipeline/lib/normalize-text.mjs +48 -0
  9. package/pipeline/lib/outbound-gate.mjs +10 -1
  10. package/pipeline/lib/redact.mjs +4 -1
  11. package/pipeline/lib/vercel-deploy.sh +5 -7
  12. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  13. package/pipeline/scripts/_notices.mjs +11 -0
  14. package/pipeline/scripts/agent-guard.py +30 -1
  15. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  16. package/pipeline/scripts/pre-commit-check.sh +59 -16
  17. package/pipeline/scripts/pre-push-check.sh +3 -3
  18. package/pipeline/scripts/run-ui-tests.sh +5 -5
  19. package/pipeline/scripts/website-deploy-commit.sh +3 -2
  20. package/pipeline/skills/.skill-manifest.json +39 -39
  21. package/pipeline/skills/shared/README.md +1 -1
  22. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  23. package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
  24. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  25. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  26. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  27. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  28. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  29. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  30. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  31. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  32. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  33. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  34. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  35. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  36. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  37. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  38. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  39. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  40. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  41. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  42. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  43. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  44. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  45. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  46. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  47. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  48. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  49. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  50. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  51. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  52. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  53. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  54. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  55. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  56. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  57. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  58. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  59. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  60. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  61. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  62. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  63. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  64. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  65. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  66. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  67. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  68. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  69. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  70. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  71. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  72. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  73. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  74. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  75. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  76. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  77. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  78. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  79. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  81. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
@@ -1,25 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Refresh the LEGACY label snapshot: fetch a legacy label map for every language
3
- from your project's label endpoint and write it into the resources snapshot, so the
4
- mapper resolves legacy values offline instead of a network round-trip per run. This
5
- is the ONLY step that hits the network - run it manually / in CI to refresh, the same
6
- way you refresh a design export.
2
+ """Refresh the legacy label snapshot from the label endpoint.
7
3
 
8
- Writes <out>/<lang>.json = flat {<key>: value} (keys verbatim, any prefix kept).
9
-
10
- The endpoint is entirely project-specific, so nothing about it is baked in - you
11
- supply the URL template and the headers it needs:
12
-
13
- fetch-legacy-labels.py --out resources/Localization/Legacy --langs all \\
14
- --endpoint 'https://labels.example.internal/{env}/labels/{lang}' \\
15
- --headers-file .secrets/label-headers.json
16
-
17
- Placeholders available in --endpoint: {lang} and {env}.
18
-
19
- Prefer --headers-file (JSON object) over repeated --header for anything secret, so
20
- tokens never land in shell history or CI logs. Response shape is discovered rather
21
- than assumed: the largest flat {str: str} map anywhere in the JSON wins, which covers
22
- both a bare map and one nested under an envelope.
4
+ Writes one flat <out>/<lang>.json per language with keys exactly as the
5
+ service returns them (backend prefix kept). This is the only step of the
6
+ skill that needs the network.
23
7
  """
24
8
  import argparse
25
9
  import json
@@ -27,103 +11,103 @@ import os
27
11
  import sys
28
12
  import urllib.request
29
13
 
30
- LANGS_ALL = ["en", "tr", "ar", "de", "es", "fr", "it", "ru"]
14
+ ALL_LANGS = ["en", "tr", "ar"] + sorted(["ru", "it", "fr", "es", "de"])
31
15
 
32
16
 
33
- def largest_str_map(obj):
34
- best, stack = {}, [obj]
35
- while stack:
36
- cur = stack.pop()
37
- if isinstance(cur, dict):
38
- strs = {k: v for k, v in cur.items() if isinstance(v, str)}
39
- if len(strs) > len(best):
40
- best = strs
41
- stack.extend(cur.values())
42
- elif isinstance(cur, list):
43
- stack.extend(cur)
44
- return best
17
+ def parse_langs(value):
18
+ if value.strip() == "all":
19
+ return list(ALL_LANGS)
20
+ return [part.strip() for part in value.split(",") if part.strip()]
45
21
 
46
22
 
47
- def parse_headers(a):
48
- headers = {"Accept": "application/json"}
49
- if a.headers_file:
50
- with open(a.headers_file, encoding="utf-8") as fh:
51
- loaded = json.load(fh)
52
- if not isinstance(loaded, dict):
53
- raise SystemExit(f"--headers-file must be a JSON object, got {type(loaded).__name__}")
54
- headers.update({str(k): str(v) for k, v in loaded.items()})
55
- for pair in a.header or []:
23
+ def parse_pairs(pairs, flag):
24
+ result = {}
25
+ for pair in pairs or []:
56
26
  if "=" not in pair:
57
- raise SystemExit(f"--header expects K=V, got {pair!r}")
58
- k, v = pair.split("=", 1)
59
- headers[k.strip()] = v.strip()
27
+ raise SystemExit(f"{flag} expects K=V, got {pair!r}")
28
+ key, value = pair.split("=", 1)
29
+ result[key.strip()] = value.strip()
30
+ return result
31
+
32
+
33
+ def build_headers(args):
34
+ headers = {"Accept": "application/json"}
35
+ if args.headers_file:
36
+ with open(args.headers_file, encoding="utf-8") as stream:
37
+ extra = json.load(stream)
38
+ if not isinstance(extra, dict):
39
+ raise SystemExit(f"--headers-file must hold a JSON object: {args.headers_file}")
40
+ headers.update({str(k): str(v) for k, v in extra.items()})
41
+ headers.update(parse_pairs(args.header, "--header"))
60
42
  return headers
61
43
 
62
44
 
63
- def parse_fail_statuses(values):
64
- out = {}
65
- for pair in values or []:
66
- if "=" not in pair:
67
- raise SystemExit(f"--fail-status expects CODE=message, got {pair!r}")
68
- code, message = pair.split("=", 1)
69
- out[code.strip()] = message.strip()
70
- return out
71
-
72
-
73
- def fetch(lang, headers, fail_statuses, a):
74
- url = a.endpoint.format(env=a.env, lang=lang)
75
- per_lang = dict(headers)
76
- per_lang.setdefault("Accept-Language", lang)
77
- req = urllib.request.Request(url, headers=per_lang)
78
- with urllib.request.urlopen(req, timeout=a.timeout) as r:
79
- data = json.loads(r.read().decode("utf-8"))
80
- if isinstance(data, dict) and a.status_field in data:
81
- code = str(data.get(a.status_field))
82
- if code in fail_statuses:
83
- raise RuntimeError(f"{url}: {fail_statuses[code]} (status {code})")
84
- m = largest_str_map(data)
85
- if not m:
86
- raise RuntimeError(f"no label map found in the response for '{lang}' ({url})")
87
- return m
45
+ def largest_string_map(data):
46
+ best = {}
47
+ stack = [data]
48
+ while stack:
49
+ node = stack.pop()
50
+ if isinstance(node, dict):
51
+ if node and all(isinstance(k, str) and isinstance(v, str) for k, v in node.items()):
52
+ if len(node) > len(best):
53
+ best = node
54
+ stack.extend(node.values())
55
+ elif isinstance(node, list):
56
+ stack.extend(node)
57
+ return best
58
+
59
+
60
+ def fetch_labels(endpoint, lang, env, headers, status_field, fail_status, timeout):
61
+ url = endpoint.format(lang=lang, env=env)
62
+ request_headers = dict(headers)
63
+ request_headers.setdefault("Accept-Language", lang)
64
+ request = urllib.request.Request(url, headers=request_headers)
65
+ with urllib.request.urlopen(request, timeout=timeout) as response:
66
+ data = json.loads(response.read().decode("utf-8"))
67
+ if isinstance(data, dict) and status_field in data:
68
+ code = str(data[status_field])
69
+ if code in fail_status:
70
+ raise RuntimeError(f"{url}: {fail_status[code]} (status {code})")
71
+ labels = largest_string_map(data)
72
+ if not labels:
73
+ raise RuntimeError(f"the response for '{lang}' ({url}) contains no string-to-string label map")
74
+ return labels
88
75
 
89
76
 
90
77
  def main():
91
- ap = argparse.ArgumentParser(description=__doc__,
92
- formatter_class=argparse.RawDescriptionHelpFormatter)
93
- ap.add_argument("--out", required=True, help="snapshot dir, e.g. resources/Localization/Legacy")
94
- ap.add_argument("--endpoint", required=True,
95
- help="URL template for the legacy label map; may contain {lang} and {env}")
96
- ap.add_argument("--header", action="append",
97
- help="extra request header as K=V (repeatable); avoid for secrets")
98
- ap.add_argument("--headers-file", help="JSON object of request headers - use this for tokens")
99
- ap.add_argument("--langs", default="all", help="'all' or a comma-separated list")
100
- ap.add_argument("--env", default="", help="value substituted for {env} in --endpoint")
101
- ap.add_argument("--status-field", default="statusCode",
102
- help="response field carrying a provider status code")
103
- ap.add_argument("--fail-status", action="append",
104
- help="CODE=message for a provider error code that must abort (repeatable)")
105
- ap.add_argument("--timeout", type=int, default=40)
106
- a = ap.parse_args()
107
-
108
- if "{lang}" not in a.endpoint:
109
- print("warning: --endpoint has no {lang} placeholder; every language will hit the same URL",
110
- file=sys.stderr)
111
-
112
- headers = parse_headers(a)
113
- fail_statuses = parse_fail_statuses(a.fail_status)
114
- langs = LANGS_ALL if a.langs.strip() == "all" else [l.strip() for l in a.langs.split(",") if l.strip()]
115
- os.makedirs(a.out, exist_ok=True)
78
+ cli = argparse.ArgumentParser(description=__doc__.splitlines()[0])
79
+ cli.add_argument("--out", dest="snapshot_dir", required=True, help="where the snapshot lives, for example resources/Localization/Legacy")
80
+ cli.add_argument("--endpoint", required=True, help="URL template containing {lang} and, if needed, {env}")
81
+ cli.add_argument("--header", action="append", help="additional request header as K=V; may be repeated")
82
+ cli.add_argument("--headers-file", help="request headers as a JSON object, so secrets stay out of argv")
83
+ cli.add_argument("--langs", default="all", help="languages separated by commas, or 'all'")
84
+ cli.add_argument("--env", default="", help="text put in place of {env}")
85
+ cli.add_argument("--status-field", default="statusCode", help="name of the response field that holds a status code")
86
+ cli.add_argument("--fail-status", action="append", help="CODE=message pair that marks a failed response; may be repeated")
87
+ cli.add_argument("--timeout", default=40, type=int)
88
+ opts = cli.parse_args()
89
+
90
+ if "{lang}" not in opts.endpoint:
91
+ print("# warning: --endpoint lacks a {lang} placeholder, so all languages request the same URL", file=sys.stderr)
92
+
93
+ headers = build_headers(opts)
94
+ fail_status = parse_pairs(opts.fail_status, "--fail-status")
95
+ langs = parse_langs(opts.langs)
96
+ os.makedirs(opts.snapshot_dir, exist_ok=True)
97
+
116
98
  total = 0
117
99
  for lang in langs:
118
- m = fetch(lang, headers, fail_statuses, a)
119
- path = os.path.join(a.out, f"{lang}.json")
120
- with open(path, "w", encoding="utf-8") as fh:
121
- json.dump(m, fh, ensure_ascii=False, indent=0, sort_keys=True)
122
- fh.write("\n")
123
- total += len(m)
124
- print(f" {lang}: {len(m)} labels → {path}")
125
- print(f"legacy snapshot refreshed: {len(langs)} languages, ~{total // max(len(langs), 1)} keys each")
126
- print("→ commit it like a design-export refresh")
100
+ labels = fetch_labels(opts.endpoint, lang, opts.env, headers, opts.status_field, fail_status, opts.timeout)
101
+ target = os.path.join(opts.snapshot_dir, lang + ".json")
102
+ with open(target, "w", encoding="utf-8") as sink:
103
+ json.dump(labels, sink, ensure_ascii=False, sort_keys=True, indent=0)
104
+ sink.write("\n")
105
+ total += len(labels)
106
+ print(f" {lang}: {len(labels)} labels -> {target}")
107
+
108
+ average = total // len(langs) if langs else 0
109
+ print(f"snapshot refreshed: {len(langs)} languages, about {average} keys each")
110
+ print(f"commit {opts.snapshot_dir} so later lookups stay offline")
127
111
 
128
112
 
129
113
  if __name__ == "__main__":