@mmerterden/multi-agent-pipeline 12.7.0 → 12.8.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 +126 -0
- package/install/_common.mjs +48 -0
- package/install/_dev-only-files.mjs +125 -5
- package/install/claude.mjs +14 -8
- package/install/copilot.mjs +5 -8
- package/package.json +17 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
- package/pipeline/lib/credential-store.sh +20 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
- package/pipeline/multi-agent-refs/phases/operations.md +28 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -0
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +49 -4
- package/pipeline/schemas/prefs.schema.json +6 -0
- package/pipeline/scripts/_smoke-root.sh +61 -0
- package/pipeline/scripts/audit-log.sh +25 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
- package/pipeline/eval/golden-tasks/README.md +0 -65
- package/pipeline/eval/intent-cases.json +0 -40
- package/pipeline/eval/run-metrics-fixture.json +0 -93
- package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
- package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
- package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
- package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
- package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
- package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
- package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
- package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
- package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
- package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
- package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
- package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
- package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
- package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
- package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
- package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
- package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
- package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
- package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
- package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
- package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
- package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
- package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
- package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
- package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
- package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
- package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
- package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
- package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
- package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
- package/pipeline/eval/triage/README.md +0 -54
- package/pipeline/scripts/benchmark-phase-0.sh +0 -128
- package/pipeline/scripts/check-md-links.mjs +0 -88
- package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
- package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
- package/pipeline/scripts/eval-intent.mjs +0 -107
- package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
- package/pipeline/scripts/eval-triage.mjs +0 -171
- package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
- package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
- package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
- package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
- package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
- package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
- package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
- package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
- package/pipeline/scripts/lint-skills.mjs +0 -154
- package/pipeline/scripts/run-smokes.mjs +0 -130
- package/pipeline/scripts/scorecard.mjs +0 -258
- package/pipeline/scripts/smoke-add-detail.sh +0 -137
- package/pipeline/scripts/smoke-agent-guard.sh +0 -74
- package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
- package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
- package/pipeline/scripts/smoke-ask-choice.sh +0 -42
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
- package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
- package/pipeline/scripts/smoke-changelog-version.sh +0 -47
- package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
- package/pipeline/scripts/smoke-channels-flow.sh +0 -130
- package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
- package/pipeline/scripts/smoke-clarify.sh +0 -148
- package/pipeline/scripts/smoke-command-inventory.sh +0 -81
- package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
- package/pipeline/scripts/smoke-community-gates.sh +0 -75
- package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
- package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
- package/pipeline/scripts/smoke-context-budget.sh +0 -72
- package/pipeline/scripts/smoke-cost-budget.sh +0 -70
- package/pipeline/scripts/smoke-cost-summary.sh +0 -139
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
- package/pipeline/scripts/smoke-description-tr.sh +0 -82
- package/pipeline/scripts/smoke-dev-critic.sh +0 -144
- package/pipeline/scripts/smoke-diff-explain.sh +0 -147
- package/pipeline/scripts/smoke-diff-risk.sh +0 -190
- package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
- package/pipeline/scripts/smoke-eval-live.sh +0 -136
- package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
- package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
- package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
- package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
- package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
- package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
- package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
- package/pipeline/scripts/smoke-generate-issue.sh +0 -120
- package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
- package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
- package/pipeline/scripts/smoke-install-layout.sh +0 -248
- package/pipeline/scripts/smoke-intent-guard.sh +0 -86
- package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
- package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
- package/pipeline/scripts/smoke-keychain.sh +0 -158
- package/pipeline/scripts/smoke-language-axis.sh +0 -109
- package/pipeline/scripts/smoke-learning-curve.sh +0 -61
- package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
- package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
- package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
- package/pipeline/scripts/smoke-md-links.sh +0 -8
- package/pipeline/scripts/smoke-md2confluence.sh +0 -126
- package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
- package/pipeline/scripts/smoke-migrate-state.sh +0 -102
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
- package/pipeline/scripts/smoke-model-fallback.sh +0 -89
- package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
- package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
- package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
- package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
- package/pipeline/scripts/smoke-pack-contents.sh +0 -140
- package/pipeline/scripts/smoke-pat-audit.sh +0 -128
- package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
- package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
- package/pipeline/scripts/smoke-phase-banner.sh +0 -101
- package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
- package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
- package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
- package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
- package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
- package/pipeline/scripts/smoke-plan-safety.sh +0 -139
- package/pipeline/scripts/smoke-plan-todos.sh +0 -196
- package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
- package/pipeline/scripts/smoke-pre-commit.sh +0 -170
- package/pipeline/scripts/smoke-pref-migration.sh +0 -226
- package/pipeline/scripts/smoke-prefs-language.sh +0 -134
- package/pipeline/scripts/smoke-progress-contract.sh +0 -127
- package/pipeline/scripts/smoke-prune-logs.sh +0 -137
- package/pipeline/scripts/smoke-purge.sh +0 -138
- package/pipeline/scripts/smoke-push-retry.sh +0 -75
- package/pipeline/scripts/smoke-repo-map.sh +0 -300
- package/pipeline/scripts/smoke-review-readiness.sh +0 -92
- package/pipeline/scripts/smoke-review-watch.sh +0 -146
- package/pipeline/scripts/smoke-routines.sh +0 -84
- package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
- package/pipeline/scripts/smoke-run-metrics.sh +0 -50
- package/pipeline/scripts/smoke-search.sh +0 -187
- package/pipeline/scripts/smoke-shadow-git.sh +0 -224
- package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
- package/pipeline/scripts/smoke-skill-language.sh +0 -83
- package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
- package/pipeline/scripts/smoke-skill-scan.sh +0 -198
- package/pipeline/scripts/smoke-source-parity.sh +0 -85
- package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
- package/pipeline/scripts/smoke-sync-parity.sh +0 -92
- package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
- package/pipeline/scripts/smoke-telemetry.sh +0 -147
- package/pipeline/scripts/smoke-test-gap.sh +0 -183
- package/pipeline/scripts/smoke-token-budget.sh +0 -67
- package/pipeline/scripts/smoke-token-preflight.sh +0 -82
- package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
- package/pipeline/scripts/smoke-triage-memory.sh +0 -174
- package/pipeline/scripts/smoke-update-check.sh +0 -135
- package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
- package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
- package/pipeline/scripts/smoke-validator-gates.sh +0 -164
- package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
- package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
- package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
- package/pipeline/scripts/smoke-work-summary.sh +0 -163
- package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
- package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
- package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
- package/pipeline/scripts/smoke-write-state.sh +0 -159
- package/pipeline/scripts/sync-parity-check.sh +0 -135
- package/pipeline/scripts/test-gap-rules/android.json +0 -25
- package/pipeline/scripts/test-gap-rules/ios.json +0 -34
- package/pipeline/scripts/test-gap-rules/node.json +0 -29
- package/pipeline/scripts/test-gap-rules/python.json +0 -25
- package/pipeline/scripts/validate-schemas.mjs +0 -88
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scorecard.mjs - the measurable part of the review score, as a gate.
|
|
3
|
-
//
|
|
4
|
-
// A refactor review scores ten categories out of ten. Six of those numbers can
|
|
5
|
-
// be measured; four cannot, and writing a number for those is how a scorecard
|
|
6
|
-
// turns into self-congratulation. So this reports exactly two things:
|
|
7
|
-
//
|
|
8
|
-
// MEASURED a metric with a threshold, pass or fail
|
|
9
|
-
// UNMEASURED a category no script can score, named as such
|
|
10
|
-
//
|
|
11
|
-
// Nothing here re-implements a check. Each metric either reads a committed
|
|
12
|
-
// threshold file or shells out to the gate that already owns the question, so
|
|
13
|
-
// there is one source of truth per question and the scorecard cannot drift from
|
|
14
|
-
// the gates it summarises.
|
|
15
|
-
//
|
|
16
|
-
// Thresholds are locked at the values measured when the gate was written. They
|
|
17
|
-
// are a ratchet: lowering one is a deliberate, reviewable edit, and a metric
|
|
18
|
-
// that improves should have its floor raised.
|
|
19
|
-
//
|
|
20
|
-
// Runs on a clean clone: no $HOME lookups, no credentials, no network. The one
|
|
21
|
-
// metric that needs local configuration (derived-skill drift) reports
|
|
22
|
-
// NOT CONFIGURED rather than passing by default.
|
|
23
|
-
//
|
|
24
|
-
// Usage: node pipeline/scripts/scorecard.mjs [--json]
|
|
25
|
-
// Exit: 0 every measured metric within threshold, 1 otherwise
|
|
26
|
-
|
|
27
|
-
import { execFileSync } from "node:child_process";
|
|
28
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
29
|
-
import { dirname, join } from "node:path";
|
|
30
|
-
import { fileURLToPath } from "node:url";
|
|
31
|
-
|
|
32
|
-
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
33
|
-
const asJson = process.argv.includes("--json");
|
|
34
|
-
|
|
35
|
-
// Locked floors. Raise when a metric improves; lowering one is the reviewable act.
|
|
36
|
-
const FLOORS = {
|
|
37
|
-
coverageLines: 72,
|
|
38
|
-
coverageBranches: 68,
|
|
39
|
-
coverageFunctions: 85,
|
|
40
|
-
contextBytes: 60000,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const results = [];
|
|
44
|
-
|
|
45
|
-
function measured(category, metric, ok, detail) {
|
|
46
|
-
results.push({ kind: "MEASURED", category, metric, ok, detail });
|
|
47
|
-
}
|
|
48
|
-
function unmeasured(category, why) {
|
|
49
|
-
results.push({ kind: "UNMEASURED", category, why });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Run a gate and report whether it passed. The gate owns the assertion; this
|
|
53
|
-
// only records the verdict, so the two can never disagree.
|
|
54
|
-
function gate(category, metric, argv) {
|
|
55
|
-
try {
|
|
56
|
-
execFileSync(argv[0], argv.slice(1), { cwd: ROOT, stdio: "pipe" });
|
|
57
|
-
return measured(category, metric, true, "gate passed");
|
|
58
|
-
} catch (e) {
|
|
59
|
-
const out = `${e.stdout || ""}${e.stderr || ""}`.trim().split("\n").slice(-3).join(" | ");
|
|
60
|
-
return measured(category, metric, false, out || `exit ${e.status}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// --- Tests: the coverage floors must not be lowered ------------------------
|
|
65
|
-
// c8 enforces the numbers on every run; what nothing enforced was the numbers
|
|
66
|
-
// themselves. .c8rc.json reported 83% while the real figure over its own include
|
|
67
|
-
// set was 33%, because `all` was missing - so the floors are worth pinning.
|
|
68
|
-
{
|
|
69
|
-
const c8 = JSON.parse(readFileSync(join(ROOT, ".c8rc.json"), "utf-8"));
|
|
70
|
-
const checks = [
|
|
71
|
-
["lines", c8.lines, FLOORS.coverageLines],
|
|
72
|
-
["branches", c8.branches, FLOORS.coverageBranches],
|
|
73
|
-
["functions", c8.functions, FLOORS.coverageFunctions],
|
|
74
|
-
];
|
|
75
|
-
const low = checks.filter(([, actual, floor]) => !(actual >= floor));
|
|
76
|
-
measured(
|
|
77
|
-
"Tests",
|
|
78
|
-
"coverage floors held",
|
|
79
|
-
c8.all === true && low.length === 0,
|
|
80
|
-
c8.all !== true
|
|
81
|
-
? "`all` is not set, so the floors only cover files the tests already load"
|
|
82
|
-
: low.length
|
|
83
|
-
? low.map(([k, a, f]) => `${k} ${a} below the locked ${f}`).join(", ")
|
|
84
|
-
: checks.map(([k, a]) => `${k} ${a}`).join(", "),
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// --- Dependency management: no high or critical advisories ------------------
|
|
89
|
-
{
|
|
90
|
-
let vulns;
|
|
91
|
-
try {
|
|
92
|
-
const raw = execFileSync("npm", ["audit", "--json"], { cwd: ROOT, encoding: "utf-8" });
|
|
93
|
-
vulns = JSON.parse(raw).metadata.vulnerabilities;
|
|
94
|
-
} catch (e) {
|
|
95
|
-
// npm audit exits non-zero when it finds something; the JSON is still on stdout.
|
|
96
|
-
try {
|
|
97
|
-
vulns = JSON.parse(e.stdout).metadata.vulnerabilities;
|
|
98
|
-
} catch {
|
|
99
|
-
vulns = null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (!vulns) {
|
|
103
|
-
measured(
|
|
104
|
-
"Dependency management",
|
|
105
|
-
"no high or critical advisories",
|
|
106
|
-
false,
|
|
107
|
-
"npm audit produced no parseable report",
|
|
108
|
-
);
|
|
109
|
-
} else {
|
|
110
|
-
const bad = vulns.high + vulns.critical;
|
|
111
|
-
measured(
|
|
112
|
-
"Dependency management",
|
|
113
|
-
"no high or critical advisories",
|
|
114
|
-
bad === 0,
|
|
115
|
-
`critical ${vulns.critical}, high ${vulns.high}, moderate ${vulns.moderate}`,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// --- Performance: the bytes every run pays before it starts ----------------
|
|
121
|
-
gate("Performance", "fixed per-run context within budget", [
|
|
122
|
-
"bash",
|
|
123
|
-
"pipeline/scripts/smoke-context-budget.sh",
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
// --- CI/CD: the workflow audit runs, and finds nothing ---------------------
|
|
127
|
-
// Strict by default, so a runner without the linters fails here rather than
|
|
128
|
-
// reporting a clean audit it never performed.
|
|
129
|
-
gate("CI/CD", "workflow audit clean (zizmor + actionlint)", [
|
|
130
|
-
"bash",
|
|
131
|
-
"pipeline/scripts/smoke-workflow-audit.sh",
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
// --- Code quality: our own punctuation rule, both encodings ---------------
|
|
135
|
-
gate("Code quality", "no banned punctuation in shipped files", [
|
|
136
|
-
"bash",
|
|
137
|
-
"pipeline/scripts/smoke-own-punctuation.sh",
|
|
138
|
-
]);
|
|
139
|
-
|
|
140
|
-
// --- Documentation: every skill description routes ------------------------
|
|
141
|
-
gate("Documentation", "every skill description has a routing clause", [
|
|
142
|
-
"node",
|
|
143
|
-
"pipeline/scripts/lint-skills.mjs",
|
|
144
|
-
]);
|
|
145
|
-
|
|
146
|
-
// --- Documentation: the licence and changelog a consumer expects ----------
|
|
147
|
-
{
|
|
148
|
-
const missing = ["LICENSE", "CHANGELOG.md", "README.md", "SECURITY.md"].filter(
|
|
149
|
-
(f) => !existsSync(join(ROOT, f)),
|
|
150
|
-
);
|
|
151
|
-
measured(
|
|
152
|
-
"Documentation",
|
|
153
|
-
"licence, changelog, readme, security present",
|
|
154
|
-
missing.length === 0,
|
|
155
|
-
missing.length ? `missing: ${missing.join(", ")}` : "all four present",
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// --- Tests: no suite reports success without asserting anything ------------
|
|
160
|
-
// The runner owns this check; asking it for the counts would re-run 120 suites,
|
|
161
|
-
// so the scorecard asserts the capability is wired rather than re-measuring it.
|
|
162
|
-
{
|
|
163
|
-
const runner = readFileSync(join(ROOT, "pipeline", "scripts", "run-smokes.mjs"), "utf-8");
|
|
164
|
-
const wired = runner.includes("countAssertions") && /asserted nothing/.test(runner);
|
|
165
|
-
measured(
|
|
166
|
-
"Tests",
|
|
167
|
-
"runner rejects a suite that asserted nothing",
|
|
168
|
-
wired,
|
|
169
|
-
wired
|
|
170
|
-
? "zero-assertion check present in run-smokes.mjs"
|
|
171
|
-
: "run-smokes.mjs no longer counts assertions",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// --- Drift: needs local configuration, so it reports honestly -------------
|
|
176
|
-
{
|
|
177
|
-
// The exit code alone cannot answer this. check-derived-drift exits 0 both
|
|
178
|
-
// when every derivation is current AND when nothing is configured at all, and
|
|
179
|
-
// those are different facts: one is a pass, the other is an absence of
|
|
180
|
-
// evidence. Reading `configured` off the JSON is what tells them apart. The
|
|
181
|
-
// first version of this block trusted exit 0 and reported an unconfigured
|
|
182
|
-
// machine as a passing metric, which is the exact failure this whole gate set
|
|
183
|
-
// exists to prevent.
|
|
184
|
-
let state;
|
|
185
|
-
let ok = false;
|
|
186
|
-
let parsed;
|
|
187
|
-
try {
|
|
188
|
-
parsed = JSON.parse(
|
|
189
|
-
execFileSync("node", ["pipeline/scripts/check-derived-drift.mjs", "--json"], {
|
|
190
|
-
cwd: ROOT,
|
|
191
|
-
encoding: "utf-8",
|
|
192
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
193
|
-
}),
|
|
194
|
-
);
|
|
195
|
-
} catch (e) {
|
|
196
|
-
// Non-zero exit still writes the report to stdout; an unparseable one means
|
|
197
|
-
// the check itself could not run.
|
|
198
|
-
try {
|
|
199
|
-
parsed = JSON.parse(e.stdout);
|
|
200
|
-
} catch {
|
|
201
|
-
parsed = undefined;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (parsed && parsed.configured > 0) {
|
|
205
|
-
const bad = parsed.drifted + parsed.unverified + parsed.unreachable;
|
|
206
|
-
ok = bad === 0;
|
|
207
|
-
state = ok
|
|
208
|
-
? `${parsed.configured} derivation(s) current with upstream`
|
|
209
|
-
: `${parsed.drifted} drifted, ${parsed.unverified} unverified, ${parsed.unreachable} unreachable`;
|
|
210
|
-
} else if (parsed) {
|
|
211
|
-
state = "NOT CONFIGURED - prefs carry no derivedSkillSources on this machine";
|
|
212
|
-
} else {
|
|
213
|
-
state = "NOT CONFIGURED - the drift check produced no parseable report";
|
|
214
|
-
}
|
|
215
|
-
if (state.startsWith("NOT CONFIGURED")) {
|
|
216
|
-
unmeasured("Derived-skill drift", state);
|
|
217
|
-
} else {
|
|
218
|
-
measured("Architecture", "derived skills current with upstream", ok, state);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// --- What no script can score --------------------------------------------
|
|
223
|
-
unmeasured("Architecture", "layer separation and dependency direction are judgement, not a number");
|
|
224
|
-
unmeasured("DevEx", "onboarding time and error-message quality need a person");
|
|
225
|
-
unmeasured(
|
|
226
|
-
"Accessibility",
|
|
227
|
-
"nothing in this repo renders UI; the audits it ships are scored per target app",
|
|
228
|
-
);
|
|
229
|
-
unmeasured(
|
|
230
|
-
"Code quality",
|
|
231
|
-
"naming, cohesion and altitude are judgement; only the mechanical rules above are gated",
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const failed = results.filter((r) => r.kind === "MEASURED" && !r.ok);
|
|
235
|
-
const passed = results.filter((r) => r.kind === "MEASURED" && r.ok);
|
|
236
|
-
|
|
237
|
-
if (asJson) {
|
|
238
|
-
console.log(JSON.stringify({ passed: passed.length, failed: failed.length, results }, null, 2));
|
|
239
|
-
} else {
|
|
240
|
-
console.log("MEASURED");
|
|
241
|
-
for (const r of results.filter((x) => x.kind === "MEASURED")) {
|
|
242
|
-
console.log(` ${r.ok ? "✓" : "✗"} ${r.category}: ${r.metric}`);
|
|
243
|
-
console.log(` ${r.detail}`);
|
|
244
|
-
}
|
|
245
|
-
console.log("");
|
|
246
|
-
console.log("UNMEASURED (reported, never scored)");
|
|
247
|
-
for (const r of results.filter((x) => x.kind === "UNMEASURED")) {
|
|
248
|
-
console.log(` - ${r.category}: ${r.why}`);
|
|
249
|
-
}
|
|
250
|
-
console.log("");
|
|
251
|
-
console.log(`══ scorecard: ${passed.length} measured metrics passed, ${failed.length} failed ══`);
|
|
252
|
-
if (failed.length === 0) {
|
|
253
|
-
console.log("Every mechanically checkable claim holds. The four unmeasured");
|
|
254
|
-
console.log("categories above are not covered by that statement.");
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
process.exit(failed.length === 0 ? 0 : 1);
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-add-detail.sh - contract check for the `add-detail` command.
|
|
3
|
-
#
|
|
4
|
-
# Does NOT hit real Bitbucket / Jira / GitHub. Instead:
|
|
5
|
-
# 1. Generates a fake PR diff and body fixture.
|
|
6
|
-
# 2. Runs the body-file + jq --rawfile + curl --data-binary pipeline
|
|
7
|
-
# against a local `jq` / `python -c` simulator that checks the
|
|
8
|
-
# resulting JSON payload round-trips with:
|
|
9
|
-
# - real newlines (no literal "\n")
|
|
10
|
-
# - no HTML entities (& / < / " / ')
|
|
11
|
-
# - required Bitbucket PUT fields present: reviewers, fromRef, toRef, draft
|
|
12
|
-
# 3. Returns non-zero if any contract is violated.
|
|
13
|
-
#
|
|
14
|
-
# Intended as a CI guard so regressions like the "23 reviewers wiped" incident
|
|
15
|
-
# cannot land unnoticed.
|
|
16
|
-
|
|
17
|
-
set -euo pipefail
|
|
18
|
-
|
|
19
|
-
TMP="$(mktemp -d -t multi-agent-smoke-XXXX)" || { echo "FATAL: mktemp failed" >&2; exit 1; }
|
|
20
|
-
trap 'rm -rf "$TMP"' EXIT
|
|
21
|
-
|
|
22
|
-
PASS=0
|
|
23
|
-
FAIL=0
|
|
24
|
-
|
|
25
|
-
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
26
|
-
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
27
|
-
|
|
28
|
-
need() {
|
|
29
|
-
command -v "$1" >/dev/null 2>&1 || {
|
|
30
|
-
echo "error: $1 is required" >&2
|
|
31
|
-
exit 2
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
need jq
|
|
35
|
-
need python3
|
|
36
|
-
|
|
37
|
-
echo "→ Writing fixture body with newlines, '&' and Turkish chars"
|
|
38
|
-
cat > "$TMP/body.md" <<'EOF'
|
|
39
|
-
## Özet
|
|
40
|
-
|
|
41
|
-
- Kök neden: UILabel'e `adjustsFontSizeToFitWidth` eklenmemiş
|
|
42
|
-
- Çözüm: `minimumScaleFactor = 0.7` ile ölçek desteği
|
|
43
|
-
- Etki: İspanyolca & Almanca'da taşma düzeliyor
|
|
44
|
-
|
|
45
|
-
## Test Senaryoları
|
|
46
|
-
|
|
47
|
-
| # | Ön koşul | Adımlar | Beklenen |
|
|
48
|
-
|---|----------|---------|----------|
|
|
49
|
-
| 1 | Dil = ES | Check-in Summary aç | Şehir isimleri sığar |
|
|
50
|
-
EOF
|
|
51
|
-
|
|
52
|
-
echo "→ Building payload with jq --rawfile"
|
|
53
|
-
jq -n \
|
|
54
|
-
--rawfile body "$TMP/body.md" \
|
|
55
|
-
--arg title 'fix(checkin): Spanish overflow [PROJ-12345]' \
|
|
56
|
-
'{
|
|
57
|
-
title: $title,
|
|
58
|
-
description: $body,
|
|
59
|
-
fromRef: { id: "refs/heads/bugfix/PROJ-12345", repository: { slug: "my-app", project: { key: "APP" } } },
|
|
60
|
-
toRef: { id: "refs/heads/develop", repository: { slug: "my-app", project: { key: "APP" } } },
|
|
61
|
-
reviewers: [{ user: { name: "alice" } }, { user: { name: "bob" } }],
|
|
62
|
-
draft: false,
|
|
63
|
-
state: "OPEN", open: true, closed: false, locked: false
|
|
64
|
-
}' > "$TMP/payload.json"
|
|
65
|
-
|
|
66
|
-
echo "→ Contract checks"
|
|
67
|
-
|
|
68
|
-
# Contract 1: payload is valid JSON.
|
|
69
|
-
if jq -e . "$TMP/payload.json" >/dev/null 2>&1; then
|
|
70
|
-
pass "payload is valid JSON"
|
|
71
|
-
else
|
|
72
|
-
fail "payload is not valid JSON"
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
# Contract 2: description field round-trips with real newlines (not literal \n).
|
|
76
|
-
DESC="$(jq -r .description "$TMP/payload.json")"
|
|
77
|
-
if [[ "$DESC" == *$'\n'* ]]; then
|
|
78
|
-
pass "description contains real newlines"
|
|
79
|
-
else
|
|
80
|
-
fail "description is single-line - newlines were escaped"
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
# Contract 3: no literal backslash-n sequence survived.
|
|
84
|
-
if grep -q '\\n' <<< "$DESC"; then
|
|
85
|
-
fail "literal \\n escaped into description"
|
|
86
|
-
else
|
|
87
|
-
pass "no literal \\n in description"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# Contract 4: no HTML entities in title or description.
|
|
91
|
-
for field in title description; do
|
|
92
|
-
VALUE="$(jq -r --arg f "$field" '.[$f]' "$TMP/payload.json")"
|
|
93
|
-
if grep -Eq '&(amp|lt|gt|quot|#39);' <<< "$VALUE"; then
|
|
94
|
-
fail "$field contains HTML entities"
|
|
95
|
-
else
|
|
96
|
-
pass "$field has no HTML entities"
|
|
97
|
-
fi
|
|
98
|
-
done
|
|
99
|
-
|
|
100
|
-
# Contract 5: UTF-8 survives end-to-end (checks 'Ö', 'İ', 'ç').
|
|
101
|
-
for glyph in 'Ö' 'İ' 'ç'; do
|
|
102
|
-
if grep -q "$glyph" <<< "$DESC"; then
|
|
103
|
-
pass "UTF-8 glyph '$glyph' preserved"
|
|
104
|
-
else
|
|
105
|
-
fail "UTF-8 glyph '$glyph' missing from description"
|
|
106
|
-
fi
|
|
107
|
-
done
|
|
108
|
-
|
|
109
|
-
# Contract 6: Bitbucket PUT contract - all of reviewers/fromRef/toRef/draft present.
|
|
110
|
-
for field in reviewers fromRef toRef draft; do
|
|
111
|
-
if jq -e "has(\"$field\")" "$TMP/payload.json" >/dev/null; then
|
|
112
|
-
pass "payload has .$field"
|
|
113
|
-
else
|
|
114
|
-
fail "payload missing .$field (Bitbucket will wipe it)"
|
|
115
|
-
fi
|
|
116
|
-
done
|
|
117
|
-
|
|
118
|
-
# Contract 7: reviewers is a non-empty array for this fixture.
|
|
119
|
-
LEN="$(jq '.reviewers | length' "$TMP/payload.json")"
|
|
120
|
-
if [ "$LEN" -gt 0 ]; then
|
|
121
|
-
pass "reviewers array preserved ($LEN entries)"
|
|
122
|
-
else
|
|
123
|
-
fail "reviewers array is empty - would wipe defaults"
|
|
124
|
-
fi
|
|
125
|
-
|
|
126
|
-
# Contract 8: fromRef / toRef are distinct (PR to self is rejected by Bitbucket).
|
|
127
|
-
FROM="$(jq -r .fromRef.id "$TMP/payload.json")"
|
|
128
|
-
TO="$(jq -r .toRef.id "$TMP/payload.json")"
|
|
129
|
-
if [ "$FROM" != "$TO" ]; then
|
|
130
|
-
pass "fromRef ($FROM) ≠ toRef ($TO)"
|
|
131
|
-
else
|
|
132
|
-
fail "fromRef == toRef - invalid PR"
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
echo
|
|
136
|
-
echo "══ add-detail smoke: $PASS passed, $FAIL failed ══"
|
|
137
|
-
[ "$FAIL" -eq 0 ]
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-agent-guard.sh - behavioral contract for the agent-guard PreToolUse hook.
|
|
3
|
-
#
|
|
4
|
-
# Verifies the guard blocks (exit 2) AI-attribution commits and force-push to a
|
|
5
|
-
# protected branch, allows (exit 0) everything else, fails open on bad input, and
|
|
6
|
-
# NEVER executes the inspected command (injection safety).
|
|
7
|
-
#
|
|
8
|
-
# Runs the guard from the repo copy in a throwaway git repo whose branch we set,
|
|
9
|
-
# so the "bare push on a protected branch" path is exercised deterministically.
|
|
10
|
-
#
|
|
11
|
-
# Exit 0 = all pass, 1 = any failure.
|
|
12
|
-
|
|
13
|
-
set -uo pipefail
|
|
14
|
-
|
|
15
|
-
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
16
|
-
GUARD="$ROOT/pipeline/scripts/agent-guard.sh"
|
|
17
|
-
command -v python3 >/dev/null 2>&1 || { echo "error: python3 required" >&2; exit 127; }
|
|
18
|
-
[ -f "$GUARD" ] || { echo "FAIL: agent-guard.sh missing at $GUARD" >&2; exit 1; }
|
|
19
|
-
[ -f "$ROOT/pipeline/scripts/agent-guard.py" ] || { echo "FAIL: agent-guard.py missing" >&2; exit 1; }
|
|
20
|
-
|
|
21
|
-
PASS=0; FAIL=0
|
|
22
|
-
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
23
|
-
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
24
|
-
|
|
25
|
-
# throwaway repo on a protected branch so `git rev-parse` inside the guard = main
|
|
26
|
-
WORK="$(mktemp -d)"
|
|
27
|
-
trap 'rm -rf "$WORK"' EXIT
|
|
28
|
-
# init on main WITH a commit so `git rev-parse --abbrev-ref HEAD` resolves to main
|
|
29
|
-
# (an unborn branch reports HEAD, which would not exercise the bare-push path)
|
|
30
|
-
( cd "$WORK" \
|
|
31
|
-
&& git init -q \
|
|
32
|
-
&& git symbolic-ref HEAD refs/heads/main \
|
|
33
|
-
&& git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init ) 2>/dev/null
|
|
34
|
-
|
|
35
|
-
J() { python3 -c 'import json,sys; print(json.dumps({"tool_input":{"command":sys.argv[1]}}))' "$1"; }
|
|
36
|
-
run() { # label expected-exit command-string
|
|
37
|
-
local got
|
|
38
|
-
( cd "$WORK" && printf '%s' "$(J "$3")" | bash "$GUARD" >/dev/null 2>&1 )
|
|
39
|
-
got=$?
|
|
40
|
-
if [ "$got" = "$2" ]; then pass "[$got] $1"; else fail "expected $2 got $got: $1"; fi
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
echo "→ blocks (exit 2)"
|
|
44
|
-
run "commit Co-Authored-By: Claude" 2 'git commit -m "x
|
|
45
|
-
|
|
46
|
-
Co-Authored-By: Claude <noreply@anthropic.com>"'
|
|
47
|
-
run "commit robot-emoji" 2 'git commit -m "x
|
|
48
|
-
|
|
49
|
-
🤖 Generated with Claude Code"'
|
|
50
|
-
run "push --force origin main" 2 'git push --force origin main'
|
|
51
|
-
run "push -f origin develop" 2 'git push -f origin develop'
|
|
52
|
-
run "push --force-with-lease master" 2 'git push --force-with-lease origin master'
|
|
53
|
-
run "bare push -f on main" 2 'git push -f'
|
|
54
|
-
|
|
55
|
-
echo "→ allows (exit 0)"
|
|
56
|
-
run "normal commit" 0 'git commit -m "feat: normal"'
|
|
57
|
-
run "push no force" 0 'git push origin main'
|
|
58
|
-
run "force push feature branch" 0 'git push --force origin feature/DEMO-1'
|
|
59
|
-
run "force push tag" 0 'git push -f origin v1.2.3'
|
|
60
|
-
run "non-git command" 0 'ls -la /tmp'
|
|
61
|
-
run "empty command" 0 ''
|
|
62
|
-
|
|
63
|
-
echo "→ fail-open on bad input"
|
|
64
|
-
( cd "$WORK" && printf 'not-json{' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "malformed json -> allow" || fail "malformed json blocked"
|
|
65
|
-
( cd "$WORK" && printf '' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "empty stdin -> allow" || fail "empty stdin blocked"
|
|
66
|
-
|
|
67
|
-
echo "→ injection safety (inspected command must NOT execute)"
|
|
68
|
-
SENT="$WORK/sentinel"; : > "$SENT"
|
|
69
|
-
( cd "$WORK" && printf '%s' "$(J "git commit -m \"\$(rm -f '$SENT')\"")" | bash "$GUARD" >/dev/null 2>&1 )
|
|
70
|
-
[ -f "$SENT" ] && pass "sentinel survived (no execution)" || fail "sentinel deleted - command executed"
|
|
71
|
-
|
|
72
|
-
echo ""
|
|
73
|
-
echo "══ agent-guard smoke: $PASS passed, $FAIL failed ══"
|
|
74
|
-
[ "$FAIL" -eq 0 ]
|