halfcycle 0.3.25 → 0.3.26
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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +63 -15
- package/dist/bin.js +57 -33
- package/dist/bin.js.map +1 -1
- package/dist/index.js +56 -32
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +6 -0
- package/dist/install.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -15142,14 +15142,25 @@ function emitNeverConfigured(missing, exitCode, reason) {
|
|
|
15142
15142
|
var HOOK_REMEDY = `Fix it by running "npx halfcycle" in this repository: it may open your browser to sign in first, then writes this engagement's credentials to ~/.halfcycle \u2014 outside the repository \u2014 and the hook loads them itself.`;
|
|
15143
15143
|
var PERMISSIONS_BLOCK = "\n permissions:\n contents: read\n id-token: write\n\n";
|
|
15144
15144
|
var BOTH_LINES_SENTENCE = "BOTH LINES. Declaring any permission replaces the defaults rather than adding to them, so a block naming only the identity token takes read access away from the checkout step and a private repository stops checking out before this check is reached.";
|
|
15145
|
-
var
|
|
15145
|
+
var CI_CANNOT_PROVE_PROJECT = "has no way to prove which project it belongs to";
|
|
15146
|
+
var CI_REMEDY_LEAD = "Add this to the workflow \u2014 at the top of the file, or on this job:\n" + PERMISSIONS_BLOCK + `${BOTH_LINES_SENTENCE} Then, once, on your own machine in this project, if you have not already: \`npx halfcycle ci bind <owner>/<repo>\`. That is the whole configuration \u2014 there is no secret to store.`;
|
|
15147
|
+
var CI_OLDER_SETUP = "The older setup, GUARD_SERVICE_TOKEN and GUARD_ENGAGEMENT_ID supplied as repository secrets, still works unchanged, and a job that has both needs neither line.";
|
|
15148
|
+
var CI_REMEDY = `This job cannot vouch for anything, because it ${CI_CANNOT_PROVE_PROJECT}. ${CI_REMEDY_LEAD} ${CI_OLDER_SETUP}`;
|
|
15149
|
+
var NEVER_CONFIGURED_HEADLINE = "GUARD NEVER CONFIGURED";
|
|
15150
|
+
var NOT_A_SERVICE_OUTAGE = "This is not a service outage.";
|
|
15146
15151
|
function neverConfiguredMessage(missing, remedy, label = "[Halfcycle]") {
|
|
15147
|
-
return `${label}
|
|
15152
|
+
return `${label} ${NEVER_CONFIGURED_HEADLINE} \u2014 this repository has no guard coverage.
|
|
15148
15153
|
No evaluation was attempted, because there is nothing to attempt one with: ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} absent.
|
|
15149
|
-
|
|
15154
|
+
${NOT_A_SERVICE_OUTAGE} It means the guard has never run here \u2014 not on this change, and not on any change before it.
|
|
15150
15155
|
${remedy}
|
|
15151
15156
|
`;
|
|
15152
15157
|
}
|
|
15158
|
+
function ciNeverConfiguredMessage() {
|
|
15159
|
+
return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE} \u2014 this job ${CI_CANNOT_PROVE_PROJECT}, so it checked nothing. ${CI_REMEDY_LEAD}
|
|
15160
|
+
${NOT_A_SERVICE_OUTAGE} Nothing was sent: this job holds no credential and was not permitted to ask GitHub for a signed identity token. It speaks for this job on this change only \u2014 other jobs and earlier changes it did not see.
|
|
15161
|
+
${CI_OLDER_SETUP}
|
|
15162
|
+
`;
|
|
15163
|
+
}
|
|
15153
15164
|
function notThisAccountReport(label) {
|
|
15154
15165
|
return `${label} This project belongs to another Halfcycle account. Halfcycle is Solo today \u2014 one person works on a project at a time \u2014 so no credential exists for this machine and none will be issued. Nothing here needs fixing; this copy simply is not checked. Ask the project's owner for view access, or \u2014 if this account is wrong \u2014 sign in as the one that owns the project and run "npx halfcycle" again.
|
|
15155
15166
|
`;
|
|
@@ -15867,7 +15878,7 @@ function parseRawSql(line) {
|
|
|
15867
15878
|
}
|
|
15868
15879
|
function parseQueryBuilder(rawLine) {
|
|
15869
15880
|
const line = stripDiffPrefix(rawLine);
|
|
15870
|
-
const m = line.match(/['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
|
|
15881
|
+
const m = line.match(/(?<!\bcreate(?:Hash|Hmac|Sign|Verify|Cipheriv|Decipheriv)\s*\([^)]*)['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
|
|
15871
15882
|
if (m) {
|
|
15872
15883
|
const kind = m[2].toLowerCase();
|
|
15873
15884
|
return { kind, table: m[1].toLowerCase(), whereColumns: [] };
|
|
@@ -16433,6 +16444,22 @@ function isCodeFile(filePath) {
|
|
|
16433
16444
|
}
|
|
16434
16445
|
return true;
|
|
16435
16446
|
}
|
|
16447
|
+
var INSTALLER_OUTPUT_DIR = ".halfcycle/";
|
|
16448
|
+
function isInstallerOutput(relPath) {
|
|
16449
|
+
const normalised = relPath.replace(/\\/g, "/");
|
|
16450
|
+
return normalised === INSTALLER_OUTPUT_DIR.slice(0, -1) || normalised.startsWith(INSTALLER_OUTPUT_DIR);
|
|
16451
|
+
}
|
|
16452
|
+
function partitionInstallerOutput(files) {
|
|
16453
|
+
const evaluated = [];
|
|
16454
|
+
const installerOutput = [];
|
|
16455
|
+
for (const file2 of files) {
|
|
16456
|
+
if (isInstallerOutput(file2))
|
|
16457
|
+
installerOutput.push(file2);
|
|
16458
|
+
else
|
|
16459
|
+
evaluated.push(file2);
|
|
16460
|
+
}
|
|
16461
|
+
return { evaluated, installerOutput };
|
|
16462
|
+
}
|
|
16436
16463
|
function buildChangeSet(files, context = {}) {
|
|
16437
16464
|
const excerptConfig = context.excerpts;
|
|
16438
16465
|
const excerptsEnabled = excerptConfig?.enabled === true;
|
|
@@ -16722,7 +16749,9 @@ async function emitRunRecord(opts) {
|
|
|
16722
16749
|
});
|
|
16723
16750
|
await emitTelemetry(run, {
|
|
16724
16751
|
controlTelemetryUrl: telemetryConfig.controlTelemetryUrl,
|
|
16725
|
-
|
|
16752
|
+
// No bearer at all on a refused credential: `emitTelemetry` skips the POST
|
|
16753
|
+
// when it has nothing to authenticate with, which is the true state here.
|
|
16754
|
+
controlTelemetryToken: opts.credentialRefused ? void 0 : opts.credential ?? telemetryConfig.controlTelemetryToken,
|
|
16726
16755
|
guardEvalLogDir: telemetryConfig.guardEvalLogDir
|
|
16727
16756
|
}, { warn: (msg) => process.stderr.write(msg + "\n") });
|
|
16728
16757
|
}
|
|
@@ -16750,11 +16779,13 @@ async function runCi() {
|
|
|
16750
16779
|
`);
|
|
16751
16780
|
return 1;
|
|
16752
16781
|
}
|
|
16753
|
-
const changedFiles = changedFilesResult.value;
|
|
16754
|
-
|
|
16782
|
+
const { evaluated: changedFiles, installerOutput } = partitionInstallerOutput(changedFilesResult.value);
|
|
16783
|
+
const setAside = installerOutput.length === 1 ? ` 1 more under ${INSTALLER_OUTPUT_DIR} is Halfcycle's own file and is not evaluated.` : installerOutput.length > 1 ? ` ${installerOutput.length} more under ${INSTALLER_OUTPUT_DIR} are Halfcycle's own files and are not evaluated.` : "";
|
|
16784
|
+
process.stdout.write(`[Halfcycle CI] Diff base ${mergeBase.slice(0, 12)} \u2014 ${describeDiffBase(diffBase)}. ${changedFiles.length} file(s) to evaluate.${setAside}
|
|
16755
16785
|
`);
|
|
16756
16786
|
if (changedFiles.length === 0) {
|
|
16757
|
-
process.stdout.write(`[Halfcycle CI]
|
|
16787
|
+
process.stdout.write(installerOutput.length > 0 ? `[Halfcycle CI] Nothing to evaluate: every changed file is under ${INSTALLER_OUTPUT_DIR}, which Halfcycle writes itself and does not check.
|
|
16788
|
+
` : `[Halfcycle CI] No changed files against the diff base \u2014 nothing to evaluate.
|
|
16758
16789
|
`);
|
|
16759
16790
|
return 0;
|
|
16760
16791
|
}
|
|
@@ -16792,7 +16823,7 @@ async function runCi() {
|
|
|
16792
16823
|
}
|
|
16793
16824
|
if (!configResult.config) {
|
|
16794
16825
|
await emitRunRecord({ runType: "ci", outcome: "unconfigured", changeSet });
|
|
16795
|
-
process.stderr.write(
|
|
16826
|
+
process.stderr.write(ciNeverConfiguredMessage());
|
|
16796
16827
|
return 1;
|
|
16797
16828
|
}
|
|
16798
16829
|
return await evaluateAndAct({ ...configResult.config, changeSet });
|
|
@@ -16811,7 +16842,8 @@ async function evaluateAndAct(args2) {
|
|
|
16811
16842
|
runType: "ci",
|
|
16812
16843
|
outcome: outcomeForClientFailure(clientResult.kind),
|
|
16813
16844
|
failureReason: failure.reason,
|
|
16814
|
-
changeSet
|
|
16845
|
+
changeSet,
|
|
16846
|
+
credentialRefused: failure.kind === "credential"
|
|
16815
16847
|
});
|
|
16816
16848
|
if (failure.kind === "credential") {
|
|
16817
16849
|
process.stderr.write(credentialRejectedMessage(failure.statusCode, failure.message, CI_REMEDY, "[Halfcycle CI]"));
|
|
@@ -16863,13 +16895,17 @@ async function evaluateAndAct(args2) {
|
|
|
16863
16895
|
}, {
|
|
16864
16896
|
warn: (msg) => process.stderr.write(msg + "\n")
|
|
16865
16897
|
});
|
|
16866
|
-
return actOnEnvelope(clientResult.envelope
|
|
16898
|
+
return actOnEnvelope(clientResult.envelope, {
|
|
16899
|
+
files: changeSet.files.length,
|
|
16900
|
+
signals: changeSetDescriptorCount(changeSet)
|
|
16901
|
+
});
|
|
16867
16902
|
}
|
|
16868
|
-
function actOnEnvelope(envelope) {
|
|
16903
|
+
function actOnEnvelope(envelope, scope) {
|
|
16869
16904
|
const notRun = notRunReport(envelope.notRun, "[Halfcycle CI]");
|
|
16870
16905
|
if (notRun)
|
|
16871
16906
|
process.stdout.write(notRun + "\n");
|
|
16872
16907
|
if (envelope.guardsFired.length === 0) {
|
|
16908
|
+
process.stdout.write(cleanRunSummary(envelope.explanation, scope) + "\n");
|
|
16873
16909
|
return 0;
|
|
16874
16910
|
}
|
|
16875
16911
|
if (envelope.blocking) {
|
|
@@ -16901,6 +16937,13 @@ function actOnEnvelope(envelope) {
|
|
|
16901
16937
|
return 0;
|
|
16902
16938
|
}
|
|
16903
16939
|
}
|
|
16940
|
+
function cleanRunSummary(verdict, scope) {
|
|
16941
|
+
const files = `${scope.files} file(s) evaluated`;
|
|
16942
|
+
if (scope.signals === 0) {
|
|
16943
|
+
return `[Halfcycle CI] ${verdict} ${files}, 0 signals found to check \u2014 nothing in this change is of a kind the guards look at (env references, SQL writes, schema constraints, wire-bound numeric columns, marked third-party endpoints).`;
|
|
16944
|
+
}
|
|
16945
|
+
return `[Halfcycle CI] ${verdict} ${files}, ${scope.signals} signal(s) checked.`;
|
|
16946
|
+
}
|
|
16904
16947
|
|
|
16905
16948
|
// dist/credential-store.js
|
|
16906
16949
|
import { chmodSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
|
|
@@ -17118,6 +17161,8 @@ async function runPostToolUse() {
|
|
|
17118
17161
|
const diffContent = isNotebook ? "" : getSingleFileDiff(absFilePath, repoRoot);
|
|
17119
17162
|
const { relative: pathRelative } = await import("node:path");
|
|
17120
17163
|
const relPath = pathRelative(repoRoot, absFilePath);
|
|
17164
|
+
if (isInstallerOutput(relPath))
|
|
17165
|
+
return;
|
|
17121
17166
|
const context = loadChangeSetContext(repoRoot);
|
|
17122
17167
|
const changeSet = buildChangeSet([{ path: relPath, diffContent }], {
|
|
17123
17168
|
...context,
|
|
@@ -17149,7 +17194,8 @@ async function runPostToolUse() {
|
|
|
17149
17194
|
outcome: outcomeForClientFailure(clientResult.kind),
|
|
17150
17195
|
failureReason: failure.reason,
|
|
17151
17196
|
changeSet,
|
|
17152
|
-
credential: evaluation.token
|
|
17197
|
+
credential: evaluation.token,
|
|
17198
|
+
credentialRefused: failure.kind === "credential"
|
|
17153
17199
|
});
|
|
17154
17200
|
if (failure.kind === "credential") {
|
|
17155
17201
|
emitCredentialRejected(
|
|
@@ -17394,7 +17440,8 @@ async function runSessionDiff(input, kind) {
|
|
|
17394
17440
|
emitLocalFault(`Could not get session diff: ${collected.message}`);
|
|
17395
17441
|
return;
|
|
17396
17442
|
}
|
|
17397
|
-
const {
|
|
17443
|
+
const { getDiff } = collected;
|
|
17444
|
+
const { evaluated: changedFiles } = partitionInstallerOutput(collected.files);
|
|
17398
17445
|
if (changedFiles.length === 0) {
|
|
17399
17446
|
return;
|
|
17400
17447
|
}
|
|
@@ -17437,7 +17484,8 @@ async function runSessionDiff(input, kind) {
|
|
|
17437
17484
|
failureReason: failure.reason,
|
|
17438
17485
|
changeSet,
|
|
17439
17486
|
diffBase,
|
|
17440
|
-
credential: evaluation.token
|
|
17487
|
+
credential: evaluation.token,
|
|
17488
|
+
credentialRefused: failure.kind === "credential"
|
|
17441
17489
|
});
|
|
17442
17490
|
if (failure.kind === "credential") {
|
|
17443
17491
|
emitCredentialRejected(
|
package/dist/bin.js
CHANGED
|
@@ -1761,10 +1761,21 @@ exit 0
|
|
|
1761
1761
|
}
|
|
1762
1762
|
function generateCiStanza() {
|
|
1763
1763
|
return `# Halfcycle guard CI job \u2014 generated by the Halfcycle installer.
|
|
1764
|
-
#
|
|
1765
|
-
# monorepo tooling: it runs the
|
|
1766
|
-
# .halfcycle/bin/, so it works in a Python
|
|
1767
|
-
# one \u2014 the only prerequisite is Node 20 to run
|
|
1764
|
+
# Below is a complete GitHub Actions workflow: a name, the events it runs on, and
|
|
1765
|
+
# one job. It assumes no package manager and no monorepo tooling: it runs the
|
|
1766
|
+
# self-contained guard binary vendored at .halfcycle/bin/, so it works in a Python
|
|
1767
|
+
# or Go repository as well as a Node one \u2014 the only prerequisite is Node 20 to run
|
|
1768
|
+
# the bundled binary.
|
|
1769
|
+
#
|
|
1770
|
+
# NO WORKFLOW YET? Copy everything from the \`name:\` line to the end of the job
|
|
1771
|
+
# into a new file, .github/workflows/halfcycle-guard.yml, without the leading
|
|
1772
|
+
# "# " on each line. That file is complete as it stands: commit it, and the check
|
|
1773
|
+
# runs on the next push and on every pull request.
|
|
1774
|
+
#
|
|
1775
|
+
# ALREADY HAVE A WORKFLOW? Copy only the job \u2014 from \`halfcycle-guard-ci:\` down to
|
|
1776
|
+
# its \`run:\` line \u2014 under the \`jobs:\` key of the workflow you have, keeping the
|
|
1777
|
+
# indentation. The job carries its own \`permissions\` block, so it needs nothing
|
|
1778
|
+
# from the rest of that file.
|
|
1768
1779
|
#
|
|
1769
1780
|
# THERE IS NOTHING TO STORE. No token in your repository's secrets, no project id
|
|
1770
1781
|
# to look up, and no address to set. The \`permissions\` block below is the whole
|
|
@@ -1798,31 +1809,39 @@ function generateCiStanza() {
|
|
|
1798
1809
|
#
|
|
1799
1810
|
# WHICH BRANCH MODEL THIS ASSUMES: none. It works on pull-request branches AND on
|
|
1800
1811
|
# commits pushed straight to the default branch, which is the shape most
|
|
1801
|
-
# Halfcycle engagements settle on. \`
|
|
1802
|
-
#
|
|
1812
|
+
# Halfcycle engagements settle on. The \`on:\` block is what runs it in both
|
|
1813
|
+
# places: every push, and every pull request. Narrow it if you want (for example
|
|
1814
|
+
# \`branches: [main]\` under \`push:\`), but keep \`pull_request\`, or a change is
|
|
1815
|
+
# first checked after it has merged. \`fetch-depth\` is what makes the check work
|
|
1816
|
+
# there: it needs the commit BEFORE the one it is evaluating, and the default
|
|
1803
1817
|
# shallow checkout does not have it. With \`fetch-depth: 0\` the check fails loudly
|
|
1804
1818
|
# if it cannot work out what to evaluate \u2014 it will not pass quietly having
|
|
1805
1819
|
# evaluated nothing.
|
|
1806
1820
|
#
|
|
1807
|
-
#
|
|
1808
|
-
#
|
|
1809
|
-
#
|
|
1810
|
-
#
|
|
1811
|
-
#
|
|
1812
|
-
#
|
|
1813
|
-
# -
|
|
1814
|
-
#
|
|
1815
|
-
#
|
|
1816
|
-
#
|
|
1817
|
-
#
|
|
1818
|
-
#
|
|
1819
|
-
#
|
|
1820
|
-
#
|
|
1821
|
-
#
|
|
1822
|
-
#
|
|
1823
|
-
#
|
|
1824
|
-
#
|
|
1825
|
-
#
|
|
1821
|
+
# name: Halfcycle guard
|
|
1822
|
+
# on:
|
|
1823
|
+
# push:
|
|
1824
|
+
# pull_request:
|
|
1825
|
+
# jobs:
|
|
1826
|
+
# halfcycle-guard-ci:
|
|
1827
|
+
# runs-on: ubuntu-latest
|
|
1828
|
+
# permissions:
|
|
1829
|
+
# contents: read
|
|
1830
|
+
# id-token: write
|
|
1831
|
+
# steps:
|
|
1832
|
+
# - uses: actions/checkout@v4
|
|
1833
|
+
# with:
|
|
1834
|
+
# # REQUIRED. 0 = full history. The check diffs against the commit before
|
|
1835
|
+
# # HEAD (or the fork point on a branch); the default depth of 1 has
|
|
1836
|
+
# # neither. Do not lower this.
|
|
1837
|
+
# fetch-depth: 0
|
|
1838
|
+
# - uses: actions/setup-node@v4
|
|
1839
|
+
# with:
|
|
1840
|
+
# node-version: '20'
|
|
1841
|
+
# - name: Halfcycle guard CI check
|
|
1842
|
+
# # No env block, on purpose: this step holds no secret. The permissions
|
|
1843
|
+
# # above are what authenticate it.
|
|
1844
|
+
# run: node ./.halfcycle/bin/bin.bundle.mjs ci
|
|
1826
1845
|
#
|
|
1827
1846
|
# The job prints the diff base it used and how many files it evaluated, on every
|
|
1828
1847
|
# run. If that line says 0 files on a commit that changed something, the base is
|
|
@@ -1832,8 +1851,8 @@ function generateCiStanza() {
|
|
|
1832
1851
|
#
|
|
1833
1852
|
# EDIT YOUR COPY, NOT THIS FILE. This one is regenerated by the installer and your
|
|
1834
1853
|
# changes to it would be replaced the next time you run \`npx halfcycle\`. It is
|
|
1835
|
-
# also inert where it sits: no CI system reads this path. Copy the
|
|
1836
|
-
#
|
|
1854
|
+
# also inert where it sits: no CI system reads this path. Copy the workflow above
|
|
1855
|
+
# \u2014 or just its job \u2014 into .github/workflows/ and change it there.
|
|
1837
1856
|
`;
|
|
1838
1857
|
}
|
|
1839
1858
|
var MCP_REGISTRATION_REL = ".mcp.json";
|
|
@@ -2005,24 +2024,29 @@ ${header}
|
|
|
2005
2024
|
return "failed";
|
|
2006
2025
|
}
|
|
2007
2026
|
}
|
|
2008
|
-
function
|
|
2027
|
+
function previousPinForEngagement(targetRepoRoot, engagementId) {
|
|
2009
2028
|
try {
|
|
2010
2029
|
const existing = readBundlePin(targetRepoRoot);
|
|
2011
2030
|
if (existing === null || existing.engagementId !== engagementId)
|
|
2012
2031
|
return void 0;
|
|
2013
|
-
return existing
|
|
2032
|
+
return existing;
|
|
2014
2033
|
} catch {
|
|
2015
2034
|
return void 0;
|
|
2016
2035
|
}
|
|
2017
2036
|
}
|
|
2037
|
+
function nonEmpty(value) {
|
|
2038
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
2039
|
+
}
|
|
2018
2040
|
function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, accountId, writtenPaths) {
|
|
2019
|
-
const
|
|
2041
|
+
const previous = previousPinForEngagement(targetRepoRoot, engagementId);
|
|
2042
|
+
const carried = nonEmpty(accountId) ?? nonEmpty(previous?.accountId);
|
|
2043
|
+
const installedAt = nonEmpty(previous?.installedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2020
2044
|
const pin = {
|
|
2021
2045
|
version,
|
|
2022
2046
|
engagementId,
|
|
2023
2047
|
engagementType,
|
|
2024
|
-
installedAt
|
|
2025
|
-
...carried !== void 0
|
|
2048
|
+
installedAt,
|
|
2049
|
+
...carried !== void 0 ? { accountId: carried } : {}
|
|
2026
2050
|
};
|
|
2027
2051
|
const pinPath = join5(targetRepoRoot, ".halfcycle", "bundle.json");
|
|
2028
2052
|
writeAllowlisted(pinPath, targetRepoRoot, JSON.stringify(pin, null, 2) + "\n", writtenPaths);
|
|
@@ -4693,7 +4717,7 @@ ${USAGE}`);
|
|
|
4693
4717
|
`);
|
|
4694
4718
|
process.stdout.write(`[halfcycle] Claude Code will also ask permission the first time Halfcycle needs to look up your next step. Choose allow \u2014 without it nothing can run.
|
|
4695
4719
|
`);
|
|
4696
|
-
process.stdout.write(`[halfcycle] To check changes in CI too: run \`npx halfcycle ci bind <owner>/<repo>\` in this folder once, then
|
|
4720
|
+
process.stdout.write(`[halfcycle] To check changes in CI too: run \`npx halfcycle ci bind <owner>/<repo>\` in this folder once, then copy the workflow in .halfcycle/ci-stanza.yml into .github/workflows/ (or just its job into a workflow you have) \u2014 there is no secret to store.
|
|
4697
4721
|
`);
|
|
4698
4722
|
}
|
|
4699
4723
|
if (credential) {
|