@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.
- package/CHANGELOG.md +43 -0
- package/docs/facts.json +1 -1
- package/install/_common.mjs +49 -0
- package/install/_dev-only-files.mjs +1 -0
- package/install/catalog-history.json +1 -0
- package/install/claude.mjs +13 -7
- package/manifest.json +40 -30
- package/package.json +3 -2
- package/pipeline/lib/claude-md-links.mjs +328 -0
- package/pipeline/lib/owned-path-gate.mjs +699 -0
- package/pipeline/lib/repo-profile-derive.mjs +1771 -0
- package/pipeline/lib/repo-profile.mjs +780 -0
- package/pipeline/lib/stack-detect.sh +59 -19
- package/pipeline/lib/unattended.mjs +17 -0
- package/pipeline/multi-agent-refs/features/repo-profile.md +96 -0
- package/pipeline/multi-agent-refs/features/review-decision.md +18 -13
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +179 -33
- package/pipeline/multi-agent-refs/outside-the-pipeline.md +33 -11
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +26 -12
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +24 -13
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +16 -4
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +8 -0
- package/pipeline/rules/outside-the-pipeline.md +6 -1
- package/pipeline/schemas/agent-state.schema.json +66 -2
- package/pipeline/schemas/phases.json +4 -4
- package/pipeline/schemas/repo-profile.schema.json +1107 -0
- package/pipeline/schemas/token-budget.json +4 -4
- package/pipeline/scripts/agent-guard.py +30 -0
- package/pipeline/scripts/owned-path-gate.mjs +205 -0
- package/pipeline/scripts/pre-commit-check.sh +151 -1
- package/pipeline/scripts/repo-profile.mjs +244 -0
- package/pipeline/scripts/review-decision-gate.mjs +42 -18
- package/pipeline/scripts/skill-candidates.mjs +882 -0
- package/pipeline/scripts/unattended_policy.py +90 -0
- package/pipeline/scripts/usage-report.mjs +36 -6
- 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
|
-
|
|
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))
|
|
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).
|
|
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
|
-
|
|
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)
|
|
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,
|