@mmerterden/multi-agent-pipeline 20.8.2 → 20.9.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 (37) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/docs/facts.json +1 -1
  3. package/install/_common.mjs +49 -0
  4. package/install/_dev-only-files.mjs +1 -0
  5. package/install/catalog-history.json +1 -0
  6. package/install/claude.mjs +13 -7
  7. package/manifest.json +40 -30
  8. package/package.json +3 -2
  9. package/pipeline/lib/claude-md-links.mjs +328 -0
  10. package/pipeline/lib/owned-path-gate.mjs +699 -0
  11. package/pipeline/lib/repo-profile-derive.mjs +1771 -0
  12. package/pipeline/lib/repo-profile.mjs +780 -0
  13. package/pipeline/lib/stack-detect.sh +59 -19
  14. package/pipeline/lib/unattended.mjs +17 -0
  15. package/pipeline/multi-agent-refs/features/repo-profile.md +96 -0
  16. package/pipeline/multi-agent-refs/features/review-decision.md +18 -13
  17. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +179 -33
  18. package/pipeline/multi-agent-refs/outside-the-pipeline.md +33 -11
  19. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +26 -12
  20. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +24 -13
  21. package/pipeline/multi-agent-refs/phases/phase-3-review.md +16 -4
  22. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -1
  23. package/pipeline/multi-agent-refs/phases/phase-5-report.md +8 -0
  24. package/pipeline/rules/outside-the-pipeline.md +6 -1
  25. package/pipeline/schemas/agent-state.schema.json +66 -2
  26. package/pipeline/schemas/phases.json +4 -4
  27. package/pipeline/schemas/repo-profile.schema.json +1107 -0
  28. package/pipeline/schemas/token-budget.json +4 -4
  29. package/pipeline/scripts/agent-guard.py +30 -0
  30. package/pipeline/scripts/owned-path-gate.mjs +205 -0
  31. package/pipeline/scripts/pre-commit-check.sh +151 -1
  32. package/pipeline/scripts/repo-profile.mjs +244 -0
  33. package/pipeline/scripts/review-decision-gate.mjs +42 -18
  34. package/pipeline/scripts/skill-candidates.mjs +882 -0
  35. package/pipeline/scripts/unattended_policy.py +90 -0
  36. package/pipeline/scripts/usage-report.mjs +36 -6
  37. package/pipeline/skills/.skill-manifest.json +1 -1
@@ -2793,3 +2793,93 @@ def git_commit_dirs(cmd, base):
2793
2793
  opt = _git_dir_opt(toks)
2794
2794
  dirs.append(cwd if opt is None else _literal_dir(opt, cwd))
2795
2795
  return dirs
2796
+
2797
+
2798
+ # `git add` and `git commit` options whose value is the next token.
2799
+ ADD_VALUE_OPTIONS = ("--chmod",)
2800
+ COMMIT_VALUE_OPTIONS = (
2801
+ "--message", "--file", "--reuse-message", "--reedit-message", "--author",
2802
+ "--date", "--template", "--fixup", "--squash", "--cleanup", "--trailer",
2803
+ )
2804
+ COMMIT_SHORT_VALUE = "mFCct"
2805
+
2806
+
2807
+ def _add_scope(args):
2808
+ out = []
2809
+ specs_only = False
2810
+ i = 0
2811
+ while i < len(args):
2812
+ t = args[i]
2813
+ i += 1
2814
+ if specs_only or not t.startswith("-"):
2815
+ out.append("ADD " + t)
2816
+ elif t == "--":
2817
+ specs_only = True
2818
+ elif t in ("-A", "--all", "--no-ignore-removal") or t.startswith("--pathspec-from-file"):
2819
+ out.append("ADD_ALL")
2820
+ elif t in ("-u", "--update"):
2821
+ out.append("ADD_UPDATE")
2822
+ elif t in ADD_VALUE_OPTIONS:
2823
+ i += 1
2824
+ elif not t.startswith("--") and ("A" in t[1:] or "u" in t[1:]):
2825
+ out.append("ADD_ALL" if "A" in t[1:] else "ADD_UPDATE")
2826
+ return out
2827
+
2828
+
2829
+ def _commit_scope(args):
2830
+ out = []
2831
+ specs_only = False
2832
+ i = 0
2833
+ while i < len(args):
2834
+ t = args[i]
2835
+ i += 1
2836
+ if specs_only or not t.startswith("-") or t == "-":
2837
+ out.append("COMMIT " + t)
2838
+ elif t == "--":
2839
+ specs_only = True
2840
+ elif t == "--all" or t.startswith("--pathspec-from-file"):
2841
+ out.append("COMMIT_ALL")
2842
+ elif t.startswith("--"):
2843
+ if t in COMMIT_VALUE_OPTIONS:
2844
+ i += 1
2845
+ else:
2846
+ for k, ch in enumerate(t[1:], start=1):
2847
+ if ch == "a":
2848
+ out.append("COMMIT_ALL")
2849
+ if ch in COMMIT_SHORT_VALUE:
2850
+ if k == len(t) - 1:
2851
+ i += 1
2852
+ break
2853
+ return out
2854
+
2855
+
2856
+ def git_commit_scope(cmd, depth=0):
2857
+ """What a shell line adds to the commit it makes, beyond the index, as
2858
+ lines: `ADD_ALL` (`git add -A` or `.`-like), `ADD_UPDATE` (`-u`),
2859
+ `ADD <pathspec>`, `COMMIT_ALL` (`commit -a`) and `COMMIT <pathspec>`. The
2860
+ pre-commit hook scans the index plus these, so a dirty file the commit will
2861
+ not include is never judged, and `commit -a` is never missed."""
2862
+ out = []
2863
+ inners = []
2864
+ if depth == 0:
2865
+ try:
2866
+ cmd = strip_heredocs(cmd)
2867
+ except Refused:
2868
+ pass
2869
+ if depth < 3:
2870
+ try:
2871
+ cmd, inners = extract_substitutions(cmd)
2872
+ except Refused:
2873
+ inners = []
2874
+ for seg in split_ops(cmd):
2875
+ toks = _strip_wrappers(tokenize(seg))
2876
+ if not toks or os.path.basename(toks[0]).lower() != "git":
2877
+ continue
2878
+ sub, rest = _git_sub(toks)
2879
+ if sub == "add":
2880
+ out.extend(_add_scope(rest))
2881
+ elif sub == "commit":
2882
+ out.extend(_commit_scope(rest))
2883
+ for inner in inners:
2884
+ out.extend(git_commit_scope(inner, depth + 1))
2885
+ return out
@@ -36,6 +36,7 @@ import { execFileSync } from "child_process";
36
36
  import { createHash } from "crypto";
37
37
  import { costUsd } from "./_cost.mjs";
38
38
  import { defaultPrefsPath, resolveReportingLogin } from "./usage-identity.mjs";
39
+ import { KNOWN_STACKS, pluginsForStacks } from "./_stack-routing.mjs";
39
40
 
40
41
  const __dirname = dirname(fileURLToPath(import.meta.url));
41
42
  const ENDPOINT_DEFAULT = "https://mmerterden.com/api/usage/ingest";
@@ -134,16 +135,23 @@ function readEnabledPluginNames(settings) {
134
135
  .map(([k]) => k);
135
136
  }
136
137
 
137
- function enabledPlugins(state) {
138
+ // A plugin the pipeline's own marketplace does not publish is somebody's
139
+ // private plugin, so its name stays on the host and only the constant
140
+ // `private` says that one was enabled.
141
+ function enabledPlugins(state, { publicPlugins, home } = {}) {
138
142
  const root = state.projectRoot || process.cwd();
143
+ const pub = publicPlugins ?? publicToolkits();
139
144
  const names = new Set();
140
145
  for (const s of [
141
146
  readJson(join(root, ".claude", "settings.json")),
142
- readJson(join(homedir(), ".claude", "settings.json")),
147
+ readJson(join(home ?? homedir(), ".claude", "settings.json")),
143
148
  ]) {
144
- for (const name of readEnabledPluginNames(s)) names.add(name.split("@")[0]);
149
+ for (const name of readEnabledPluginNames(s)) {
150
+ const bare = name.split("@")[0];
151
+ if (bare) names.add(pub.has(bare) ? bare : "private");
152
+ }
145
153
  }
146
- return Array.from(names).filter(Boolean).slice(0, 20);
154
+ return Array.from(names).slice(0, 20);
147
155
  }
148
156
 
149
157
  function integrationTags(state) {
@@ -183,10 +191,31 @@ function credHealth() {
183
191
  return { on, svc };
184
192
  }
185
193
 
186
- function invokedCalls(state) {
194
+ // A repo-local skill is named by whoever owns the repo, and a fallback toolkit
195
+ // that the public marketplace does not carry is somebody's private plugin:
196
+ // neither name is sent. Each is replaced by a constant naming its source, so
197
+ // the panel still counts that such a skill was used.
198
+ function publicToolkits(manifestPath) {
199
+ return new Set(pluginsForStacks(KNOWN_STACKS, manifestPath ? { manifestPath } : {}).plugins);
200
+ }
201
+
202
+ function invokedCalls(state, { publicPlugins, manifestPath } = {}) {
203
+ let pub = publicPlugins ?? null;
187
204
  const names = new Set();
188
205
  for (const c of state.telemetry?.skillCalls ?? []) {
189
- if (c?.skill) names.add(String(c.skill));
206
+ if (!c?.skill) continue;
207
+ const skill = String(c.skill);
208
+ if (c.source === "repo") {
209
+ names.add("repo-local");
210
+ continue;
211
+ }
212
+ if (c.source === "marketplace-fallback") {
213
+ pub ??= publicToolkits(manifestPath);
214
+ const toolkit = String(c.toolkit || skill.split(":")[0]);
215
+ names.add(pub.has(toolkit) ? skill : "marketplace-fallback");
216
+ continue;
217
+ }
218
+ names.add(skill);
190
219
  }
191
220
  return Array.from(names).slice(0, 30);
192
221
  }
@@ -786,6 +815,7 @@ if (invokedDirectly) {
786
815
 
787
816
  export {
788
817
  endpointAllowed,
818
+ invokedCalls,
789
819
  integrationTags,
790
820
  enabledPlugins,
791
821
  readEnabledPluginNames,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-27T07:55:06Z",
3
+ "generatedAt": "2026-09-27T10:26:38Z",
4
4
  "skillCount": 218,
5
5
  "entries": [
6
6
  {