halfcycle 0.3.7 → 0.3.9
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 +37 -10
- package/README.md +2 -0
- package/bin/bin.bundle.mjs +114 -18
- package/dist/bin.js +748 -552
- package/dist/bin.js.map +3 -3
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/control-origin.d.ts +102 -0
- package/dist/control-origin.d.ts.map +1 -0
- package/dist/create-engagement.d.ts +3 -1
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/index.js +275 -102
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +1 -0
- package/dist/install.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
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
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{
|
|
6
|
+
{
|
|
7
|
+
"name": "halfcycle-setup",
|
|
8
|
+
"path": "../commands/halfcycle-setup.md"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "halfcycle-design",
|
|
12
|
+
"path": "../commands/halfcycle-design.md"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "write-spec",
|
|
16
|
+
"path": "../commands/write-spec.md"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "consistency-check",
|
|
20
|
+
"path": "../commands/consistency-check.md"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "task-graph",
|
|
24
|
+
"path": "../commands/task-graph.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "context-update",
|
|
28
|
+
"path": "../commands/context-update.md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "agent-teams-orchestration",
|
|
32
|
+
"path": "../commands/agent-teams-orchestration.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "task-execution",
|
|
36
|
+
"path": "../commands/task-execution.md"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "stack-assembly-worker",
|
|
40
|
+
"path": "../commands/stack-assembly-worker.md"
|
|
41
|
+
}
|
|
15
42
|
]
|
|
16
43
|
}
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ npx halfcycle
|
|
|
8
8
|
|
|
9
9
|
That is the whole install. One command, in the repo you want to work in.
|
|
10
10
|
|
|
11
|
+
**Where it connects.** That command talks to Halfcycle at `https://control.halfcycle.ai`, to sign you in through your browser and to create the engagement your work is recorded against. It is the same address for everybody and it ships in the CLI, so there is nothing to configure, nothing to look up, and no value anyone has to give you first; the addresses for your board and for the checks come back from there too. If it cannot be reached, the install stops and writes nothing rather than setting your repository up without cover.
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## What it is
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14608,7 +14608,31 @@ var firedGuardSchema = external_exports.object({
|
|
|
14608
14608
|
guardId: external_exports.string(),
|
|
14609
14609
|
patternRef: external_exports.string(),
|
|
14610
14610
|
severity: severitySchema,
|
|
14611
|
-
explanation: external_exports.string()
|
|
14611
|
+
explanation: external_exports.string(),
|
|
14612
|
+
/**
|
|
14613
|
+
* The repo-relative path of the file this firing was decided on. The matcher
|
|
14614
|
+
* has the file live at the moment it decides to fire; this is the field that
|
|
14615
|
+
* carries the answer out. It is the CLIENT's own coordinate — the wire type
|
|
14616
|
+
* still has no field for a matcher body, a channel, a version, a scope or a
|
|
14617
|
+
* guard's provenance, and physically cannot carry one.
|
|
14618
|
+
*
|
|
14619
|
+
* THE PRESENCE RULE, WHICH LIVES HERE AND NOT IN A CONSUMER'S PROSE: every
|
|
14620
|
+
* firing produced from this contract version onward carries `path`, so on the
|
|
14621
|
+
* WIRE, absence means exactly one thing — a response from a guard service
|
|
14622
|
+
* older than this contract. It is declared optional because a client's guard
|
|
14623
|
+
* runner is built from its own checkout and calls a service that may be older
|
|
14624
|
+
* than it; a required field would make that pairing unparseable.
|
|
14625
|
+
*
|
|
14626
|
+
* The rule does NOT transfer to a store that persists a firing, where an
|
|
14627
|
+
* absent path has a second legitimate meaning: a row written before the
|
|
14628
|
+
* column existed. A persisted NULL is therefore not a writer bug.
|
|
14629
|
+
*
|
|
14630
|
+
* NO SHAPE CONSTRAINT. This value is whatever the changed tree called the
|
|
14631
|
+
* file. A consumer that maps it onto a coordinate with its own path rule must
|
|
14632
|
+
* safe-parse it and decide what to do with a refusal, rather than assume the
|
|
14633
|
+
* two shapes agree.
|
|
14634
|
+
*/
|
|
14635
|
+
path: external_exports.string().optional()
|
|
14612
14636
|
}).strict();
|
|
14613
14637
|
var resultEnvelopeSchema = external_exports.object({
|
|
14614
14638
|
guardsFired: external_exports.array(firedGuardSchema),
|
|
@@ -16003,6 +16027,15 @@ function emitInfraWarning(message) {
|
|
|
16003
16027
|
process.stdout.write(`[Halfcycle] WARNING: Guard service unavailable \u2014 ${message}. Session continues (fail-open).
|
|
16004
16028
|
`);
|
|
16005
16029
|
}
|
|
16030
|
+
function emitLocalFault(message) {
|
|
16031
|
+
process.stdout.write(`[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).
|
|
16032
|
+
`);
|
|
16033
|
+
}
|
|
16034
|
+
function emitContractFault(message) {
|
|
16035
|
+
process.stdout.write(`[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
|
|
16036
|
+
This is not a service outage: the guard service answered. The suspect is this repository's own installed halfcycle runner, which is out of date with what the service now sends. Update it: run "npx halfcycle" in this repository, then retry. Session continues (fail-open).
|
|
16037
|
+
`);
|
|
16038
|
+
}
|
|
16006
16039
|
var UNCONFIGURED_EXIT_TOOL_USE = 2;
|
|
16007
16040
|
var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
|
|
16008
16041
|
function emitNeverConfigured(missing, exitCode) {
|
|
@@ -16018,6 +16051,52 @@ This is not a service outage. It means the guard has never run here \u2014 not o
|
|
|
16018
16051
|
${remedy}
|
|
16019
16052
|
`;
|
|
16020
16053
|
}
|
|
16054
|
+
var CREDENTIAL_REJECTED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
16055
|
+
function classifyFailure(failure) {
|
|
16056
|
+
if (failure.kind === "contract-error") {
|
|
16057
|
+
return {
|
|
16058
|
+
kind: "contract",
|
|
16059
|
+
message: failure.message,
|
|
16060
|
+
label: "contract error",
|
|
16061
|
+
reason: `contract error: ${failure.message}`
|
|
16062
|
+
};
|
|
16063
|
+
}
|
|
16064
|
+
if (failure.kind === "wire-error" && CREDENTIAL_REJECTED_STATUSES.has(failure.statusCode)) {
|
|
16065
|
+
const label = `credential rejected (${failure.statusCode})`;
|
|
16066
|
+
return {
|
|
16067
|
+
kind: "credential",
|
|
16068
|
+
statusCode: failure.statusCode,
|
|
16069
|
+
message: failure.message,
|
|
16070
|
+
label,
|
|
16071
|
+
reason: `${label}: ${failure.message}`
|
|
16072
|
+
};
|
|
16073
|
+
}
|
|
16074
|
+
return {
|
|
16075
|
+
kind: "service",
|
|
16076
|
+
message: failure.message,
|
|
16077
|
+
// Unchanged wording for this class ON PURPOSE — a genuine outage still says
|
|
16078
|
+
// exactly what it said before, and still fails open locally. This task moved
|
|
16079
|
+
// ONE condition out of this branch; it did not redefine the branch.
|
|
16080
|
+
label: "infrastructure failure",
|
|
16081
|
+
reason: `infrastructure failure: ${failure.message}`
|
|
16082
|
+
};
|
|
16083
|
+
}
|
|
16084
|
+
var CREDENTIAL_REJECTED_EXIT_TOOL_USE = UNCONFIGURED_EXIT_TOOL_USE;
|
|
16085
|
+
var CREDENTIAL_REJECTED_EXIT_STOP_FAMILY = UNCONFIGURED_EXIT_STOP_FAMILY;
|
|
16086
|
+
function credentialRejectedMessage(statusCode, serviceMessage, remedy, label = "[Halfcycle]") {
|
|
16087
|
+
const forbidden = statusCode === 403;
|
|
16088
|
+
const statusName = forbidden ? "Forbidden" : "Unauthorized";
|
|
16089
|
+
const cause = forbidden ? `this credential is not authorised for the engagement this repository is sending` : `the credential this repository is sending is not one it accepts`;
|
|
16090
|
+
return `${label} GUARD CREDENTIAL REJECTED \u2014 the guard service answered and refused this credential.
|
|
16091
|
+
No evaluation was performed: the service returned ${statusCode} ${statusName} \u2014 ${serviceMessage}
|
|
16092
|
+
This is not a service outage. The guard service is up and replying; ${cause}, so this change was not checked.
|
|
16093
|
+
${remedy}
|
|
16094
|
+
`;
|
|
16095
|
+
}
|
|
16096
|
+
function emitCredentialRejected(statusCode, serviceMessage, exitCode) {
|
|
16097
|
+
writeSync(2, credentialRejectedMessage(statusCode, serviceMessage, HOOK_REMEDY));
|
|
16098
|
+
process.exitCode = exitCode;
|
|
16099
|
+
}
|
|
16021
16100
|
|
|
16022
16101
|
// dist/ci.js
|
|
16023
16102
|
async function runCi() {
|
|
@@ -16071,21 +16150,24 @@ async function runCi() {
|
|
|
16071
16150
|
changeSet
|
|
16072
16151
|
});
|
|
16073
16152
|
if (!clientResult.ok) {
|
|
16074
|
-
const
|
|
16075
|
-
const failureReason = isInfra ? `infrastructure failure: ${clientResult.message}` : `contract error: ${clientResult.message}`;
|
|
16153
|
+
const failure = classifyFailure(clientResult);
|
|
16076
16154
|
await emitRunRecord({
|
|
16077
16155
|
engagementId: guardEngagementId,
|
|
16078
16156
|
runType: "ci",
|
|
16079
16157
|
outcome: outcomeForClientFailure(clientResult.kind),
|
|
16080
|
-
failureReason,
|
|
16158
|
+
failureReason: failure.reason,
|
|
16081
16159
|
changeSet
|
|
16082
16160
|
});
|
|
16083
|
-
if (
|
|
16084
|
-
process.stderr.write(
|
|
16161
|
+
if (failure.kind === "credential") {
|
|
16162
|
+
process.stderr.write(credentialRejectedMessage(failure.statusCode, failure.message, CI_REMEDY, "[Halfcycle CI]"));
|
|
16163
|
+
return 1;
|
|
16164
|
+
}
|
|
16165
|
+
if (failure.kind === "service") {
|
|
16166
|
+
process.stderr.write(`[Halfcycle CI] Infrastructure failure: Guard service unavailable \u2014 ${failure.message}
|
|
16085
16167
|
This is NOT a guard violation. Fix the infrastructure issue and re-run.
|
|
16086
16168
|
`);
|
|
16087
16169
|
} else {
|
|
16088
|
-
process.stderr.write(`[Halfcycle CI] Infrastructure failure (contract error): ${
|
|
16170
|
+
process.stderr.write(`[Halfcycle CI] Infrastructure failure (contract error): ${failure.message}
|
|
16089
16171
|
`);
|
|
16090
16172
|
}
|
|
16091
16173
|
return 1;
|
|
@@ -16165,7 +16247,7 @@ async function runPostToolUse() {
|
|
|
16165
16247
|
return;
|
|
16166
16248
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
16167
16249
|
if (!repoRootResult.ok) {
|
|
16168
|
-
|
|
16250
|
+
emitLocalFault(`Could not find git repo root: ${repoRootResult.message}`);
|
|
16169
16251
|
return;
|
|
16170
16252
|
}
|
|
16171
16253
|
const repoRoot = repoRootResult.value;
|
|
@@ -16189,16 +16271,23 @@ async function runPostToolUse() {
|
|
|
16189
16271
|
changeSet
|
|
16190
16272
|
});
|
|
16191
16273
|
if (!clientResult.ok) {
|
|
16192
|
-
const
|
|
16193
|
-
const errorLabel = isInfra ? "infrastructure failure" : "contract error";
|
|
16274
|
+
const failure = classifyFailure(clientResult);
|
|
16194
16275
|
await emitRunRecord({
|
|
16195
16276
|
engagementId: guardEngagementId,
|
|
16196
16277
|
runType: "hook",
|
|
16197
16278
|
outcome: outcomeForClientFailure(clientResult.kind),
|
|
16198
|
-
failureReason:
|
|
16279
|
+
failureReason: failure.reason,
|
|
16199
16280
|
changeSet
|
|
16200
16281
|
});
|
|
16201
|
-
|
|
16282
|
+
if (failure.kind === "credential") {
|
|
16283
|
+
emitCredentialRejected(failure.statusCode, failure.message, CREDENTIAL_REJECTED_EXIT_TOOL_USE);
|
|
16284
|
+
return;
|
|
16285
|
+
}
|
|
16286
|
+
if (failure.kind === "contract") {
|
|
16287
|
+
emitContractFault(failure.message);
|
|
16288
|
+
return;
|
|
16289
|
+
}
|
|
16290
|
+
emitInfraWarning(failure.reason);
|
|
16202
16291
|
return;
|
|
16203
16292
|
}
|
|
16204
16293
|
const telemetryConfig = readTelemetryConfig();
|
|
@@ -16401,7 +16490,7 @@ function collectDiff(repoRoot, base) {
|
|
|
16401
16490
|
async function runSessionDiff(input, kind) {
|
|
16402
16491
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
16403
16492
|
if (!repoRootResult.ok) {
|
|
16404
|
-
|
|
16493
|
+
emitLocalFault(`Could not find git repo root: ${repoRootResult.message}`);
|
|
16405
16494
|
return;
|
|
16406
16495
|
}
|
|
16407
16496
|
const repoRoot = repoRootResult.value;
|
|
@@ -16411,7 +16500,7 @@ async function runSessionDiff(input, kind) {
|
|
|
16411
16500
|
const diffBase = base.kind;
|
|
16412
16501
|
const collected = collectDiff(repoRoot, base);
|
|
16413
16502
|
if (!collected.ok) {
|
|
16414
|
-
|
|
16503
|
+
emitLocalFault(`Could not get session diff: ${collected.message}`);
|
|
16415
16504
|
return;
|
|
16416
16505
|
}
|
|
16417
16506
|
const { files: changedFiles, getDiff } = collected;
|
|
@@ -16442,17 +16531,24 @@ async function runSessionDiff(input, kind) {
|
|
|
16442
16531
|
changeSet
|
|
16443
16532
|
});
|
|
16444
16533
|
if (!clientResult.ok) {
|
|
16445
|
-
const
|
|
16446
|
-
const errorLabel = isInfra ? "infrastructure failure" : "contract error";
|
|
16534
|
+
const failure = classifyFailure(clientResult);
|
|
16447
16535
|
await emitRunRecord({
|
|
16448
16536
|
engagementId: guardEngagementId,
|
|
16449
16537
|
runType: "hook",
|
|
16450
16538
|
outcome: outcomeForClientFailure(clientResult.kind),
|
|
16451
|
-
failureReason:
|
|
16539
|
+
failureReason: failure.reason,
|
|
16452
16540
|
changeSet,
|
|
16453
16541
|
diffBase
|
|
16454
16542
|
});
|
|
16455
|
-
|
|
16543
|
+
if (failure.kind === "credential") {
|
|
16544
|
+
emitCredentialRejected(failure.statusCode, failure.message, CREDENTIAL_REJECTED_EXIT_STOP_FAMILY);
|
|
16545
|
+
return;
|
|
16546
|
+
}
|
|
16547
|
+
if (failure.kind === "contract") {
|
|
16548
|
+
emitContractFault(failure.message);
|
|
16549
|
+
return;
|
|
16550
|
+
}
|
|
16551
|
+
emitInfraWarning(failure.reason);
|
|
16456
16552
|
return;
|
|
16457
16553
|
}
|
|
16458
16554
|
const telemetryConfig = readTelemetryConfig();
|