@makerx/yoke 0.9.0 → 0.11.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/README.md +5 -5
- package/dist/cli.js +28 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,15 +23,15 @@ yoke sync --user # ~/.claude/skills
|
|
|
23
23
|
|
|
24
24
|
- `/pr` raises a PR with a short What/Why/(How) description.
|
|
25
25
|
- `/review [number]` reviews a PR with Claude and Codex, combines their findings and offers to post them. [Configure](src/features/review/README.md#settings).
|
|
26
|
-
- `/review-ci` adds a workflow that reviews each PR in CI and helps set its
|
|
26
|
+
- `/review-ci` adds a workflow that reviews each PR in CI and helps set its variable and secret. [Details](src/features/review/README.md#ci).
|
|
27
27
|
|
|
28
28
|
## CLI
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
yoke sync [--user]
|
|
32
|
-
yoke review [number]
|
|
33
|
-
yoke review-ci
|
|
34
|
-
yoke models
|
|
31
|
+
yoke sync [--user] [--force] # write yoke's skills
|
|
32
|
+
yoke review [number] # review a PR (used by /review)
|
|
33
|
+
yoke review-ci # add the CI review workflow (used by /review-ci)
|
|
34
|
+
yoke models # list each provider's models
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## Configuration
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Command } from "commander";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@makerx/yoke",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.11.0",
|
|
11
11
|
description: "Lean, user-invoked Claude Code skills for MakerX AI-assisted delivery, plus the CLI they call.",
|
|
12
12
|
license: "MIT",
|
|
13
13
|
author: "MakerX",
|
|
@@ -627,10 +627,10 @@ function buildOverride({ harness, model }, provider) {
|
|
|
627
627
|
// src/features/review/ci.ts
|
|
628
628
|
var reviewWorkflowRef = "MakerXStudio/yoke/.github/workflows/review.yml@main";
|
|
629
629
|
var callerWorkflowPath = join3(".github", "workflows", "review-pr.yml");
|
|
630
|
-
var
|
|
630
|
+
var baseUrlVariable = "REVIEW_CI_BASE_URL";
|
|
631
631
|
var apiKeySecret = "REVIEW_CI_API_KEY";
|
|
632
632
|
function workflowSecrets({ entra }) {
|
|
633
|
-
return entra ? [
|
|
633
|
+
return entra ? [] : [apiKeySecret];
|
|
634
634
|
}
|
|
635
635
|
function entraInputs({ azureClientId, azureTenantId, environment }, provider, errors) {
|
|
636
636
|
const clientId = azureClientId?.trim();
|
|
@@ -686,6 +686,7 @@ function yamlValue(value) {
|
|
|
686
686
|
return /^[\w./:@-]+$/.test(value) && !/^([-+.\d_eE]+|0[xo][\da-f]+|true|false|yes|no|on|off|y|n|null|~)$/i.test(value) ? value : JSON.stringify(value);
|
|
687
687
|
}
|
|
688
688
|
function renderCallerWorkflow(workflow) {
|
|
689
|
+
const secrets = workflowSecrets(workflow);
|
|
689
690
|
return [
|
|
690
691
|
"name: Review PR",
|
|
691
692
|
"",
|
|
@@ -703,19 +704,20 @@ function renderCallerWorkflow(workflow) {
|
|
|
703
704
|
` uses: ${reviewWorkflowRef}`,
|
|
704
705
|
" with:",
|
|
705
706
|
...workflow.inputs.map(([name, value]) => ` ${name}: ${yamlValue(value)}`),
|
|
706
|
-
|
|
707
|
-
...
|
|
707
|
+
` base-url: \${{ vars.${baseUrlVariable} }}`,
|
|
708
|
+
...secrets.length > 0 ? [" secrets:", ...secrets.map((secret2) => ` ${secret2}: \${{ secrets.${secret2} }}`)] : [],
|
|
708
709
|
""
|
|
709
710
|
].join("\n");
|
|
710
711
|
}
|
|
711
712
|
function shellQuote(value) {
|
|
712
713
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
713
714
|
}
|
|
714
|
-
function
|
|
715
|
+
function setupCommands(settings, workflow) {
|
|
715
716
|
const baseUrl = settings[`${workflow.provider}.baseUrl`];
|
|
716
|
-
return
|
|
717
|
-
|
|
718
|
-
|
|
717
|
+
return [
|
|
718
|
+
`gh variable set ${baseUrlVariable}${baseUrl ? ` --body ${shellQuote(baseUrl)}` : ""}`,
|
|
719
|
+
...workflowSecrets(workflow).map((secret2) => `gh secret set ${secret2}`)
|
|
720
|
+
];
|
|
719
721
|
}
|
|
720
722
|
var githubOidcIssuer = "https://token.actions.githubusercontent.com";
|
|
721
723
|
var entraTokenExchangeAudience = "api://AzureADTokenExchange";
|
|
@@ -780,12 +782,12 @@ function entraSetup({ clientId, environment }, repo) {
|
|
|
780
782
|
}
|
|
781
783
|
function reviewCiReport(outcome, settings, workflow, repo) {
|
|
782
784
|
const action = outcome === "wrote" ? "wrote" : "kept";
|
|
783
|
-
const
|
|
785
|
+
const reads = [`variable ${baseUrlVariable}`, ...workflowSecrets(workflow).map((secret2) => `secret ${secret2}`)];
|
|
784
786
|
return [
|
|
785
787
|
`${action} ${callerWorkflowPath}`,
|
|
786
788
|
"",
|
|
787
|
-
`The workflow reads ${
|
|
788
|
-
...
|
|
789
|
+
`The workflow reads ${reads.join(" and ")}. Set ${reads.length > 1 ? "them" : "it"} on the repo (or the org) with:`,
|
|
790
|
+
...setupCommands(settings, workflow).map((command) => ` ${command}`),
|
|
789
791
|
...workflow.entra ? entraSetup(workflow.entra, repo()) : [],
|
|
790
792
|
"",
|
|
791
793
|
"Commit and push the workflow; each PR opened from this repo then gets a review."
|
|
@@ -938,7 +940,7 @@ This change adds or edits a workflow that calls \`MakerXStudio/yoke/.github/work
|
|
|
938
940
|
|
|
939
941
|
Real defects in it are still findings, such as:
|
|
940
942
|
|
|
941
|
-
- Passing every secret with \`secrets: inherit\` instead of only \`REVIEW_CI_BASE_URL\`
|
|
943
|
+
- Passing every secret with \`secrets: inherit\` instead of only \`REVIEW_CI_API_KEY\` (plus \`REVIEW_CI_BASE_URL\` from callers that still pass the URL as a secret).
|
|
942
944
|
- An input the reusable workflow does not declare.
|
|
943
945
|
- No \`permissions:\` block limiting the token to \`contents: read\` and \`pull-requests: write\`, plus \`id-token: write\` with \`azure-client-id\`. The reusable workflow sets no permissions of its own, so the caller's block is the only limit on the token its agents run with.
|
|
944
946
|
`;
|
|
@@ -1179,8 +1181,8 @@ async function reportFailure(pr2, reason, ui, deps) {
|
|
|
1179
1181
|
|
|
1180
1182
|
// src/features/review/slots.ts
|
|
1181
1183
|
var slotNames = ["reviewer1", "reviewer2", "synthesis"];
|
|
1182
|
-
function notSet(key,
|
|
1183
|
-
return isCi(env) ?
|
|
1184
|
+
function notSet(key, ciName, placeholder, env) {
|
|
1185
|
+
return isCi(env) ? `${ciName} is not set` : `${key} is not set; ${howToSet(key, placeholder)}`;
|
|
1184
1186
|
}
|
|
1185
1187
|
function readProvider(settings, kind, env = process.env) {
|
|
1186
1188
|
const baseUrlKey = `${kind}.baseUrl`;
|
|
@@ -1191,8 +1193,8 @@ function readProvider(settings, kind, env = process.env) {
|
|
|
1191
1193
|
if (baseUrl && token) return { kind, baseUrl, token };
|
|
1192
1194
|
if (baseUrl && apiKey) return { kind, baseUrl, apiKey };
|
|
1193
1195
|
return [
|
|
1194
|
-
baseUrl ? null : notSet(baseUrlKey,
|
|
1195
|
-
apiKey || token ? null : notSet(apiKeyKey, apiKeySecret
|
|
1196
|
+
baseUrl ? null : notSet(baseUrlKey, `Variable ${baseUrlVariable}`, "<url>", env),
|
|
1197
|
+
apiKey || token ? null : notSet(apiKeyKey, `Secret ${apiKeySecret}`, "<key>", env)
|
|
1196
1198
|
].filter((error) => error !== null);
|
|
1197
1199
|
}
|
|
1198
1200
|
function readSpec(settings, slot, errors) {
|
|
@@ -2183,7 +2185,7 @@ If \`yoke\` is not found, run \`npm i -g @makerx/yoke\` and retry.
|
|
|
2183
2185
|
// src/features/review/ciSkill.ts
|
|
2184
2186
|
var reviewCiSkill = {
|
|
2185
2187
|
name: "review-ci",
|
|
2186
|
-
description: "Add yoke's adversarial PR review workflow to this repo and set the
|
|
2188
|
+
description: "Add yoke's adversarial PR review workflow to this repo and set the variable and secret it needs.",
|
|
2187
2189
|
disableModelInvocation: true,
|
|
2188
2190
|
userLevel: true,
|
|
2189
2191
|
body: `
|
|
@@ -2191,13 +2193,13 @@ Run \`yoke review-ci $ARGUMENTS\`. If it reports that a model is required, run \
|
|
|
2191
2193
|
|
|
2192
2194
|
With \`--provider foundry\`, if the Foundry resource has local (API key) auth disabled or the user prefers Entra, rerun with \`--azure-client-id\` and \`--azure-tenant-id\` of the Entra app the job signs in as, plus \`--environment\` if the job should run in a GitHub environment. Ask the user for these values; don't guess them.
|
|
2193
2195
|
|
|
2194
|
-
Then set the
|
|
2195
|
-
- \`REVIEW_CI_BASE_URL\` is not secret: confirm the URL with the user and run the printed command.
|
|
2196
|
+
Then set the variable and secret it printed. Run \`gh variable list\` and \`gh secret list\` first; if one is missing, ask whether it is already set at the org level before setting it on the repo.
|
|
2197
|
+
- \`REVIEW_CI_BASE_URL\` is a variable, not a secret: confirm the URL with the user and run the printed \`gh variable set\` command.
|
|
2196
2198
|
- \`REVIEW_CI_API_KEY\` must never enter this conversation: ask the user to run \`gh secret set REVIEW_CI_API_KEY\` in their own terminal, outside this session, and paste the key at its prompt. With Entra auth there is no API key to set.
|
|
2197
2199
|
|
|
2198
2200
|
With Entra auth, show the user the federated credential it printed (issuer, audience, subject) and the \`az ad app federated-credential create\` command, and ask whether to run it or whether they will add the credential in the Azure portal. The app also needs the Cognitive Services User role on the Foundry resource, and the environment, if given, must exist on the repo.
|
|
2199
2201
|
|
|
2200
|
-
Finish by running \`gh secret list\` and telling the user to commit and push the workflow file.
|
|
2202
|
+
Finish by running \`gh variable list\` and \`gh secret list\` and telling the user to commit and push the workflow file.
|
|
2201
2203
|
|
|
2202
2204
|
If \`yoke\` is not found, run \`npm i -g @makerx/yoke\` and retry.
|
|
2203
2205
|
`
|
|
@@ -2225,12 +2227,12 @@ function skillsRoot(user, cwd = process.cwd(), home = homedir3()) {
|
|
|
2225
2227
|
function isOwnedByUser(skillFile) {
|
|
2226
2228
|
return existsSync8(skillFile) && !readFileSync8(skillFile, "utf8").includes(ownerMarker);
|
|
2227
2229
|
}
|
|
2228
|
-
function syncSkills(destination, userDestination, skills = bundledSkills) {
|
|
2230
|
+
function syncSkills(destination, userDestination, force = false, skills = bundledSkills) {
|
|
2229
2231
|
const result = { written: [], skipped: [] };
|
|
2230
2232
|
for (const skill of skills) {
|
|
2231
2233
|
const target = join9(skill.userLevel ? userDestination : destination, skill.name);
|
|
2232
2234
|
const skillFile = join9(target, "SKILL.md");
|
|
2233
|
-
if (isOwnedByUser(skillFile)) {
|
|
2235
|
+
if (!force && isOwnedByUser(skillFile)) {
|
|
2234
2236
|
result.skipped.push(target);
|
|
2235
2237
|
continue;
|
|
2236
2238
|
}
|
|
@@ -2244,13 +2246,13 @@ function syncSkills(destination, userDestination, skills = bundledSkills) {
|
|
|
2244
2246
|
// src/cli.ts
|
|
2245
2247
|
var { version } = package_default;
|
|
2246
2248
|
var program = new Command("yoke").description("Lean Claude Code skills and the CLI they call.").version(version);
|
|
2247
|
-
program.command("sync").description("Write yoke's skills into this repo's .claude/skills, or ~/.claude/skills with --user").option("--user", "write to ~/.claude/skills instead, for every repo").action((options) => {
|
|
2249
|
+
program.command("sync").description("Write yoke's skills into this repo's .claude/skills, or ~/.claude/skills with --user").option("--user", "write to ~/.claude/skills instead, for every repo").option("--force", "overwrite same-named skills that yoke didn't write").action((options) => {
|
|
2248
2250
|
const user = options.user ?? false;
|
|
2249
2251
|
const cwd = process.cwd();
|
|
2250
2252
|
const display = (path) => path.startsWith(cwd) ? relative(cwd, path) : path;
|
|
2251
|
-
const { written, skipped } = syncSkills(skillsRoot(user), skillsRoot(true));
|
|
2253
|
+
const { written, skipped } = syncSkills(skillsRoot(user), skillsRoot(true), options.force ?? false);
|
|
2252
2254
|
for (const file of written) console.log(`wrote ${display(file)}`);
|
|
2253
|
-
for (const dir of skipped) console.log(`skipped ${display(dir)} (
|
|
2255
|
+
for (const dir of skipped) console.log(`skipped ${display(dir)} (not yoke's skill; rerun with --force to overwrite it)`);
|
|
2254
2256
|
});
|
|
2255
2257
|
var pr = program.command("pr").description("Pull request commands");
|
|
2256
2258
|
pr.command("raise").description("Validate a PR title and body, then create the PR with gh").requiredOption("--title <title>", "single-line PR title").requiredOption("--body <body>", "PR body with ## What, ## Why and optionally ## How").option("--base <branch>", "base branch (defaults to the repo default)").option("--no-draft", "create a ready-for-review PR instead of a draft").addHelpText("after", raiseGuide).action((options) => {
|