@intentius/chant-lexicon-github 0.124.30 → 0.124.32
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/dist/action-pins.js +53 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +142 -0
- package/dist/codegen/generate-cli.js +26 -0
- package/dist/codegen/generate-lexicon.js +36 -0
- package/dist/codegen/generate-typescript.js +108 -0
- package/dist/codegen/generate.js +111 -0
- package/dist/codegen/naming.js +49 -0
- package/dist/codegen/package.js +40 -0
- package/dist/codegen/parse.js +595 -0
- package/dist/codegen/patches.js +36 -0
- package/dist/components/ci-jobs.js +84 -0
- package/dist/components/generate-op-pipeline.js +440 -0
- package/dist/components/generate-op-waves-pipeline.js +158 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +476 -0
- package/dist/composites/cache.js +17 -0
- package/dist/composites/catalog.js +859 -0
- package/dist/composites/checkout.js +25 -0
- package/dist/composites/dependabot.js +43 -0
- package/dist/composites/deploy-environment.js +38 -0
- package/dist/composites/docker-build.js +70 -0
- package/dist/composites/download-artifact.js +18 -0
- package/dist/composites/go-ci.js +62 -0
- package/dist/composites/index.js +15 -0
- package/dist/composites/node-ci.js +44 -0
- package/dist/composites/node-pipeline.js +96 -0
- package/dist/composites/pr-plan-report.js +129 -0
- package/dist/composites/python-ci.js +60 -0
- package/dist/composites/setup-go.js +18 -0
- package/dist/composites/setup-node.js +21 -0
- package/dist/composites/setup-python.js +18 -0
- package/dist/composites/upload-artifact.js +19 -0
- package/dist/coverage.js +32 -0
- package/dist/expression.js +177 -0
- package/dist/generated/index.js +26 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/graph-edges.js +174 -0
- package/dist/import/generator.js +108 -0
- package/dist/import/parser.js +69 -0
- package/dist/index.js +22 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +69 -0
- package/dist/lint/audit-lineage.js +168 -0
- package/dist/lint/post-synth/gha006.js +50 -0
- package/dist/lint/post-synth/gha009.js +35 -0
- package/dist/lint/post-synth/gha011.js +34 -0
- package/dist/lint/post-synth/gha013.js +40 -0
- package/dist/lint/post-synth/gha017.js +25 -0
- package/dist/lint/post-synth/gha018.js +33 -0
- package/dist/lint/post-synth/gha019.js +58 -0
- package/dist/lint/post-synth/gha021.js +42 -0
- package/dist/lint/post-synth/gha022.js +44 -0
- package/dist/lint/post-synth/gha023.js +37 -0
- package/dist/lint/post-synth/gha024.js +32 -0
- package/dist/lint/post-synth/gha025.js +33 -0
- package/dist/lint/post-synth/gha026.js +32 -0
- package/dist/lint/post-synth/gha027.js +46 -0
- package/dist/lint/post-synth/gha028.js +28 -0
- package/dist/lint/post-synth/gha029.js +56 -0
- package/dist/lint/post-synth/gha030.js +35 -0
- package/dist/lint/post-synth/gha031.js +75 -0
- package/dist/lint/post-synth/gha032.js +35 -0
- package/dist/lint/post-synth/gha033.js +39 -0
- package/dist/lint/post-synth/gha034.js +35 -0
- package/dist/lint/post-synth/gha035.js +50 -0
- package/dist/lint/post-synth/gha036.js +37 -0
- package/dist/lint/post-synth/gha037.js +41 -0
- package/dist/lint/post-synth/gha038.js +35 -0
- package/dist/lint/post-synth/gha039.js +34 -0
- package/dist/lint/post-synth/gha040.js +36 -0
- package/dist/lint/post-synth/gha041.js +32 -0
- package/dist/lint/post-synth/gha042.js +33 -0
- package/dist/lint/post-synth/gha043.js +35 -0
- package/dist/lint/post-synth/gha044.js +36 -0
- package/dist/lint/post-synth/gha045.js +33 -0
- package/dist/lint/post-synth/gha046.js +55 -0
- package/dist/lint/post-synth/gha047.js +44 -0
- package/dist/lint/post-synth/gha048.js +34 -0
- package/dist/lint/post-synth/gha049.js +36 -0
- package/dist/lint/post-synth/gha050.js +37 -0
- package/dist/lint/post-synth/gha051.js +38 -0
- package/dist/lint/post-synth/gha052.js +35 -0
- package/dist/lint/post-synth/gha053.js +34 -0
- package/dist/lint/post-synth/gha054.js +35 -0
- package/dist/lint/post-synth/gha055.js +44 -0
- package/dist/lint/post-synth/gha056.js +32 -0
- package/dist/lint/post-synth/gha057.js +35 -0
- package/dist/lint/post-synth/gha058.js +42 -0
- package/dist/lint/post-synth/gha059.js +119 -0
- package/dist/lint/post-synth/gha060.js +129 -0
- package/dist/lint/post-synth/gha061.js +78 -0
- package/dist/lint/post-synth/gha062.js +65 -0
- package/dist/lint/post-synth/gha063.js +56 -0
- package/dist/lint/post-synth/gha064.js +47 -0
- package/dist/lint/post-synth/gha065.js +125 -0
- package/dist/lint/post-synth/gha066.js +40 -0
- package/dist/lint/post-synth/gha067.js +75 -0
- package/dist/lint/post-synth/gha068.js +47 -0
- package/dist/lint/post-synth/gha069.js +99 -0
- package/dist/lint/post-synth/index.js +114 -0
- package/dist/lint/post-synth/yaml-helpers.js +465 -0
- package/dist/lint/rules/data/action-usage-policy.js +16 -0
- package/dist/lint/rules/data/advisory-feed.js +15 -0
- package/dist/lint/rules/data/deprecated-versions.js +14 -0
- package/dist/lint/rules/data/flagged-actions.js +18 -0
- package/dist/lint/rules/data/known-action-slugs.js +38 -0
- package/dist/lint/rules/data/known-actions.js +12 -0
- package/dist/lint/rules/data/preinstalled-tools.js +36 -0
- package/dist/lint/rules/data/recommended-inputs.js +9 -0
- package/dist/lint/rules/data/risky-features.js +22 -0
- package/dist/lint/rules/data/secret-patterns.js +30 -0
- package/dist/lint/rules/data/trusted-action-owners.js +12 -0
- package/dist/lint/rules/data/untrusted-contexts.js +44 -0
- package/dist/lint/rules/deprecated-action-version.js +44 -0
- package/dist/lint/rules/detect-secrets.js +45 -0
- package/dist/lint/rules/extract-inline-structs.js +55 -0
- package/dist/lint/rules/file-job-limit.js +43 -0
- package/dist/lint/rules/index.js +16 -0
- package/dist/lint/rules/job-timeout.js +53 -0
- package/dist/lint/rules/missing-recommended-inputs.js +51 -0
- package/dist/lint/rules/no-hardcoded-secrets.js +39 -0
- package/dist/lint/rules/no-raw-expressions.js +44 -0
- package/dist/lint/rules/suggest-cache.js +60 -0
- package/dist/lint/rules/use-condition-builders.js +37 -0
- package/dist/lint/rules/use-matrix-builder.js +36 -0
- package/dist/lint/rules/use-typed-actions.js +41 -0
- package/dist/lint/rules/validate-concurrency.js +59 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +33 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +152 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +344 -0
- package/dist/serializer.js +572 -0
- package/dist/spec/fetch.js +46 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +38 -0
- package/dist/variables.js +41 -0
- package/package.json +6 -6
- package/src/codegen/package.ts +2 -2
- package/src/components/__fixtures__/pr-loop-build.github.golden.yml +6 -2
- package/src/components/__fixtures__/pr-loop-waves.github.golden.yml +178 -0
- package/src/components/__fixtures__/pr-loop.github.golden.yml +6 -2
- package/src/components/generate-pipeline.ts +66 -29
- package/src/components/generate-pr-pipeline.test.ts +44 -20
- package/src/components/generate-pr-waves-pipeline.test.ts +117 -0
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GHA016: Validate Concurrency
|
|
3
|
+
*
|
|
4
|
+
* Flags `new Concurrency({cancelInProgress: true})` without `group`.
|
|
5
|
+
*/
|
|
6
|
+
import { ts } from "@intentius/chant/lint/typescript";
|
|
7
|
+
export const validateConcurrencyRule = {
|
|
8
|
+
id: "GHA016",
|
|
9
|
+
severity: "warning",
|
|
10
|
+
category: "correctness",
|
|
11
|
+
description: "Concurrency with cancel-in-progress should specify a group",
|
|
12
|
+
check(context) {
|
|
13
|
+
const { sourceFile } = context;
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
function visit(node) {
|
|
16
|
+
if (ts.isNewExpression(node)) {
|
|
17
|
+
let isConcurrency = false;
|
|
18
|
+
if (ts.isIdentifier(node.expression) && node.expression.text === "Concurrency")
|
|
19
|
+
isConcurrency = true;
|
|
20
|
+
if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "Concurrency")
|
|
21
|
+
isConcurrency = true;
|
|
22
|
+
if (isConcurrency && node.arguments?.length) {
|
|
23
|
+
const arg = node.arguments[0];
|
|
24
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
25
|
+
let hasCancelInProgress = false;
|
|
26
|
+
let hasGroup = false;
|
|
27
|
+
for (const prop of arg.properties) {
|
|
28
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
29
|
+
if (prop.name.text === "cancelInProgress" || prop.name.text === "cancel-in-progress") {
|
|
30
|
+
// Check if it's set to true
|
|
31
|
+
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
32
|
+
hasCancelInProgress = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (prop.name.text === "group") {
|
|
36
|
+
hasGroup = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (hasCancelInProgress && !hasGroup) {
|
|
41
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
42
|
+
diagnostics.push({
|
|
43
|
+
file: sourceFile.fileName,
|
|
44
|
+
line: line + 1,
|
|
45
|
+
column: character + 1,
|
|
46
|
+
ruleId: "GHA016",
|
|
47
|
+
severity: "warning",
|
|
48
|
+
message: "Concurrency with cancel-in-progress should specify a group to avoid cancelling unrelated runs.",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
ts.forEachChild(node, visit);
|
|
55
|
+
}
|
|
56
|
+
visit(sourceFile);
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAapF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAE1E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
3
|
+
import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
|
+
let cachedIndex = null;
|
|
6
|
+
function getIndex() {
|
|
7
|
+
if (cachedIndex)
|
|
8
|
+
return cachedIndex;
|
|
9
|
+
const data = require("../generated/lexicon-github.json");
|
|
10
|
+
cachedIndex = new LexiconIndex(data);
|
|
11
|
+
return cachedIndex;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Provide GitHub Actions completions based on context.
|
|
15
|
+
*/
|
|
16
|
+
export function githubCompletions(ctx) {
|
|
17
|
+
return lexiconCompletions(ctx, getIndex(), "GitHub Actions entity");
|
|
18
|
+
}
|
package/dist/lsp/hover.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAa1E;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAEpE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
3
|
+
import { LexiconIndex, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
|
+
let cachedIndex = null;
|
|
6
|
+
function getIndex() {
|
|
7
|
+
if (cachedIndex)
|
|
8
|
+
return cachedIndex;
|
|
9
|
+
const data = require("../generated/lexicon-github.json");
|
|
10
|
+
cachedIndex = new LexiconIndex(data);
|
|
11
|
+
return cachedIndex;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Provide hover information for GitHub Actions entity types.
|
|
15
|
+
*/
|
|
16
|
+
export function githubHover(ctx) {
|
|
17
|
+
return lexiconHover(ctx, getIndex(), resourceHover);
|
|
18
|
+
}
|
|
19
|
+
function resourceHover(className, entry) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`**${className}**`);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(`GitHub Actions type: \`${entry.resourceType}\``);
|
|
24
|
+
if (entry.kind === "resource") {
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push("*Resource entity — serialized as a top-level workflow/job entry*");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("*Property entity — used as a nested value in resources*");
|
|
31
|
+
}
|
|
32
|
+
return { contents: lines.join("\n") };
|
|
33
|
+
}
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only MCP tools that expose what `chant build` already computes about a
|
|
3
|
+
* GitHub Actions workflow — its jobs and run order, what it pulls in from
|
|
4
|
+
* outside (actions, images), and its security findings — so an agent can ask
|
|
5
|
+
* about a change *before it runs or merges* (#327).
|
|
6
|
+
*
|
|
7
|
+
* The GitHub counterpart to the GitLab context tools. Every tool builds from
|
|
8
|
+
* source and returns data. None of them touch the live GitHub instance, read
|
|
9
|
+
* run history, or write anything — that boundary is what keeps chant a context
|
|
10
|
+
* producer and not a copy of the live tooling.
|
|
11
|
+
*/
|
|
12
|
+
import { build } from "@intentius/chant/build";
|
|
13
|
+
import { runPostSynthChecks, getPrimaryOutput } from "@intentius/chant/lint/post-synth";
|
|
14
|
+
import { postSynthChecks } from "../lint/post-synth/index.js";
|
|
15
|
+
import { githubSerializer } from "../serializer.js";
|
|
16
|
+
import { extractJobs, extractActionRefs, extractImageRefs, extractTriggers, extractWorkflowName, } from "../lint/post-synth/yaml-helpers.js";
|
|
17
|
+
/** Build the project and return the emitted GitHub workflow YAML plus the raw result. */
|
|
18
|
+
async function buildGithub(path) {
|
|
19
|
+
const result = await build(path, [githubSerializer]);
|
|
20
|
+
const out = result.outputs.get("github");
|
|
21
|
+
return { yaml: out ? getPrimaryOutput(out) : "", result };
|
|
22
|
+
}
|
|
23
|
+
/** Discover the lexicon's post-synth checks without depending on the plugin. */
|
|
24
|
+
function githubPostSynthChecks() {
|
|
25
|
+
return postSynthChecks;
|
|
26
|
+
}
|
|
27
|
+
/** Is a `uses:` ref pinned to a full commit SHA (the only immutable form)? */
|
|
28
|
+
export function actionPinned(ref) {
|
|
29
|
+
const at = ref.lastIndexOf("@");
|
|
30
|
+
return at !== -1 && /^[0-9a-f]{40}$/.test(ref.slice(at + 1));
|
|
31
|
+
}
|
|
32
|
+
/** Jobs that would re-run because they depend (transitively) on `job`. */
|
|
33
|
+
export function downstreamJobs(yaml, job) {
|
|
34
|
+
const jobs = extractJobs(yaml);
|
|
35
|
+
// edge: upstream -> downstream (J depends on each of its needs)
|
|
36
|
+
const downstreamOf = new Map();
|
|
37
|
+
for (const [name, j] of jobs) {
|
|
38
|
+
for (const dep of j.needs ?? []) {
|
|
39
|
+
const arr = downstreamOf.get(dep) ?? [];
|
|
40
|
+
arr.push(name);
|
|
41
|
+
downstreamOf.set(dep, arr);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const out = new Set();
|
|
45
|
+
const queue = [job];
|
|
46
|
+
while (queue.length) {
|
|
47
|
+
const cur = queue.shift();
|
|
48
|
+
for (const next of downstreamOf.get(cur) ?? []) {
|
|
49
|
+
if (!out.has(next)) {
|
|
50
|
+
out.add(next);
|
|
51
|
+
queue.push(next);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...out];
|
|
56
|
+
}
|
|
57
|
+
const PATH_INPUT = {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
path: { type: "string", description: "Path to the chant project directory (default: current directory)" },
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
/** The read-only context tools for the GitHub lexicon. */
|
|
64
|
+
export function githubContextTools() {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
name: "github:checks",
|
|
68
|
+
description: "Build the workflow and return its security and correctness findings (the GHA checks) as JSON. Read-only — does not touch the live GitHub instance.",
|
|
69
|
+
inputSchema: PATH_INPUT,
|
|
70
|
+
async handler(params) {
|
|
71
|
+
const { yaml, result } = await buildGithub(params.path ?? ".");
|
|
72
|
+
if (!yaml)
|
|
73
|
+
return { findings: [], note: "no GitHub workflow produced from this project" };
|
|
74
|
+
const scoped = { ...result, outputs: new Map([["github", result.outputs.get("github")]]) };
|
|
75
|
+
const diags = runPostSynthChecks(githubPostSynthChecks(), scoped);
|
|
76
|
+
return {
|
|
77
|
+
findings: diags.map((d) => ({
|
|
78
|
+
id: d.checkId,
|
|
79
|
+
severity: d.severity,
|
|
80
|
+
job: d.entity ?? null,
|
|
81
|
+
message: d.message,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "github:workflow",
|
|
88
|
+
description: "Build the workflow and return its triggers and jobs as written (name, what each job runs after, step count) — before anything runs. Read-only.",
|
|
89
|
+
inputSchema: PATH_INPUT,
|
|
90
|
+
async handler(params) {
|
|
91
|
+
const { yaml } = await buildGithub(params.path ?? ".");
|
|
92
|
+
const jobs = [...extractJobs(yaml).values()].map((j) => ({
|
|
93
|
+
name: j.name,
|
|
94
|
+
runsAfter: j.needs ?? [],
|
|
95
|
+
steps: j.steps?.length ?? 0,
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
workflow: extractWorkflowName(yaml) ?? null,
|
|
99
|
+
triggers: Object.keys(extractTriggers(yaml)),
|
|
100
|
+
jobs,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "github:references",
|
|
106
|
+
description: "Build the workflow and list everything it pulls in from outside (actions via uses:, container/service images) and whether each is pinned to an immutable commit SHA. Read-only.",
|
|
107
|
+
inputSchema: PATH_INPUT,
|
|
108
|
+
async handler(params) {
|
|
109
|
+
const { yaml } = await buildGithub(params.path ?? ".");
|
|
110
|
+
const actions = extractActionRefs(yaml).map((a) => ({
|
|
111
|
+
kind: a.level === "job" ? "reusable-workflow" : "action",
|
|
112
|
+
job: a.job,
|
|
113
|
+
source: a.ref,
|
|
114
|
+
pinned: actionPinned(a.ref),
|
|
115
|
+
}));
|
|
116
|
+
const images = extractImageRefs(yaml).map((i) => ({
|
|
117
|
+
kind: "image",
|
|
118
|
+
job: i.job,
|
|
119
|
+
source: i.image,
|
|
120
|
+
pinned: i.image.includes("@sha256:"),
|
|
121
|
+
}));
|
|
122
|
+
return [...actions, ...images];
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "github:affected",
|
|
127
|
+
description: "Build the workflow and, given a job name, list the jobs that would re-run because they depend on it (the needs chain). Read-only.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
path: { type: "string", description: "Path to the chant project directory (default: current directory)" },
|
|
132
|
+
job: { type: "string", description: "Job name to trace downstream from" },
|
|
133
|
+
},
|
|
134
|
+
required: ["job"],
|
|
135
|
+
},
|
|
136
|
+
async handler(params) {
|
|
137
|
+
const { yaml } = await buildGithub(params.path ?? ".");
|
|
138
|
+
const job = params.job;
|
|
139
|
+
return { job, wouldRerun: downstreamJobs(yaml, job) };
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "github:workflow-yaml",
|
|
144
|
+
description: "Build the project and return the generated GitHub Actions workflow YAML as a string. Read-only.",
|
|
145
|
+
inputSchema: PATH_INPUT,
|
|
146
|
+
async handler(params) {
|
|
147
|
+
const { yaml } = await buildGithub(params.path ?? ".");
|
|
148
|
+
return { yaml };
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Thin entry point for `npm run bundle` in lexicon-github.
|
|
4
|
+
*/
|
|
5
|
+
import { generate, writeGeneratedFiles } from "./codegen/generate.js";
|
|
6
|
+
import { packageLexicon } from "./codegen/package.js";
|
|
7
|
+
import { writeBundleSpec } from "@intentius/chant/codegen/package";
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
// 1. Generate src/generated/ files
|
|
12
|
+
const genResult = await generate({ verbose: true });
|
|
13
|
+
writeGeneratedFiles(genResult, pkgDir);
|
|
14
|
+
console.error(`Generated ${genResult.resources} entities, ${genResult.properties} property types, ${genResult.enums} enums`);
|
|
15
|
+
// 2. Run package pipeline and write dist/
|
|
16
|
+
const { spec, stats } = await packageLexicon({ verbose: true });
|
|
17
|
+
const distDir = join(pkgDir, "dist");
|
|
18
|
+
writeBundleSpec(spec, distDir);
|
|
19
|
+
console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
20
|
+
console.error(`dist/ written to ${distDir}`);
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Actions lexicon plugin.
|
|
3
|
+
*
|
|
4
|
+
* Provides serializer, template detection, and code generation
|
|
5
|
+
* for GitHub Actions workflows.
|
|
6
|
+
*/
|
|
7
|
+
import { compositeCatalog } from "./composites/catalog.js";
|
|
8
|
+
import { postSynthChecks as postSynthCheckList } from "./lint/post-synth/index.js";
|
|
9
|
+
import { githubAuditCatalog } from "./lint/audit-catalog.js";
|
|
10
|
+
import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
|
|
11
|
+
import { githubSerializer } from "./serializer.js";
|
|
12
|
+
import { githubGraphEdges } from "./graph-edges.js";
|
|
13
|
+
import { useTypedActionsRule } from "./lint/rules/use-typed-actions.js";
|
|
14
|
+
import { useConditionBuildersRule } from "./lint/rules/use-condition-builders.js";
|
|
15
|
+
import { noHardcodedSecretsRule } from "./lint/rules/no-hardcoded-secrets.js";
|
|
16
|
+
import { useMatrixBuilderRule } from "./lint/rules/use-matrix-builder.js";
|
|
17
|
+
import { extractInlineStructsRule } from "./lint/rules/extract-inline-structs.js";
|
|
18
|
+
import { fileJobLimitRule } from "./lint/rules/file-job-limit.js";
|
|
19
|
+
import { noRawExpressionsRule } from "./lint/rules/no-raw-expressions.js";
|
|
20
|
+
import { missingRecommendedInputsRule } from "./lint/rules/missing-recommended-inputs.js";
|
|
21
|
+
import { deprecatedActionVersionRule } from "./lint/rules/deprecated-action-version.js";
|
|
22
|
+
import { jobTimeoutRule } from "./lint/rules/job-timeout.js";
|
|
23
|
+
import { suggestCacheRule } from "./lint/rules/suggest-cache.js";
|
|
24
|
+
import { validateConcurrencyRule } from "./lint/rules/validate-concurrency.js";
|
|
25
|
+
import { detectSecretsRule } from "./lint/rules/detect-secrets.js";
|
|
26
|
+
import { githubCompletions } from "./lsp/completions.js";
|
|
27
|
+
import { githubHover } from "./lsp/hover.js";
|
|
28
|
+
import { GitHubActionsParser } from "./import/parser.js";
|
|
29
|
+
import { GitHubActionsGenerator } from "./import/generator.js";
|
|
30
|
+
import { githubContextTools } from "./mcp/context-tools.js";
|
|
31
|
+
import { generateGithubPipeline } from "./components/generate-pipeline.js";
|
|
32
|
+
import { generateGithubOpPipeline } from "./components/generate-op-pipeline.js";
|
|
33
|
+
import { generateGithubOpWavesPipeline } from "./components/generate-op-waves-pipeline.js";
|
|
34
|
+
export const githubPlugin = {
|
|
35
|
+
name: "github",
|
|
36
|
+
auditCatalog: () => githubAuditCatalog,
|
|
37
|
+
// Generate mode (#891): synthesize a GitHub Actions workflow from the component graph.
|
|
38
|
+
generateComponentPipeline: (components, options) => generateGithubPipeline(components, options),
|
|
39
|
+
// Generate mode, Op counterpart (#927): synthesize one cron-triggered workflow per scheduled Op.
|
|
40
|
+
generateOpPipeline: (ops, options) => generateGithubOpPipeline(ops, options),
|
|
41
|
+
// Ordered waves of Op runs, each behind one gate (#3679).
|
|
42
|
+
generateOpWavesPipeline: (spec, options) => generateGithubOpWavesPipeline(spec, options),
|
|
43
|
+
serializer: githubSerializer,
|
|
44
|
+
// `needs`, reusable-workflow `uses:` and `workflow_run` as graph edges (#3910).
|
|
45
|
+
graphEdges: (entities) => githubGraphEdges(entities),
|
|
46
|
+
lintRules() {
|
|
47
|
+
return [
|
|
48
|
+
useTypedActionsRule,
|
|
49
|
+
useConditionBuildersRule,
|
|
50
|
+
noHardcodedSecretsRule,
|
|
51
|
+
useMatrixBuilderRule,
|
|
52
|
+
extractInlineStructsRule,
|
|
53
|
+
fileJobLimitRule,
|
|
54
|
+
noRawExpressionsRule,
|
|
55
|
+
missingRecommendedInputsRule,
|
|
56
|
+
deprecatedActionVersionRule,
|
|
57
|
+
jobTimeoutRule,
|
|
58
|
+
suggestCacheRule,
|
|
59
|
+
validateConcurrencyRule,
|
|
60
|
+
detectSecretsRule,
|
|
61
|
+
];
|
|
62
|
+
},
|
|
63
|
+
postSynthChecks() {
|
|
64
|
+
return postSynthCheckList;
|
|
65
|
+
},
|
|
66
|
+
intrinsics() {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
name: "Expression",
|
|
70
|
+
description: "Typed class wrapping a raw GitHub Actions expression, serializing to `${{ }}` (chant #1069 — was registered as \"expression\", which src/index.ts never exported)",
|
|
71
|
+
outputKey: "expression",
|
|
72
|
+
isTag: false,
|
|
73
|
+
},
|
|
74
|
+
// chant #1966 — src/expression.ts's context-accessor and condition
|
|
75
|
+
// helpers, each a pure function of its arguments building an
|
|
76
|
+
// `Expression` (or, for `steps`/`needs`, a plain object of one).
|
|
77
|
+
// `foldsEagerly: true` (not `foldsAsCall`) because their usual use is
|
|
78
|
+
// embedded directly in a template literal (`` `${matrix("os")}` ``),
|
|
79
|
+
// which needs the real, already-live `Expression` at fold time to
|
|
80
|
+
// stringify correctly — see IntrinsicDef.foldsEagerly's doc.
|
|
81
|
+
{ name: "secrets", description: "Access a secret by name.", isTag: false, foldsEagerly: true },
|
|
82
|
+
{ name: "matrix", description: "Access a matrix value by key.", isTag: false, foldsEagerly: true },
|
|
83
|
+
{ name: "steps", description: "Access a step output.", isTag: false, foldsEagerly: true },
|
|
84
|
+
{ name: "needs", description: "Access a job output from a needed job.", isTag: false, foldsEagerly: true },
|
|
85
|
+
{ name: "inputs", description: "Access a workflow input.", isTag: false, foldsEagerly: true },
|
|
86
|
+
{ name: "vars", description: "Access a configuration variable.", isTag: false, foldsEagerly: true },
|
|
87
|
+
{ name: "env", description: "Access an environment variable.", isTag: false, foldsEagerly: true },
|
|
88
|
+
{ name: "always", description: "Always run, regardless of status.", isTag: false, foldsEagerly: true },
|
|
89
|
+
{ name: "failure", description: "Run only if a previous step has failed.", isTag: false, foldsEagerly: true },
|
|
90
|
+
{ name: "success", description: "Run only if all previous steps succeeded.", isTag: false, foldsEagerly: true },
|
|
91
|
+
{ name: "cancelled", description: "Run only if the workflow was cancelled.", isTag: false, foldsEagerly: true },
|
|
92
|
+
{ name: "contains", description: "Check if a string contains a substring.", isTag: false, foldsEagerly: true },
|
|
93
|
+
{ name: "startsWith", description: "Check if a string starts with a prefix.", isTag: false, foldsEagerly: true },
|
|
94
|
+
{ name: "toJSON", description: "Convert a value to JSON.", isTag: false, foldsEagerly: true },
|
|
95
|
+
{ name: "fromJSON", description: "Parse a JSON string.", isTag: false, foldsEagerly: true },
|
|
96
|
+
{ name: "format", description: "Format a string with placeholders.", isTag: false, foldsEagerly: true },
|
|
97
|
+
{ name: "branch", description: "Check if the ref matches a branch name.", isTag: false, foldsEagerly: true },
|
|
98
|
+
{ name: "tag", description: "Check if the ref matches a tag prefix.", isTag: false, foldsEagerly: true },
|
|
99
|
+
];
|
|
100
|
+
},
|
|
101
|
+
initTemplates(template) {
|
|
102
|
+
if (template === "node-ci") {
|
|
103
|
+
return {
|
|
104
|
+
src: {
|
|
105
|
+
"pipeline.ts": `import { NodeCI } from "@intentius/chant-lexicon-github";
|
|
106
|
+
|
|
107
|
+
export const app = NodeCI({
|
|
108
|
+
nodeVersion: "22",
|
|
109
|
+
installCommand: "npm ci",
|
|
110
|
+
buildScript: "build",
|
|
111
|
+
testScript: "test",
|
|
112
|
+
});
|
|
113
|
+
`,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (template === "docker-build") {
|
|
118
|
+
return {
|
|
119
|
+
src: {
|
|
120
|
+
"pipeline.ts": `import { Job, Step, Workflow, PushTrigger, Checkout } from "@intentius/chant-lexicon-github";
|
|
121
|
+
|
|
122
|
+
export const push = new PushTrigger({ branches: ["main"] });
|
|
123
|
+
|
|
124
|
+
export const workflow = new Workflow({
|
|
125
|
+
name: "Docker Build",
|
|
126
|
+
on: { push: { branches: ["main"] } },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const build = new Job({
|
|
130
|
+
"runs-on": "ubuntu-latest",
|
|
131
|
+
steps: [
|
|
132
|
+
Checkout({}).step,
|
|
133
|
+
new Step({
|
|
134
|
+
name: "Build and push",
|
|
135
|
+
run: "docker build -t myapp .",
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
`,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Default template
|
|
144
|
+
return {
|
|
145
|
+
src: {
|
|
146
|
+
"pipeline.ts": `import { Workflow, Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-github";
|
|
147
|
+
|
|
148
|
+
export const workflow = new Workflow({
|
|
149
|
+
name: "CI",
|
|
150
|
+
on: {
|
|
151
|
+
push: { branches: ["main"] },
|
|
152
|
+
pull_request: { branches: ["main"] },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export const build = new Job({
|
|
157
|
+
"runs-on": "ubuntu-latest",
|
|
158
|
+
steps: [
|
|
159
|
+
Checkout({}).step,
|
|
160
|
+
SetupNode({ nodeVersion: "22", cache: "npm" }).step,
|
|
161
|
+
new Step({ name: "Install", run: "npm ci" }),
|
|
162
|
+
new Step({ name: "Build", run: "npm run build" }),
|
|
163
|
+
new Step({ name: "Test", run: "npm test" }),
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
`,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
detectTemplate(data) {
|
|
171
|
+
if (typeof data !== "object" || data === null)
|
|
172
|
+
return false;
|
|
173
|
+
const obj = data;
|
|
174
|
+
// GitHub Actions workflows have `on:` + `jobs:` keys
|
|
175
|
+
if (obj.on !== undefined && obj.jobs !== undefined)
|
|
176
|
+
return true;
|
|
177
|
+
// Check for job-like entries with runs-on
|
|
178
|
+
for (const value of Object.values(obj)) {
|
|
179
|
+
if (typeof value === "object" && value !== null) {
|
|
180
|
+
const entry = value;
|
|
181
|
+
if (entry["runs-on"] !== undefined || entry.steps !== undefined) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
},
|
|
188
|
+
completionProvider(ctx) {
|
|
189
|
+
return githubCompletions(ctx);
|
|
190
|
+
},
|
|
191
|
+
hoverProvider(ctx) {
|
|
192
|
+
return githubHover(ctx);
|
|
193
|
+
},
|
|
194
|
+
templateParser() {
|
|
195
|
+
return new GitHubActionsParser();
|
|
196
|
+
},
|
|
197
|
+
templateGenerator() {
|
|
198
|
+
return new GitHubActionsGenerator();
|
|
199
|
+
},
|
|
200
|
+
async generate(options) {
|
|
201
|
+
const { generate, writeGeneratedFiles } = await import("./codegen/generate.js");
|
|
202
|
+
const { dirname } = await import("path");
|
|
203
|
+
const { fileURLToPath } = await import("url");
|
|
204
|
+
const result = await generate({ verbose: options?.verbose ?? true });
|
|
205
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
206
|
+
writeGeneratedFiles(result, pkgDir);
|
|
207
|
+
console.error(`Generated ${result.resources} entities, ${result.properties} property types, ${result.enums} enums`);
|
|
208
|
+
if (result.warnings.length > 0) {
|
|
209
|
+
console.error(`${result.warnings.length} warnings`);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
async validate(options) {
|
|
213
|
+
const { validate } = await import("./validate.js");
|
|
214
|
+
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
215
|
+
const result = await validate();
|
|
216
|
+
printValidationResult(result);
|
|
217
|
+
},
|
|
218
|
+
async coverage(options) {
|
|
219
|
+
const { analyzeGitHubCoverage } = await import("./coverage.js");
|
|
220
|
+
await analyzeGitHubCoverage({
|
|
221
|
+
verbose: options?.verbose,
|
|
222
|
+
minOverall: options?.minOverall,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
async package(options) {
|
|
226
|
+
const { packageLexicon } = await import("./codegen/package.js");
|
|
227
|
+
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
228
|
+
const { join, dirname } = await import("path");
|
|
229
|
+
const { fileURLToPath } = await import("url");
|
|
230
|
+
const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
|
|
231
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
232
|
+
const distDir = join(pkgDir, "dist");
|
|
233
|
+
writeBundleSpec(spec, distDir);
|
|
234
|
+
console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
235
|
+
},
|
|
236
|
+
composites() {
|
|
237
|
+
return compositeCatalog;
|
|
238
|
+
},
|
|
239
|
+
mcpTools() {
|
|
240
|
+
return [
|
|
241
|
+
createDiffTool(githubSerializer, "Compare current build output against previous output for GitHub Actions", "github"),
|
|
242
|
+
...githubContextTools(),
|
|
243
|
+
];
|
|
244
|
+
},
|
|
245
|
+
mcpResources() {
|
|
246
|
+
return [
|
|
247
|
+
createCatalogResource(import.meta.url, "GitHub Actions Entity Catalog", "JSON list of all supported GitHub Actions entity types", "lexicon-github.json", "github"),
|
|
248
|
+
{
|
|
249
|
+
uri: "examples/basic-ci",
|
|
250
|
+
name: "Basic CI Example",
|
|
251
|
+
description: "A basic GitHub Actions CI workflow with build and test",
|
|
252
|
+
mimeType: "text/typescript",
|
|
253
|
+
async handler() {
|
|
254
|
+
return `import { Workflow, Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-github";
|
|
255
|
+
|
|
256
|
+
export const workflow = new Workflow({
|
|
257
|
+
name: "CI",
|
|
258
|
+
on: {
|
|
259
|
+
push: { branches: ["main"] },
|
|
260
|
+
pull_request: { branches: ["main"] },
|
|
261
|
+
},
|
|
262
|
+
permissions: { contents: "read" },
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export const build = new Job({
|
|
266
|
+
"runs-on": "ubuntu-latest",
|
|
267
|
+
steps: [
|
|
268
|
+
Checkout({}).step,
|
|
269
|
+
SetupNode({ nodeVersion: "22", cache: "npm" }).step,
|
|
270
|
+
new Step({ name: "Install", run: "npm ci" }),
|
|
271
|
+
new Step({ name: "Build", run: "npm run build" }),
|
|
272
|
+
new Step({ name: "Test", run: "npm test" }),
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
`;
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
},
|
|
280
|
+
async docs(options) {
|
|
281
|
+
const { generateDocs } = await import("./codegen/docs.js");
|
|
282
|
+
await generateDocs(options);
|
|
283
|
+
},
|
|
284
|
+
skills: createSkillsLoader(import.meta.url, [
|
|
285
|
+
{
|
|
286
|
+
file: "chant-github.md",
|
|
287
|
+
name: "chant-github",
|
|
288
|
+
description: "GitHub Actions workflow lifecycle — build, validate, deploy",
|
|
289
|
+
triggers: [
|
|
290
|
+
{ type: "file-pattern", value: "**/*.github.ts" },
|
|
291
|
+
{ type: "file-pattern", value: "**/.github/workflows/*.yml" },
|
|
292
|
+
{ type: "context", value: "github actions" },
|
|
293
|
+
{ type: "context", value: "workflow" },
|
|
294
|
+
],
|
|
295
|
+
parameters: [],
|
|
296
|
+
examples: [
|
|
297
|
+
{
|
|
298
|
+
title: "Basic CI workflow",
|
|
299
|
+
description: "Create a CI workflow with build and test",
|
|
300
|
+
input: "Create a CI workflow",
|
|
301
|
+
output: `new Workflow({ name: "CI", on: { push: { branches: ["main"] }, pull_request: { branches: ["main"] } } })`,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
file: "chant-github-patterns.md",
|
|
307
|
+
name: "chant-github-patterns",
|
|
308
|
+
description: "GitHub Actions workflow patterns — triggers, jobs, matrix, caching, artifacts",
|
|
309
|
+
triggers: [
|
|
310
|
+
{ type: "context", value: "github actions" },
|
|
311
|
+
{ type: "context", value: "workflow" },
|
|
312
|
+
{ type: "context", value: "matrix" },
|
|
313
|
+
{ type: "context", value: "cache" },
|
|
314
|
+
],
|
|
315
|
+
parameters: [],
|
|
316
|
+
examples: [
|
|
317
|
+
{
|
|
318
|
+
title: "Matrix strategy",
|
|
319
|
+
input: "Set up a Node.js matrix build",
|
|
320
|
+
output: `new Strategy({ matrix: { "node-version": ["18", "20", "22"] } })`,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
file: "chant-github-security.md",
|
|
326
|
+
name: "chant-github-security",
|
|
327
|
+
description: "GitHub Actions security — secret scanning, OIDC, permissions hardening, supply chain",
|
|
328
|
+
triggers: [
|
|
329
|
+
{ type: "context", value: "github security" },
|
|
330
|
+
{ type: "context", value: "workflow security" },
|
|
331
|
+
{ type: "context", value: "oidc" },
|
|
332
|
+
{ type: "context", value: "permissions" },
|
|
333
|
+
],
|
|
334
|
+
parameters: [],
|
|
335
|
+
examples: [
|
|
336
|
+
{
|
|
337
|
+
title: "Permissions hardening",
|
|
338
|
+
input: "Lock down workflow permissions",
|
|
339
|
+
output: `new Workflow({ permissions: { contents: "read" } })`,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
]),
|
|
344
|
+
};
|