halfcycle 0.3.36 → 0.3.38
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/README.md +5 -1
- package/bin/bin.bundle.mjs +266 -88
- package/dist/bin.js +382 -221
- package/dist/bin.js.map +3 -3
- package/dist/build-record/obtain.d.ts +7 -3
- package/dist/build-record/obtain.d.ts.map +1 -1
- package/dist/build-record/render-v2.d.ts +4 -3
- package/dist/build-record/render-v2.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +7 -0
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/control-origin.d.ts +2 -2
- package/dist/create-engagement.d.ts +27 -1
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/device-signin.d.ts +93 -17
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +8 -4
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +211 -122
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +35 -9
- package/dist/install.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +8 -2
- package/dist/loopback-signin.d.ts.map +1 -1
- package/dist/open-phase.d.ts +12 -3
- package/dist/open-phase.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/mint-board-code.d.ts +0 -41
- package/dist/mint-board-code.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -205,6 +205,15 @@ function looksLikeSecret(text) {
|
|
|
205
205
|
return false;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ../core/dist/mcp-registration.js
|
|
209
|
+
var MCP_REGISTRATION_REL = ".mcp.json";
|
|
210
|
+
var MCP_SERVER_KEY = "halfcycle";
|
|
211
|
+
|
|
212
|
+
// ../core/dist/shell-quote.js
|
|
213
|
+
function quoteForShell(value) {
|
|
214
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
215
|
+
}
|
|
216
|
+
|
|
208
217
|
// ../events/dist/result.js
|
|
209
218
|
import { z as z2 } from "zod";
|
|
210
219
|
var firedGuardSchema = z2.object({
|
|
@@ -574,9 +583,14 @@ var closedBySchema = z4.discriminatedUnion("kind", [
|
|
|
574
583
|
z4.object({ kind: z4.literal("account-removed") }).strict(),
|
|
575
584
|
z4.object({ kind: z4.literal("not-recorded") }).strict()
|
|
576
585
|
]);
|
|
586
|
+
var STEP_NAME_MAX = 200;
|
|
587
|
+
var stepNameSchema = plainText(STEP_NAME_MAX).refine((v) => v.trim().length > 0, {
|
|
588
|
+
message: "must not be blank"
|
|
589
|
+
});
|
|
577
590
|
var unfinishedStepSchema = z4.object({
|
|
578
591
|
stepId: plainText(),
|
|
579
|
-
state: z4.enum(["parked", "failed"])
|
|
592
|
+
state: z4.enum(["parked", "failed"]),
|
|
593
|
+
name: stepNameSchema.optional()
|
|
580
594
|
}).strict();
|
|
581
595
|
var crossedSchema = z4.discriminatedUnion("kind", [
|
|
582
596
|
z4.object({ kind: z4.literal("none") }).strict(),
|
|
@@ -994,13 +1008,28 @@ var engagementCredentialResponseSchema = z7.object({
|
|
|
994
1008
|
sessionToken: z7.string(),
|
|
995
1009
|
mcpUrl: z7.string(),
|
|
996
1010
|
guardUrl: z7.string(),
|
|
997
|
-
controlTelemetryUrl: z7.string()
|
|
1011
|
+
controlTelemetryUrl: z7.string(),
|
|
1012
|
+
methodVersion: z7.string().optional(),
|
|
1013
|
+
consoleUrl: z7.string().optional()
|
|
998
1014
|
}).strict();
|
|
999
1015
|
var ciTokenExchangeResponseSchema = z7.object({
|
|
1000
1016
|
token: z7.string(),
|
|
1001
1017
|
expiresAt: utcIso8601,
|
|
1002
1018
|
engagementId: z7.string()
|
|
1003
1019
|
}).strict();
|
|
1020
|
+
var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
|
|
1021
|
+
var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
|
|
1022
|
+
var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
|
|
1023
|
+
var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
|
|
1024
|
+
var NO_CREDENTIAL_SENTENCE = 'Halfcycle is not connected: this machine has no working Halfcycle credential for this project. Run "npx halfcycle" in the project folder to sign in again.';
|
|
1025
|
+
var NOT_THIS_ACCOUNT_SENTENCE = `Halfcycle is not connected: this project belongs to another Halfcycle account. To switch, run "npx halfcycle" in this project and ${SWITCH_ACCOUNT_ANSWER}.`;
|
|
1026
|
+
var UPDATE_CLAUDE_CODE_SENTENCE = "Halfcycle is not connected: this Claude Code version does not name the server it connects to, so no credential was sent. Update Claude Code, then reopen.";
|
|
1027
|
+
var NO_NODE_SENTENCE = "Halfcycle is not connected: Node is not installed, or not on the path this session uses, so nothing here is checked. Install Node, then reopen.";
|
|
1028
|
+
var PLACEHOLDER_SENTENCES = Object.freeze({
|
|
1029
|
+
[NO_CREDENTIAL_PLACEHOLDER]: NO_CREDENTIAL_SENTENCE,
|
|
1030
|
+
[NOT_THIS_ACCOUNT_PLACEHOLDER]: NOT_THIS_ACCOUNT_SENTENCE,
|
|
1031
|
+
[UPDATE_CLAUDE_CODE_PLACEHOLDER]: UPDATE_CLAUDE_CODE_SENTENCE
|
|
1032
|
+
});
|
|
1004
1033
|
|
|
1005
1034
|
// ../events/dist/phase-record.js
|
|
1006
1035
|
import { z as z8 } from "zod";
|
|
@@ -1720,7 +1749,7 @@ var WRITE_ALLOWLIST = [
|
|
|
1720
1749
|
".halfcycle/bin",
|
|
1721
1750
|
"docs/build-records",
|
|
1722
1751
|
"test/fixtures/captured",
|
|
1723
|
-
|
|
1752
|
+
MCP_REGISTRATION_REL,
|
|
1724
1753
|
".gitattributes",
|
|
1725
1754
|
ZONE_B_DIR
|
|
1726
1755
|
];
|
|
@@ -2093,6 +2122,20 @@ exec node "$PROJECT_DIR/${VENDORED_BIN_REL}" "$@"
|
|
|
2093
2122
|
}
|
|
2094
2123
|
var GENERATED_SESSION_START_HEADER = "# Session-start marker \u2014 generated by";
|
|
2095
2124
|
var SESSION_ID_FROM_HOOK_INPUT_SH = `SESSION_ID="$(printf '%s' "$INPUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);process.stdout.write(typeof j.session_id==="string"?j.session_id:"")}catch{process.stdout.write("")}})' 2>/dev/null || true)"`;
|
|
2125
|
+
var REPO_HASH_FROM_ROOT_SH = `HASH="$(printf '%s' "$REPO_ROOT" | node -e 'const c=[];process.stdin.on("data",d=>c.push(d)).on("end",()=>{process.stdout.write(require("node:crypto").createHash("sha256").update(Buffer.concat(c)).digest("hex").slice(0,12))})' 2>/dev/null || true)"`;
|
|
2126
|
+
function noNodeReplyShell() {
|
|
2127
|
+
const reply = JSON.stringify({
|
|
2128
|
+
systemMessage: NO_NODE_SENTENCE,
|
|
2129
|
+
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: NO_NODE_SENTENCE }
|
|
2130
|
+
});
|
|
2131
|
+
if (reply.includes("'")) {
|
|
2132
|
+
throw new Error("[bundle install] the no-Node notice cannot be single-quoted; refusing to write the session-start script.");
|
|
2133
|
+
}
|
|
2134
|
+
return `if ! command -v node >/dev/null 2>&1; then
|
|
2135
|
+
printf '%s\\n' '${reply}'
|
|
2136
|
+
exit 0
|
|
2137
|
+
fi`;
|
|
2138
|
+
}
|
|
2096
2139
|
function generateSessionStartMarker() {
|
|
2097
2140
|
return `#!/usr/bin/env bash
|
|
2098
2141
|
${GENERATED_SESSION_START_HEADER} the Halfcycle installer.
|
|
@@ -2101,10 +2144,15 @@ ${GENERATED_SESSION_START_HEADER} the Halfcycle installer.
|
|
|
2101
2144
|
# it began instead of only against the latest commit. The session id comes in on
|
|
2102
2145
|
# stdin with the hook input; without one, the older session-less name is written
|
|
2103
2146
|
# and the stop-time check compares against the latest commit as it used to.
|
|
2147
|
+
#
|
|
2148
|
+
# Without Node nothing Halfcycle installs here can run, so this says so once and
|
|
2149
|
+
# writes no marker.
|
|
2150
|
+
${noNodeReplyShell()}
|
|
2104
2151
|
INPUT="$(cat 2>/dev/null || true)"
|
|
2105
2152
|
REPO_ROOT="\${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
|
|
2106
2153
|
if [ -z "$REPO_ROOT" ]; then exit 0; fi
|
|
2107
|
-
|
|
2154
|
+
${REPO_HASH_FROM_ROOT_SH}
|
|
2155
|
+
if [ -z "$HASH" ]; then exit 0; fi
|
|
2108
2156
|
${SESSION_ID_FROM_HOOK_INPUT_SH}
|
|
2109
2157
|
MARKER_DIR="\${TMPDIR:-/tmp}"
|
|
2110
2158
|
MARKER_DIR="\${MARKER_DIR%/}" # strip trailing slash (macOS TMPDIR ends in /)
|
|
@@ -2114,64 +2162,6 @@ else
|
|
|
2114
2162
|
MARKER_FILE="\${MARKER_DIR}/halfcycle-session-\${HASH}.ref"
|
|
2115
2163
|
fi
|
|
2116
2164
|
git -C "$REPO_ROOT" rev-parse HEAD > "$MARKER_FILE" 2>/dev/null || true
|
|
2117
|
-
|
|
2118
|
-
# ---------------------------------------------------------------------------
|
|
2119
|
-
# STANDING GUARD-COVERAGE STATEMENT (T-27; two arms since T-02b).
|
|
2120
|
-
#
|
|
2121
|
-
# A repository with no credential on this machine is UNGUARDED: the PostToolUse
|
|
2122
|
-
# hook will find nothing to evaluate with, and it says so once per edit, after
|
|
2123
|
-
# the edit. This says it once, up front, before anything is written \u2014 which is
|
|
2124
|
-
# the one thing a session-start hook can do that nothing else can.
|
|
2125
|
-
#
|
|
2126
|
-
# TWO REASONS TO HAVE NO CREDENTIAL, TWO DIFFERENT THINGS TO SAY (T-02b,
|
|
2127
|
-
# solo-project-ownership \u2014 outcome B). "You have not signed in here yet" is
|
|
2128
|
-
# fixed by re-running the installer; "this is not your project" is permanent,
|
|
2129
|
-
# and the same remedy would send the reader in a circle forever. The two are
|
|
2130
|
-
# told apart by HALFCYCLE_ENV_NOT_THIS_ACCOUNT, set by \`halfcycle_env_file\`
|
|
2131
|
-
# below from a machine-level marker beside the credential store.
|
|
2132
|
-
#
|
|
2133
|
-
# Everything below runs in a SUBSHELL: it sources a credential store, and a
|
|
2134
|
-
# session-start hook must not leak an engagement's variables into whatever the
|
|
2135
|
-
# editor runs next. Any failure inside it is swallowed; this script's exit
|
|
2136
|
-
# status is 0 either way, and the marker above has already been written.
|
|
2137
|
-
# ---------------------------------------------------------------------------
|
|
2138
|
-
${engagementResolutionShell()}
|
|
2139
|
-
|
|
2140
|
-
(
|
|
2141
|
-
if halfcycle_env_file "$REPO_ROOT"; then
|
|
2142
|
-
set -a
|
|
2143
|
-
. "$HALFCYCLE_ENV_FILE"
|
|
2144
|
-
set +a
|
|
2145
|
-
fi
|
|
2146
|
-
if [ "$HALFCYCLE_ENV_NOT_THIS_ACCOUNT" = "1" ]; then
|
|
2147
|
-
# OUTCOME B (T-02b, solo-project-ownership). This is not the missing-keys
|
|
2148
|
-
# case below wearing different words: there is nothing to fix, and the loop
|
|
2149
|
-
# that follows is skipped entirely rather than run and then overridden \u2014 a
|
|
2150
|
-
# repository whose credential is absent for THIS reason must never be told
|
|
2151
|
-
# to re-run the installer, which can never obtain one for it.
|
|
2152
|
-
echo "[halfcycle] This project belongs to another Halfcycle account, so nothing here is checked."
|
|
2153
|
-
echo "[halfcycle] Your work carries on exactly as it would without Halfcycle \u2014 no edit is"
|
|
2154
|
-
echo "[halfcycle] evaluated, and nothing you do is reported as having passed."
|
|
2155
|
-
echo "[halfcycle] Halfcycle is Solo today \u2014 one developer per project \u2014 with Team plans coming."
|
|
2156
|
-
echo "[halfcycle] Ask the project's owner for view access."
|
|
2157
|
-
echo "[halfcycle] If this account is wrong, sign in as the one that owns the project and run"
|
|
2158
|
-
echo "[halfcycle] \\"npx halfcycle\\" again."
|
|
2159
|
-
else
|
|
2160
|
-
hc_missing=""
|
|
2161
|
-
for hc_key in ${GUARD_COVERAGE_REQUIRED_KEYS.join(" ")}; do
|
|
2162
|
-
eval "hc_value=\\\${$hc_key:-}"
|
|
2163
|
-
if [ -z "$hc_value" ]; then hc_missing="$hc_missing $hc_key"; fi
|
|
2164
|
-
done
|
|
2165
|
-
if [ -n "$hc_missing" ]; then
|
|
2166
|
-
echo "[halfcycle] NO GUARD COVERAGE IN THIS REPOSITORY. Nothing will evaluate the edits made in"
|
|
2167
|
-
echo "[halfcycle] this session: this machine has no Halfcycle credential for it, so the guard hook"
|
|
2168
|
-
echo "[halfcycle] has nothing to call with (missing:$hc_missing)."
|
|
2169
|
-
echo "[halfcycle] Run \\"npx halfcycle\\" in this repository to fix it \u2014 it signs you in through your"
|
|
2170
|
-
echo "[halfcycle] browser if needed, writes the credential to \\$HOME/.halfcycle outside this tree,"
|
|
2171
|
-
echo "[halfcycle] and needs nothing configured first."
|
|
2172
|
-
fi
|
|
2173
|
-
fi
|
|
2174
|
-
) 2>/dev/null || true
|
|
2175
2165
|
exit 0
|
|
2176
2166
|
`;
|
|
2177
2167
|
}
|
|
@@ -2187,7 +2177,8 @@ INPUT="$(cat 2>/dev/null || true)"
|
|
|
2187
2177
|
|
|
2188
2178
|
REPO_ROOT="\${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}"
|
|
2189
2179
|
if [ -z "$REPO_ROOT" ]; then exit 0; fi
|
|
2190
|
-
|
|
2180
|
+
${REPO_HASH_FROM_ROOT_SH}
|
|
2181
|
+
if [ -z "$HASH" ]; then exit 0; fi
|
|
2191
2182
|
|
|
2192
2183
|
# The session id arrives on stdin as part of the hook payload. Empty when absent.
|
|
2193
2184
|
${SESSION_ID_FROM_HOOK_INPUT_SH}
|
|
@@ -2307,8 +2298,6 @@ function removeRetiredGeneratedFile(targetRepo, rel, headerLineIndex, knownHeade
|
|
|
2307
2298
|
}
|
|
2308
2299
|
return "removed";
|
|
2309
2300
|
}
|
|
2310
|
-
var MCP_REGISTRATION_REL = ".mcp.json";
|
|
2311
|
-
var MCP_SERVER_KEY = "halfcycle";
|
|
2312
2301
|
var MCP_HEADERS_HELPER_COMMAND = `node -e "${[
|
|
2313
2302
|
`const p=require('path'),f=require('fs'),c=require('child_process');`,
|
|
2314
2303
|
`let d=process.cwd();`,
|
|
@@ -2451,9 +2440,10 @@ function previousPinForEngagement(targetRepoRoot, engagementId) {
|
|
|
2451
2440
|
function nonEmpty(value) {
|
|
2452
2441
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
2453
2442
|
}
|
|
2454
|
-
function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, accountId, writtenPaths) {
|
|
2443
|
+
function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, accountId, methodVersion, writtenPaths) {
|
|
2455
2444
|
const previous = previousPinForEngagement(targetRepoRoot, engagementId);
|
|
2456
2445
|
const carried = nonEmpty(accountId) ?? nonEmpty(previous?.accountId);
|
|
2446
|
+
const carriedMethodVersion = nonEmpty(methodVersion) ?? nonEmpty(previous?.methodVersion);
|
|
2457
2447
|
const installedAt = nonEmpty(previous?.installedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2458
2448
|
const projectPage = projectPageUrl(engagementId);
|
|
2459
2449
|
const pin = {
|
|
@@ -2462,6 +2452,7 @@ function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, a
|
|
|
2462
2452
|
engagementType,
|
|
2463
2453
|
installedAt,
|
|
2464
2454
|
...carried !== void 0 ? { accountId: carried } : {},
|
|
2455
|
+
...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
|
|
2465
2456
|
...projectPage !== void 0 ? { projectPage } : {}
|
|
2466
2457
|
};
|
|
2467
2458
|
const pinPath = join5(targetRepoRoot, BUNDLE_PIN_REL);
|
|
@@ -2499,7 +2490,7 @@ function checkDrift(targetRepoRoot) {
|
|
|
2499
2490
|
const pin = readBundlePin(targetRepoRoot);
|
|
2500
2491
|
const installed = pin?.version ?? null;
|
|
2501
2492
|
const current = manifest.version;
|
|
2502
|
-
return { drifted: installed !== current, installed, current };
|
|
2493
|
+
return { drifted: installed !== current, installed, current, methodVersion: pin?.methodVersion ?? null };
|
|
2503
2494
|
}
|
|
2504
2495
|
function writeProjectIdentity(targetRepo) {
|
|
2505
2496
|
const path = join5(targetRepo, PROJECT_IDENTITY_REL);
|
|
@@ -2730,7 +2721,7 @@ async function install(options) {
|
|
|
2730
2721
|
case "absent":
|
|
2731
2722
|
break;
|
|
2732
2723
|
}
|
|
2733
|
-
writeBundlePin(targetRepo, manifest.version, engagementId, engagementType, accountId, report.writtenPaths);
|
|
2724
|
+
writeBundlePin(targetRepo, manifest.version, engagementId, engagementType, accountId, credential?.methodVersion, report.writtenPaths);
|
|
2734
2725
|
noteFile(draft, targetRepo, BUNDLE_PIN_REL, "written");
|
|
2735
2726
|
noteFile(draft, targetRepo, CREW_ROSTER_REL, writeCrewRoster(targetRepo, report));
|
|
2736
2727
|
const scanResult = runBootstrapScan(targetRepo);
|
|
@@ -2820,7 +2811,7 @@ function planeRefusal(detail) {
|
|
|
2820
2811
|
reason: typeof candidate.reason === "string" ? candidate.reason : void 0
|
|
2821
2812
|
};
|
|
2822
2813
|
}
|
|
2823
|
-
var CONTROL_ORIGIN_HINT = `This CLI talks to ${DEFAULT_CONTROL_ORIGIN}; it must be the CONTROL origin \u2014 the one that serves /engagements
|
|
2814
|
+
var CONTROL_ORIGIN_HINT = `This CLI talks to ${DEFAULT_CONTROL_ORIGIN}; it must be the CONTROL origin \u2014 the one that serves /engagements. The MCP server runs at a different address, and the platform supplies that one itself; you configure neither.`;
|
|
2824
2815
|
async function createEngagement(baseUrl, name, credential, derivedName) {
|
|
2825
2816
|
return requestEngagementValues(
|
|
2826
2817
|
`${baseUrl.replace(/\/+$/, "")}/engagements`,
|
|
@@ -2893,43 +2884,16 @@ async function requestEngagementValues(url, requestBody, credential, shape) {
|
|
|
2893
2884
|
}).join(", ");
|
|
2894
2885
|
throw new Error(`[bundle install] ${shape.action} response from ${url} is not the credential response this CLI is built to read (${fields}). ${shape.nothingHappened}. Either that service is a different version from this installer, or ${url} is not a Halfcycle service.`);
|
|
2895
2886
|
}
|
|
2887
|
+
const consoleUrl = typeof body.consoleUrl === "string" && body.consoleUrl.trim() !== "" ? body.consoleUrl.trim() : void 0;
|
|
2888
|
+
const methodVersion = typeof body.methodVersion === "string" && body.methodVersion.trim() !== "" ? body.methodVersion.trim() : void 0;
|
|
2896
2889
|
return {
|
|
2897
2890
|
engagementId: body.engagementId,
|
|
2898
2891
|
sessionToken: body.sessionToken,
|
|
2899
2892
|
mcpUrl: body.mcpUrl.trim().replace(/\/+$/, ""),
|
|
2900
2893
|
guardUrl: body.guardUrl.trim().replace(/\/+$/, ""),
|
|
2901
|
-
...controlTelemetryUrl !== void 0 ? { controlTelemetryUrl } : {}
|
|
2902
|
-
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
// dist/mint-board-code.js
|
|
2906
|
-
async function mintBoardEnterCode(baseUrl, sessionToken) {
|
|
2907
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/board/enter-codes`;
|
|
2908
|
-
let res;
|
|
2909
|
-
try {
|
|
2910
|
-
res = await fetch(url, {
|
|
2911
|
-
method: "POST",
|
|
2912
|
-
headers: {
|
|
2913
|
-
"content-type": "application/json",
|
|
2914
|
-
authorization: `Bearer ${sessionToken}`
|
|
2915
|
-
},
|
|
2916
|
-
body: "{}"
|
|
2917
|
-
});
|
|
2918
|
-
} catch (err) {
|
|
2919
|
-
throw new Error(`[bundle install] Could not reach the Halfcycle board mint endpoint at ${url}: ${err instanceof Error ? err.message : String(err)}.`);
|
|
2920
|
-
}
|
|
2921
|
-
if (!res.ok) {
|
|
2922
|
-
throw new Error(`[bundle install] Board code mint failed: ${url} returned ${res.status}.`);
|
|
2923
|
-
}
|
|
2924
|
-
const body = await res.json().catch(() => null);
|
|
2925
|
-
if (!body || typeof body.enterCode !== "string" || typeof body.engagementId !== "string" || typeof body.boardUrl !== "string" || typeof body.expiresAt !== "string") {
|
|
2926
|
-
throw new Error(`[bundle install] Board code mint response from ${url} did not carry {enterCode, engagementId, boardUrl, expiresAt}.`);
|
|
2927
|
-
}
|
|
2928
|
-
return {
|
|
2929
|
-
enterCode: body.enterCode,
|
|
2930
|
-
engagementId: body.engagementId,
|
|
2931
|
-
boardUrl: body.boardUrl,
|
|
2932
|
-
expiresAt: body.expiresAt
|
|
2894
|
+
...controlTelemetryUrl !== void 0 ? { controlTelemetryUrl } : {},
|
|
2895
|
+
...consoleUrl !== void 0 ? { consoleUrl } : {},
|
|
2896
|
+
...methodVersion !== void 0 ? { methodVersion } : {}
|
|
2933
2897
|
};
|
|
2934
2898
|
}
|
|
2935
2899
|
|
|
@@ -3384,20 +3348,127 @@ function buildEngagementRecord(input) {
|
|
|
3384
3348
|
|
|
3385
3349
|
// dist/build-record/render-v2.js
|
|
3386
3350
|
var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
|
|
3387
|
-
var
|
|
3351
|
+
var SPACE = new RegExp("\\p{Zs}", "u");
|
|
3352
|
+
var WORD = /[\p{L}\p{N}]/u;
|
|
3353
|
+
var ASCII_PUNCTUATION = /[!-/:-@[-`{-~]/;
|
|
3354
|
+
var CHARACTER_REFERENCE = /^#?[A-Za-z0-9]+;/;
|
|
3355
|
+
var PAIRED = ["*", "_", "~", "$"];
|
|
3356
|
+
function codeSpanMask(cps) {
|
|
3357
|
+
const mask = cps.map(() => false);
|
|
3358
|
+
const runs = [];
|
|
3359
|
+
for (let i = 0; i < cps.length; ) {
|
|
3360
|
+
if (cps[i] !== "`") {
|
|
3361
|
+
i++;
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
let j = i;
|
|
3365
|
+
while (j < cps.length && cps[j] === "`")
|
|
3366
|
+
j++;
|
|
3367
|
+
runs.push({ start: i, length: j - i });
|
|
3368
|
+
i = j;
|
|
3369
|
+
}
|
|
3370
|
+
for (let k = 0; k < runs.length; ) {
|
|
3371
|
+
const open = runs[k];
|
|
3372
|
+
let m = k + 1;
|
|
3373
|
+
while (m < runs.length && runs[m].length !== open.length)
|
|
3374
|
+
m++;
|
|
3375
|
+
if (m === runs.length) {
|
|
3376
|
+
k++;
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
const close = runs[m];
|
|
3380
|
+
for (let i = open.start; i < close.start + close.length; i++)
|
|
3381
|
+
mask[i] = true;
|
|
3382
|
+
k = m + 1;
|
|
3383
|
+
}
|
|
3384
|
+
return mask;
|
|
3385
|
+
}
|
|
3386
|
+
function escapeMask(cps, inSpan) {
|
|
3387
|
+
const n = cps.length;
|
|
3388
|
+
const escape = cps.map(() => false);
|
|
3389
|
+
const isSpace = (i) => i >= 0 && i < n && SPACE.test(cps[i]) && !HIDDEN.test(cps[i]);
|
|
3390
|
+
const isWord = (i) => i >= 0 && i < n && WORD.test(cps[i]);
|
|
3391
|
+
const rest = (i) => cps.slice(i).join("");
|
|
3392
|
+
for (let i = 0; i < n; i++) {
|
|
3393
|
+
if (inSpan[i])
|
|
3394
|
+
continue;
|
|
3395
|
+
const ch = cps[i];
|
|
3396
|
+
const next = i + 1 < n ? cps[i + 1] : null;
|
|
3397
|
+
switch (ch) {
|
|
3398
|
+
case "\\":
|
|
3399
|
+
escape[i] = next === null || ASCII_PUNCTUATION.test(next);
|
|
3400
|
+
break;
|
|
3401
|
+
case "<":
|
|
3402
|
+
escape[i] = next !== null && !isSpace(i + 1) && rest(i + 1).includes(">");
|
|
3403
|
+
break;
|
|
3404
|
+
case "&":
|
|
3405
|
+
escape[i] = CHARACTER_REFERENCE.test(rest(i + 1));
|
|
3406
|
+
break;
|
|
3407
|
+
case "]":
|
|
3408
|
+
escape[i] = next === "(";
|
|
3409
|
+
break;
|
|
3410
|
+
case "[":
|
|
3411
|
+
case "{":
|
|
3412
|
+
escape[i] = (next === "+" || next === "-") && (rest(i + 2).includes(`${next}]`) || rest(i + 2).includes(`${next}}`));
|
|
3413
|
+
break;
|
|
3414
|
+
default:
|
|
3415
|
+
break;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
for (let i = 0; i < n; ) {
|
|
3419
|
+
if (inSpan[i] || cps[i] !== "#") {
|
|
3420
|
+
i++;
|
|
3421
|
+
continue;
|
|
3422
|
+
}
|
|
3423
|
+
let j = i;
|
|
3424
|
+
while (j < n && cps[j] === "#" && !inSpan[j])
|
|
3425
|
+
j++;
|
|
3426
|
+
let k = j;
|
|
3427
|
+
while (k < n && isSpace(k))
|
|
3428
|
+
k++;
|
|
3429
|
+
if (k === n && (i === 0 || isSpace(i - 1)))
|
|
3430
|
+
for (let m = i; m < j; m++)
|
|
3431
|
+
escape[m] = true;
|
|
3432
|
+
i = j;
|
|
3433
|
+
}
|
|
3434
|
+
for (const c of PAIRED) {
|
|
3435
|
+
const live = [];
|
|
3436
|
+
for (let i = 0; i < n; ) {
|
|
3437
|
+
if (inSpan[i] || cps[i] !== c) {
|
|
3438
|
+
i++;
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
let j = i;
|
|
3442
|
+
while (j < n && cps[j] === c && !inSpan[j])
|
|
3443
|
+
j++;
|
|
3444
|
+
const inert = c !== "$" && (isSpace(i - 1) && isSpace(j) || c === "_" && isWord(i - 1) && isWord(j));
|
|
3445
|
+
if (!inert)
|
|
3446
|
+
live.push({ start: i, end: j });
|
|
3447
|
+
i = j;
|
|
3448
|
+
}
|
|
3449
|
+
if (live.length < 2)
|
|
3450
|
+
continue;
|
|
3451
|
+
for (const run of live)
|
|
3452
|
+
for (let m = run.start; m < run.end; m++)
|
|
3453
|
+
escape[m] = true;
|
|
3454
|
+
}
|
|
3455
|
+
return escape;
|
|
3456
|
+
}
|
|
3388
3457
|
function printRecordText(text) {
|
|
3389
3458
|
if (text === "")
|
|
3390
3459
|
return "(blank)";
|
|
3460
|
+
const cps = [...text];
|
|
3461
|
+
const escape = escapeMask(cps, codeSpanMask(cps));
|
|
3391
3462
|
let out = "";
|
|
3392
|
-
|
|
3463
|
+
cps.forEach((ch, i) => {
|
|
3393
3464
|
if (HIDDEN.test(ch)) {
|
|
3394
3465
|
out += `U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3395
|
-
} else if (
|
|
3466
|
+
} else if (escape[i]) {
|
|
3396
3467
|
out += `\\${ch}`;
|
|
3397
3468
|
} else {
|
|
3398
3469
|
out += ch;
|
|
3399
3470
|
}
|
|
3400
|
-
}
|
|
3471
|
+
});
|
|
3401
3472
|
return out;
|
|
3402
3473
|
}
|
|
3403
3474
|
function makePrinter() {
|
|
@@ -3487,12 +3558,18 @@ function headline(observed, attested, checks) {
|
|
|
3487
3558
|
if (problemsReported) {
|
|
3488
3559
|
return "**Closed with problems reported.** Halfcycle saw nothing outstanding, and the person closing the phase reported problems found in its acceptance walk.";
|
|
3489
3560
|
}
|
|
3490
|
-
return checks === "not-observed" ? "**Closed with nothing outstanding, but no
|
|
3561
|
+
return checks === "not-observed" ? "**Closed with nothing outstanding, but no check on record.** Halfcycle saw nothing outstanding, and the person closing the phase stated its finish conditions were met, but Halfcycle has no record of any check running during this phase, so this is not a clean result." : "**Closed cleanly.** Halfcycle saw nothing outstanding, and the person closing the phase stated its finish conditions were met.";
|
|
3491
3562
|
case "unfinished-steps":
|
|
3492
3563
|
case "clean-over-findings":
|
|
3493
|
-
|
|
3564
|
+
if (!met) {
|
|
3565
|
+
return "**Closed over outstanding work.** Halfcycle saw work still outstanding, and the person closing the phase chose to override it.";
|
|
3566
|
+
}
|
|
3567
|
+
return problemsReported ? "**Closed over outstanding work.** Halfcycle saw work still outstanding, and the person closing the phase closed it as it stood, reporting problems found in its acceptance walk." : "**Closed over outstanding work.** Halfcycle saw work still outstanding, and the person closing the phase stated its finish conditions were met anyway.";
|
|
3494
3568
|
case "not-recorded":
|
|
3495
|
-
|
|
3569
|
+
if (!met) {
|
|
3570
|
+
return "**Outstanding work not recorded.** Halfcycle has no record of what was outstanding at this close. The person closing the phase chose to override.";
|
|
3571
|
+
}
|
|
3572
|
+
return problemsReported ? "**Outstanding work not recorded.** Halfcycle has no record of what was outstanding at this close. The person closing the phase reported problems found in its acceptance walk." : "**Outstanding work not recorded.** Halfcycle has no record of what was outstanding at this close. The person closing the phase stated its finish conditions were met.";
|
|
3496
3573
|
}
|
|
3497
3574
|
}
|
|
3498
3575
|
function closedAtText(closedAt, p) {
|
|
@@ -3511,30 +3588,38 @@ function signedInAs(closedBy, p) {
|
|
|
3511
3588
|
return "not recorded for this close";
|
|
3512
3589
|
}
|
|
3513
3590
|
}
|
|
3591
|
+
var OUTSTANDING_LABEL = "- Outstanding work in Halfcycle's own records of this phase (the method's steps, and problems recorded against the phase): ";
|
|
3514
3592
|
function outstanding(crossed, p) {
|
|
3515
3593
|
switch (crossed.kind) {
|
|
3516
3594
|
case "none":
|
|
3517
|
-
return [
|
|
3595
|
+
return [`${OUTSTANDING_LABEL}none.`];
|
|
3518
3596
|
case "not-recorded":
|
|
3519
|
-
return [
|
|
3597
|
+
return [`${OUTSTANDING_LABEL}not recorded for this close.`];
|
|
3520
3598
|
case "clean-over-findings":
|
|
3521
|
-
return [
|
|
3522
|
-
`- Outstanding work Halfcycle could see: ${counted(crossed.findings, "problem", "problems")} recorded against this phase.`
|
|
3523
|
-
];
|
|
3599
|
+
return [`${OUTSTANDING_LABEL}${counted(crossed.findings, "problem", "problems")} recorded against this phase.`];
|
|
3524
3600
|
case "unfinished-steps":
|
|
3525
3601
|
return [
|
|
3526
|
-
|
|
3527
|
-
...crossed.steps.map((step) => ` - Step: ${p.text(step.stepId)} \u2014 ${step.state === "failed" ? "failed" : "set aside unfinished"}`)
|
|
3602
|
+
`${OUTSTANDING_LABEL}${counted(crossed.steps.length, "step", "steps")} of the method left unfinished. ${shownBy(crossed.steps)}`,
|
|
3603
|
+
...crossed.steps.map((step) => ` - Step: ${p.text(step.name ?? step.stepId)} \u2014 ${step.state === "failed" ? "failed" : "set aside unfinished"}`)
|
|
3528
3604
|
];
|
|
3529
3605
|
}
|
|
3530
3606
|
}
|
|
3531
|
-
function
|
|
3607
|
+
function shownBy(steps) {
|
|
3608
|
+
const named = steps.filter((s) => s.name !== void 0).length;
|
|
3609
|
+
if (named === 0)
|
|
3610
|
+
return "Each is shown by the method's own label for it:";
|
|
3611
|
+
if (named === steps.length)
|
|
3612
|
+
return "Each is shown by the method's own name for it:";
|
|
3613
|
+
return "Each is shown by the method's own name for it, or by its label where the record holds no name:";
|
|
3614
|
+
}
|
|
3615
|
+
function machines(coverage, checks) {
|
|
3532
3616
|
const label = "- Computers signed in to Halfcycle for this project when the phase closed: ";
|
|
3533
3617
|
if (coverage === null)
|
|
3534
3618
|
return `${label}not recorded.`;
|
|
3535
3619
|
if (coverage.enrolled === 0)
|
|
3536
3620
|
return `${label}none.`;
|
|
3537
|
-
|
|
3621
|
+
const line = `${label}${String(coverage.enrolled)}. Of these, ${String(coverage.conformant)} had reported Halfcycle's checks as fully set up.`;
|
|
3622
|
+
return checks === "not-observed" && coverage.conformant > 0 ? `${line} Set up is not the same as running: Halfcycle has no record of any check running during this phase.` : line;
|
|
3538
3623
|
}
|
|
3539
3624
|
function walkLines(walk, p) {
|
|
3540
3625
|
const label = "> - Acceptance walk (the finished work tried as a user would, by someone other than its builder): ";
|
|
@@ -3554,9 +3639,15 @@ function walkLines(walk, p) {
|
|
|
3554
3639
|
function statedOrNot(value, p) {
|
|
3555
3640
|
return value === null ? "Not stated by the client." : p.text(value);
|
|
3556
3641
|
}
|
|
3642
|
+
function decisionLine(attested) {
|
|
3643
|
+
if (attested.decision === "override") {
|
|
3644
|
+
return "> - Decision: close the phase by override, without stating its finish conditions were met.";
|
|
3645
|
+
}
|
|
3646
|
+
return attested.walk !== null && attested.walk.verdict === "defects" ? "> - Decision: close the phase as it stood, with the problems its acceptance walk found." : "> - Decision: the phase's agreed finish conditions were met.";
|
|
3647
|
+
}
|
|
3557
3648
|
function renderStated(attested, p) {
|
|
3558
3649
|
const lines = [
|
|
3559
|
-
attested
|
|
3650
|
+
decisionLine(attested),
|
|
3560
3651
|
`> - Name given: ${attested.actor === null ? "removed with the account that made this close" : p.text(attested.actor)}`,
|
|
3561
3652
|
`> - Reason given: ${attested.reason === null ? "none." : p.text(attested.reason)}`,
|
|
3562
3653
|
...walkLines(attested.walk, p),
|
|
@@ -3584,7 +3675,7 @@ function renderClose(heading, observed, attested, checks, p) {
|
|
|
3584
3675
|
`- Closed at: ${closedAtText(observed.closedAt, p)}`,
|
|
3585
3676
|
`- Signed in as: ${signedInAs(observed.closedBy, p)}`,
|
|
3586
3677
|
...outstanding(observed.crossed, p),
|
|
3587
|
-
machines(observed.seatCoverage)
|
|
3678
|
+
machines(observed.seatCoverage, checks)
|
|
3588
3679
|
].join("\n"),
|
|
3589
3680
|
STATED_LABEL,
|
|
3590
3681
|
renderStated(attested, p)
|
|
@@ -3847,8 +3938,7 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
|
|
|
3847
3938
|
};
|
|
3848
3939
|
}
|
|
3849
3940
|
function refetchCommand(phase) {
|
|
3850
|
-
|
|
3851
|
-
return `npx halfcycle@latest build-record ${quoted}`;
|
|
3941
|
+
return `npx halfcycle@latest build-record ${quoteForShell(phase)}`;
|
|
3852
3942
|
}
|
|
3853
3943
|
export {
|
|
3854
3944
|
BUILD_RECORD_DIR,
|
|
@@ -3878,7 +3968,6 @@ export {
|
|
|
3878
3968
|
layerDocumentRow,
|
|
3879
3969
|
mergeSettings,
|
|
3880
3970
|
mergeWithRecords,
|
|
3881
|
-
mintBoardEnterCode,
|
|
3882
3971
|
mintOrReadIdentity,
|
|
3883
3972
|
obtainBuildRecord,
|
|
3884
3973
|
questionsToPut,
|