@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,218 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// lint-mcp-refs.mjs - every mcp__<server>__<tool> reference must be callable.
|
|
3
|
-
//
|
|
4
|
-
// A host composes an MCP tool name as mcp__<registered-server-name>__<tool>.
|
|
5
|
-
// The pipeline had 100 references to `mcp__dev_toolkit__*` while the server is
|
|
6
|
-
// registered as `dev-toolkit`, so the real names are `mcp__dev-toolkit__*`.
|
|
7
|
-
// Nothing caught it: prose is not executed, and a wrong name inside an
|
|
8
|
-
// `allowed-tools:` allowlist silently denies the tool instead of erroring.
|
|
9
|
-
//
|
|
10
|
-
// Checks (errors - fail the gate):
|
|
11
|
-
// 1. the server segment must be a known registered server name
|
|
12
|
-
// 2. the known-bad snake_case spellings are called out with their fix
|
|
13
|
-
//
|
|
14
|
-
// Optional deep check (advisory - never fails):
|
|
15
|
-
// 3. when the companion dev-toolkit repo resolves locally (via
|
|
16
|
-
// prefs.global.devToolkit.localPath, or the mcpServers registration in
|
|
17
|
-
// ~/.claude.json / ~/.claude/settings.json), handshake it over stdio and
|
|
18
|
-
// confirm every referenced dev-toolkit tool actually exists in tools/list.
|
|
19
|
-
// CI has no local checkout, so an unresolvable repo is a skip, not a fail.
|
|
20
|
-
//
|
|
21
|
-
// Zero-dependency. Runs inside `npm test` and ci-lite.
|
|
22
|
-
//
|
|
23
|
-
// Usage: node pipeline/scripts/lint-mcp-refs.mjs [--strict-tools]
|
|
24
|
-
// Exit: 0 clean, 1 unknown server segment (or a missing tool with --strict-tools).
|
|
25
|
-
|
|
26
|
-
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
27
|
-
import { homedir } from "node:os";
|
|
28
|
-
import { dirname, join, resolve } from "node:path";
|
|
29
|
-
import { fileURLToPath } from "node:url";
|
|
30
|
-
import { spawnSync } from "node:child_process";
|
|
31
|
-
|
|
32
|
-
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
33
|
-
const strictTools = process.argv.includes("--strict-tools");
|
|
34
|
-
|
|
35
|
-
// Servers the pipeline is allowed to reference. The key is the registered
|
|
36
|
-
// server name as it appears in an mcpServers config, which is what the host
|
|
37
|
-
// interpolates into the tool name.
|
|
38
|
-
const KNOWN_SERVERS = {
|
|
39
|
-
"dev-toolkit": "companion device/browser toolkit (@mmerterden/dev-toolkit-mcp)",
|
|
40
|
-
claude_ai_Figma: "Figma MCP (analysis phase only)",
|
|
41
|
-
XcodeBuildMCP: "optional third-party Xcode build server",
|
|
42
|
-
};
|
|
43
|
-
// Spellings that look right but cannot resolve to a registered server.
|
|
44
|
-
const KNOWN_BAD = { dev_toolkit: "dev-toolkit", "claude-ai-Figma": "claude_ai_Figma" };
|
|
45
|
-
|
|
46
|
-
const SCAN_DIRS = ["pipeline/commands", "pipeline/skills", "pipeline/multi-agent-refs"];
|
|
47
|
-
const SCAN_EXT = /\.(md|sh|mjs|json)$/;
|
|
48
|
-
const PRUNE = new Set([
|
|
49
|
-
"node_modules",
|
|
50
|
-
".git",
|
|
51
|
-
".worktrees",
|
|
52
|
-
"Pods",
|
|
53
|
-
".build",
|
|
54
|
-
"DerivedData",
|
|
55
|
-
".next",
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
function walk(dir, out = []) {
|
|
59
|
-
let entries;
|
|
60
|
-
try {
|
|
61
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
62
|
-
} catch {
|
|
63
|
-
return out;
|
|
64
|
-
}
|
|
65
|
-
for (const e of entries) {
|
|
66
|
-
if (PRUNE.has(e.name)) continue;
|
|
67
|
-
const p = join(dir, e.name);
|
|
68
|
-
if (e.isDirectory()) walk(p, out);
|
|
69
|
-
else if (SCAN_EXT.test(e.name)) out.push(p);
|
|
70
|
-
}
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const files = SCAN_DIRS.flatMap((d) => walk(join(repoRoot, d)));
|
|
75
|
-
const REF = /mcp__([A-Za-z0-9_-]+?)__([a-z0-9_]+)/g;
|
|
76
|
-
|
|
77
|
-
const errors = [];
|
|
78
|
-
const refs = new Map(); // server -> Map(tool -> Set(file:line))
|
|
79
|
-
|
|
80
|
-
for (const file of files) {
|
|
81
|
-
const rel = file.slice(repoRoot.length + 1);
|
|
82
|
-
const lines = readFileSync(file, "utf8").split("\n");
|
|
83
|
-
lines.forEach((line, i) => {
|
|
84
|
-
for (const m of line.matchAll(REF)) {
|
|
85
|
-
const [, server, tool] = m;
|
|
86
|
-
// Prose writes families as mcp__dev-toolkit__ios_* - a wildcard, not a tool.
|
|
87
|
-
const next = line[m.index + m[0].length];
|
|
88
|
-
if (next === "*" || tool.endsWith("_")) continue;
|
|
89
|
-
const where = `${rel}:${i + 1}`;
|
|
90
|
-
if (!refs.has(server)) refs.set(server, new Map());
|
|
91
|
-
const tools = refs.get(server);
|
|
92
|
-
if (!tools.has(tool)) tools.set(tool, new Set());
|
|
93
|
-
tools.get(tool).add(where);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const [server, tools] of refs) {
|
|
99
|
-
if (server in KNOWN_SERVERS) continue;
|
|
100
|
-
const fix = KNOWN_BAD[server];
|
|
101
|
-
const sample = [...tools.values()][0].values().next().value;
|
|
102
|
-
errors.push(
|
|
103
|
-
fix
|
|
104
|
-
? `unknown MCP server segment "${server}" (${tools.size} tool refs, e.g. ${sample}) - the registered server is "${fix}", so the prefix must be mcp__${fix}__`
|
|
105
|
-
: `unknown MCP server segment "${server}" (${tools.size} tool refs, e.g. ${sample}) - add it to KNOWN_SERVERS in lint-mcp-refs.mjs if this server is expected, or fix the prefix to match the registered server name`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ---- optional deep check: do the referenced dev-toolkit tools exist? --------
|
|
110
|
-
|
|
111
|
-
function resolveToolkitRepo() {
|
|
112
|
-
const prefsPath = join(homedir(), ".claude", "multi-agent-preferences.json");
|
|
113
|
-
if (existsSync(prefsPath)) {
|
|
114
|
-
try {
|
|
115
|
-
const dt = JSON.parse(readFileSync(prefsPath, "utf8"))?.global?.devToolkit;
|
|
116
|
-
if (dt && dt.enabled === false) return null;
|
|
117
|
-
if (dt?.localPath) {
|
|
118
|
-
const p = dt.localPath.replace(/^~|\$HOME/, homedir());
|
|
119
|
-
if (existsSync(join(p, "package.json"))) return p;
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
/* fall through to registration lookup */
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const candidates = [];
|
|
126
|
-
const collect = (obj) => {
|
|
127
|
-
for (const [name, cfg] of Object.entries(obj || {})) {
|
|
128
|
-
const arg = Array.isArray(cfg?.args) ? cfg.args.find((a) => /\.(mjs|js)$/.test(a)) : null;
|
|
129
|
-
if (name.includes("dev") && arg) candidates.push(dirname(resolve(arg)));
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
for (const cfgPath of [
|
|
133
|
-
join(homedir(), ".claude.json"),
|
|
134
|
-
join(homedir(), ".claude", "settings.json"),
|
|
135
|
-
]) {
|
|
136
|
-
if (!existsSync(cfgPath)) continue;
|
|
137
|
-
try {
|
|
138
|
-
const json = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
139
|
-
collect(json.mcpServers);
|
|
140
|
-
for (const proj of Object.values(json.projects || {})) collect(proj.mcpServers);
|
|
141
|
-
} catch {
|
|
142
|
-
/* ignore unreadable config */
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
for (const c of candidates) {
|
|
146
|
-
try {
|
|
147
|
-
const pkg = JSON.parse(readFileSync(join(c, "package.json"), "utf8"));
|
|
148
|
-
if (pkg.dependencies?.["@modelcontextprotocol/sdk"] && statSync(join(c, ".git"))) return c;
|
|
149
|
-
} catch {
|
|
150
|
-
/* not the toolkit */
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function listTools(repo) {
|
|
157
|
-
const frames =
|
|
158
|
-
[
|
|
159
|
-
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"lint-mcp-refs","version":"1"}}}',
|
|
160
|
-
'{"jsonrpc":"2.0","method":"notifications/initialized"}',
|
|
161
|
-
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}',
|
|
162
|
-
].join("\n") + "\n";
|
|
163
|
-
const r = spawnSync(process.execPath, ["index.js"], {
|
|
164
|
-
cwd: repo,
|
|
165
|
-
input: frames,
|
|
166
|
-
encoding: "utf8",
|
|
167
|
-
timeout: 20000,
|
|
168
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
169
|
-
});
|
|
170
|
-
if (r.error || !r.stdout) return null;
|
|
171
|
-
for (const line of r.stdout.split("\n")) {
|
|
172
|
-
if (!line.trim()) continue;
|
|
173
|
-
try {
|
|
174
|
-
const msg = JSON.parse(line);
|
|
175
|
-
if (msg.id === 2 && msg.result?.tools) return msg.result.tools.map((t) => t.name);
|
|
176
|
-
} catch {
|
|
177
|
-
/* not a frame */
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const referencedToolkitTools = refs.get("dev-toolkit");
|
|
184
|
-
let deep = "skipped (no local dev-toolkit checkout)";
|
|
185
|
-
if (referencedToolkitTools) {
|
|
186
|
-
const repo = resolveToolkitRepo();
|
|
187
|
-
const served = repo ? listTools(repo) : null;
|
|
188
|
-
if (!served) {
|
|
189
|
-
deep = repo ? `skipped (${repo} did not answer tools/list)` : deep;
|
|
190
|
-
} else {
|
|
191
|
-
const missing = [...referencedToolkitTools.keys()].filter((t) => !served.includes(t));
|
|
192
|
-
if (missing.length === 0) {
|
|
193
|
-
deep = `all ${referencedToolkitTools.size} referenced tools exist in the live server (${served.length} served)`;
|
|
194
|
-
} else {
|
|
195
|
-
const msg = `dev-toolkit tools referenced but not served (${served.length} served): ${missing
|
|
196
|
-
.map((t) => `${t} [${[...referencedToolkitTools.get(t)][0]}]`)
|
|
197
|
-
.join(", ")}`;
|
|
198
|
-
deep = `${missing.length} referenced tool(s) not served`;
|
|
199
|
-
if (strictTools) errors.push(msg);
|
|
200
|
-
else console.warn(` WARNING - ${msg}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const totalRefs = [...refs.values()].reduce((n, tools) => n + tools.size, 0);
|
|
206
|
-
console.log(
|
|
207
|
-
`lint-mcp-refs: ${totalRefs} distinct tool references across ${refs.size} server(s) in ${files.length} files`,
|
|
208
|
-
);
|
|
209
|
-
for (const [server, tools] of refs) {
|
|
210
|
-
console.log(` ${server in KNOWN_SERVERS ? "✓" : "✗"} mcp__${server}__* : ${tools.size} tools`);
|
|
211
|
-
}
|
|
212
|
-
console.log(` tool-existence check: ${deep}`);
|
|
213
|
-
|
|
214
|
-
if (errors.length > 0) {
|
|
215
|
-
console.error(`lint-mcp-refs: ${errors.length} error(s)`);
|
|
216
|
-
for (const e of errors) console.error(" " + e);
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Frontmatter linter for every SKILL.md shipped by the pipeline.
|
|
3
|
-
//
|
|
4
|
-
// Until v10.0.0 the shipped SKILL.md files were counted by smokes but never
|
|
5
|
-
// content-checked, so a skill could ship without the frontmatter that
|
|
6
|
-
// Claude Code / Copilot CLI need to register it. This gate enforces the
|
|
7
|
-
// minimal contract; it is zero-dependency (line-based YAML subset) so it
|
|
8
|
-
// runs inside `npm test` and ci-lite without installs.
|
|
9
|
-
//
|
|
10
|
-
// Contract per SKILL.md (errors - fail the gate):
|
|
11
|
-
// - frontmatter block present (--- ... ---) starting at line 1
|
|
12
|
-
// - name: required, lowercase kebab-case (underscores tolerated for
|
|
13
|
-
// legacy skills already published under a snake_case name)
|
|
14
|
-
// - description: required, non-empty, single line, <= 1024 chars
|
|
15
|
-
// - user-invocable: when present, must be true or false
|
|
16
|
-
// - platform: when present, must be ios | android | common | all
|
|
17
|
-
// - description: must carry a routing/trigger clause ("use when ..."), the
|
|
18
|
-
// one thing it is for - blocking since all 193 skills have one
|
|
19
|
-
//
|
|
20
|
-
// Spec-conformance signals (warnings - never fail the gate):
|
|
21
|
-
// - name should match the agent-skills spec pattern
|
|
22
|
-
// (lowercase letters/digits/hyphens, <= 64 chars)
|
|
23
|
-
// - a body over ~500 lines with no companion-file reference should be
|
|
24
|
-
// split into references/ files loaded on demand
|
|
25
|
-
|
|
26
|
-
import { readFileSync } from "node:fs";
|
|
27
|
-
import { execSync } from "node:child_process";
|
|
28
|
-
import { dirname, join } from "node:path";
|
|
29
|
-
import { fileURLToPath } from "node:url";
|
|
30
|
-
|
|
31
|
-
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
32
|
-
// Scope to pipeline/skills only. Slash commands under pipeline/commands also use
|
|
33
|
-
// the <name>/SKILL.md layout now, but they are command definitions
|
|
34
|
-
// (description + allowed-tools, name derived from the dir), NOT skills, so the
|
|
35
|
-
// skill frontmatter contract (required `name:`) does not apply to them.
|
|
36
|
-
const files = execSync("find pipeline/skills -name SKILL.md", { cwd: repoRoot, encoding: "utf-8" })
|
|
37
|
-
.trim()
|
|
38
|
-
.split("\n")
|
|
39
|
-
.filter(Boolean)
|
|
40
|
-
.sort();
|
|
41
|
-
|
|
42
|
-
const errors = [];
|
|
43
|
-
const fail = (file, msg) => errors.push(`${file}: ${msg}`);
|
|
44
|
-
// Non-fatal quality signals (a terse description routes poorly). Warnings never
|
|
45
|
-
// fail the gate; they surface skills whose frontmatter could trigger better.
|
|
46
|
-
const warnings = [];
|
|
47
|
-
const warn = (file, msg) => warnings.push(`${file}: ${msg}`);
|
|
48
|
-
|
|
49
|
-
for (const file of files) {
|
|
50
|
-
const raw = readFileSync(join(repoRoot, file), "utf-8");
|
|
51
|
-
|
|
52
|
-
if (!raw.startsWith("---\n")) {
|
|
53
|
-
fail(file, "missing frontmatter block at line 1");
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const end = raw.indexOf("\n---", 4);
|
|
57
|
-
if (end === -1) {
|
|
58
|
-
fail(file, "frontmatter block never closed");
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const fields = {};
|
|
63
|
-
for (const line of raw.slice(4, end).split("\n")) {
|
|
64
|
-
const m = line.match(/^([a-zA-Z][a-zA-Z0-9-]*):\s*(.*)$/);
|
|
65
|
-
if (m) fields[m[1]] = m[2].replace(/^["']|["']$/g, "").trim();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!fields.name) {
|
|
69
|
-
fail(file, "missing required field: name");
|
|
70
|
-
} else if (!/^[a-z0-9]+([-_][a-z0-9]+)*$/.test(fields.name)) {
|
|
71
|
-
fail(file, `name is not lowercase kebab/snake-case: "${fields.name}"`);
|
|
72
|
-
} else {
|
|
73
|
-
// Spec conformance (warn-only): the agent-skills spec allows lowercase
|
|
74
|
-
// letters, digits, and hyphens, max 64 chars. Snake_case passes the
|
|
75
|
-
// legacy error check above but is out of spec.
|
|
76
|
-
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(fields.name)) {
|
|
77
|
-
warn(file, `name is outside the agent-skills spec pattern (hyphens only): "${fields.name}"`);
|
|
78
|
-
}
|
|
79
|
-
if (fields.name.length > 64) {
|
|
80
|
-
warn(file, `name exceeds the spec's 64-char limit (${fields.name.length})`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!("description" in fields)) {
|
|
85
|
-
fail(file, "missing required field: description");
|
|
86
|
-
} else if (fields.description.length === 0) {
|
|
87
|
-
fail(file, "description is empty");
|
|
88
|
-
} else if (fields.description.length > 1024) {
|
|
89
|
-
fail(file, `description exceeds 1024 chars (${fields.description.length})`);
|
|
90
|
-
} else {
|
|
91
|
-
// Routing quality: a description of a few words rarely says what the skill
|
|
92
|
-
// does AND when to use it, so the model cannot decide to trigger it.
|
|
93
|
-
// Skip YAML block scalars (`description: |` / `>`): this line-based parser
|
|
94
|
-
// only sees the indicator, not the real multi-line text, so a word count
|
|
95
|
-
// would be a false positive.
|
|
96
|
-
const desc = fields.description;
|
|
97
|
-
const isBlockScalar = /^[|>][+-]?\d*$/.test(desc);
|
|
98
|
-
const words = desc.split(/\s+/).filter(Boolean).length;
|
|
99
|
-
if (!isBlockScalar && words < 6) {
|
|
100
|
-
warn(file, `description is terse (${words} words) - say what it does AND when to use it`);
|
|
101
|
-
}
|
|
102
|
-
// Routing quality (BLOCKING since the backlog reached zero). A description
|
|
103
|
-
// without a trigger clause tells the model what the skill is and nothing
|
|
104
|
-
// about when to reach for it, which is the one job the description has -
|
|
105
|
-
// it is the routing rule, not a summary.
|
|
106
|
-
//
|
|
107
|
-
// This was warn-only while 93 of 193 skills were missing one, because a
|
|
108
|
-
// gate that fires on half the tree gets ignored. All 93 were written, so
|
|
109
|
-
// the baseline is zero and it can hold the line. Block scalars are skipped
|
|
110
|
-
// for the same parser reason as above.
|
|
111
|
-
const ROUTING_RE =
|
|
112
|
-
/\b(use (this|it|when|for|whenever)|when (the|a|an|you|asked|working|building|implementing|writing|reviewing)|triggers?|invoked?( when| for)?|applies when|activated?)\b/i;
|
|
113
|
-
if (!isBlockScalar && !ROUTING_RE.test(desc)) {
|
|
114
|
-
fail(file, 'description has no routing/trigger clause (e.g. "use when ...")');
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Body size (warn-only): the agent-skills guidance is to keep SKILL.md
|
|
119
|
-
// lean and push detail into companion files loaded on demand. A ~500+
|
|
120
|
-
// line body that references no companion file loads whole every time.
|
|
121
|
-
const body = raw.slice(end + 4);
|
|
122
|
-
const bodyLines = body.split("\n").length;
|
|
123
|
-
if (bodyLines > 500) {
|
|
124
|
-
const referencesCompanion =
|
|
125
|
-
/\]\((?!https?:|mailto:|#)[^)]+\)/.test(body) ||
|
|
126
|
-
/`[^`\n]*\/[^`\n]*\.(md|sh|mjs|json|py|txt)`/.test(body);
|
|
127
|
-
if (!referencesCompanion) {
|
|
128
|
-
warn(
|
|
129
|
-
file,
|
|
130
|
-
`body is ${bodyLines} lines with no companion-file reference - consider splitting into references/`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if ("user-invocable" in fields && !["true", "false"].includes(fields["user-invocable"])) {
|
|
136
|
-
fail(file, `user-invocable must be true|false, got "${fields["user-invocable"]}"`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if ("platform" in fields && !["ios", "android", "common", "all"].includes(fields.platform)) {
|
|
140
|
-
fail(file, `platform must be ios|android|common|all, got "${fields.platform}"`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (warnings.length > 0) {
|
|
145
|
-
console.warn(`lint-skills: ${warnings.length} warning(s) (non-fatal):`);
|
|
146
|
-
for (const w of warnings) console.warn(" " + w);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (errors.length > 0) {
|
|
150
|
-
console.error(`lint-skills: ${errors.length} error(s) across ${files.length} SKILL.md files`);
|
|
151
|
-
for (const e of errors) console.error(" " + e);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
console.log(`lint-skills: ${files.length} SKILL.md files OK`);
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Structured runner for pipeline/scripts/smoke-*.sh.
|
|
3
|
-
//
|
|
4
|
-
// Replaces the bare `for f in smoke-*.sh; do bash "$f" || exit 1; done`
|
|
5
|
-
// loop with per-suite timeouts, timing, and a summary, so a hung smoke
|
|
6
|
-
// (network wait, interactive prompt) can never block `npm test` or CI
|
|
7
|
-
// indefinitely.
|
|
8
|
-
//
|
|
9
|
-
// Env:
|
|
10
|
-
// SMOKE_TIMEOUT_SECONDS per-suite timeout (default 180)
|
|
11
|
-
// SMOKE_NO_BAIL=1 run every suite and summarize instead of
|
|
12
|
-
// stopping at the first failure
|
|
13
|
-
// Args:
|
|
14
|
-
// optional substring filters: `node run-smokes.mjs adapters keychain`
|
|
15
|
-
// runs only suites whose filename contains one of the terms.
|
|
16
|
-
|
|
17
|
-
import { spawnSync } from "node:child_process";
|
|
18
|
-
import { readdirSync } from "node:fs";
|
|
19
|
-
import { dirname, join } from "node:path";
|
|
20
|
-
import { fileURLToPath } from "node:url";
|
|
21
|
-
|
|
22
|
-
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const timeoutSec = Number(process.env.SMOKE_TIMEOUT_SECONDS || 180);
|
|
24
|
-
const bail = process.env.SMOKE_NO_BAIL !== "1";
|
|
25
|
-
const argv = process.argv.slice(2);
|
|
26
|
-
const reportCounts = argv.includes("--counts");
|
|
27
|
-
const filters = argv.filter((a) => !a.startsWith("--"));
|
|
28
|
-
|
|
29
|
-
// How many assertions did a suite actually make?
|
|
30
|
-
//
|
|
31
|
-
// Exit code alone cannot answer that, and the difference matters: three smokes
|
|
32
|
-
// were exiting 0 having checked nothing. smoke-workflow-audit skipped both its
|
|
33
|
-
// linters because neither was installed anywhere, so the GitHub Actions
|
|
34
|
-
// security audit had never run while showing green - it was hiding a
|
|
35
|
-
// high-severity finding. smoke-plugin-validate had no manifest to validate in
|
|
36
|
-
// this repo at all. A gate that cannot fail is worse than no gate, because the
|
|
37
|
-
// green tick is read as coverage.
|
|
38
|
-
//
|
|
39
|
-
// The four output shapes actually in use here, surveyed across all suites
|
|
40
|
-
// rather than assumed. Anything a suite prints that matches none of them
|
|
41
|
-
// counts as zero, which is the intended answer: an unrecognisable result is
|
|
42
|
-
// not evidence that something was checked.
|
|
43
|
-
//
|
|
44
|
-
// 1. "══ name: 22 passed, 0 failed ══" most suites
|
|
45
|
-
// 2. "check-md-links: 212 files OK" counted sweeps
|
|
46
|
-
// 3. per-line "✓" / "ok " / "PASS:" suites with no summary line
|
|
47
|
-
// 4. "smoke-language-axis: ok" terse single-verdict suites
|
|
48
|
-
function countAssertions(stdout) {
|
|
49
|
-
const passed = [...stdout.matchAll(/(\d+) passed/g)];
|
|
50
|
-
if (passed.length > 0) return Number(passed[passed.length - 1][1]);
|
|
51
|
-
|
|
52
|
-
const swept = [...stdout.matchAll(/(\d+) (?:files|patterns|checks|entries) (?:OK|clean)/gi)];
|
|
53
|
-
if (swept.length > 0) return Number(swept[swept.length - 1][1]);
|
|
54
|
-
|
|
55
|
-
const marks = stdout.match(/^[ \t]*(✓|ok |PASS[: ])/gm);
|
|
56
|
-
if (marks) return marks.length;
|
|
57
|
-
|
|
58
|
-
if (/^\S+: ok\s*$/m.test(stdout)) return 1;
|
|
59
|
-
|
|
60
|
-
return 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const suites = readdirSync(scriptsDir)
|
|
64
|
-
.filter((f) => f.startsWith("smoke-") && f.endsWith(".sh"))
|
|
65
|
-
.filter((f) => filters.length === 0 || filters.some((t) => f.includes(t)))
|
|
66
|
-
.sort();
|
|
67
|
-
|
|
68
|
-
if (suites.length === 0) {
|
|
69
|
-
console.error("run-smokes: no smoke suites matched");
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const failures = [];
|
|
74
|
-
let ran = 0;
|
|
75
|
-
const startedAt = Date.now();
|
|
76
|
-
|
|
77
|
-
for (const suite of suites) {
|
|
78
|
-
ran++;
|
|
79
|
-
const t0 = Date.now();
|
|
80
|
-
const res = spawnSync("bash", [join(scriptsDir, suite)], {
|
|
81
|
-
encoding: "utf-8",
|
|
82
|
-
timeout: timeoutSec * 1000,
|
|
83
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
84
|
-
});
|
|
85
|
-
const secs = ((Date.now() - t0) / 1000).toFixed(1);
|
|
86
|
-
|
|
87
|
-
const timedOut = res.error && res.error.code === "ETIMEDOUT";
|
|
88
|
-
const asserted = countAssertions(res.stdout || "");
|
|
89
|
-
const ok = !timedOut && res.status === 0 && (reportCounts || asserted > 0);
|
|
90
|
-
|
|
91
|
-
if (reportCounts) {
|
|
92
|
-
console.log(`${String(asserted).padStart(4)} ${suite}`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (ok) {
|
|
97
|
-
console.log(`ok ${suite} (${secs}s, ${asserted} checks)`);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!timedOut && res.status === 0 && asserted === 0) {
|
|
102
|
-
console.error(
|
|
103
|
-
`FAIL ${suite} (exited 0 having asserted nothing, ${secs}s)\n` +
|
|
104
|
-
" A suite that checks nothing reports coverage it does not have.\n" +
|
|
105
|
-
" Give it something to assert, or delete it.",
|
|
106
|
-
);
|
|
107
|
-
if (res.stdout) console.error(res.stdout.split("\n").slice(-15).join("\n"));
|
|
108
|
-
failures.push(suite);
|
|
109
|
-
if (bail) break;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const reason = timedOut ? `timeout after ${timeoutSec}s` : `exit ${res.status}`;
|
|
114
|
-
console.error(`FAIL ${suite} (${reason}, ${secs}s)`);
|
|
115
|
-
if (res.stdout) console.error(res.stdout.split("\n").slice(-30).join("\n"));
|
|
116
|
-
if (res.stderr) console.error(res.stderr.split("\n").slice(-30).join("\n"));
|
|
117
|
-
failures.push(suite);
|
|
118
|
-
if (bail) break;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const totalSecs = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
122
|
-
const skipped = suites.length - ran;
|
|
123
|
-
console.log(
|
|
124
|
-
`run-smokes: ${ran - failures.length}/${suites.length} passed in ${totalSecs}s` +
|
|
125
|
-
(skipped > 0 ? ` (${skipped} not run after bail)` : ""),
|
|
126
|
-
);
|
|
127
|
-
if (failures.length > 0) {
|
|
128
|
-
console.error(`run-smokes: failed -> ${failures.join(", ")}`);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|