halfcycle 0.3.26 → 0.3.28
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 +8 -8
- package/bin/bin.bundle.mjs +122 -55
- package/dist/bin.d.ts +11 -5
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +1397 -359
- package/dist/bin.js.map +3 -3
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/create-engagement.d.ts +5 -5
- package/dist/device-signin.d.ts +17 -7
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +50 -4
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +689 -165
- package/dist/index.js.map +3 -3
- package/dist/install-manifest.d.ts +169 -0
- package/dist/install-manifest.d.ts.map +1 -0
- package/dist/install.d.ts +146 -7
- package/dist/install.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +25 -5
- package/dist/loopback-signin.d.ts.map +1 -1
- package/dist/merge-settings.d.ts +12 -0
- package/dist/merge-settings.d.ts.map +1 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/own-engagement.d.ts.map +1 -1
- package/dist/scan.d.ts +7 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/uninstall.d.ts +96 -0
- package/dist/uninstall.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/ci-bind.d.ts +0 -116
- package/dist/ci-bind.d.ts.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
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/README.md
CHANGED
|
@@ -44,15 +44,13 @@ Credit where it is due, and it is due. GStack, Matt Pocock's skills, GitHub Spec
|
|
|
44
44
|
|
|
45
45
|
The objection this usually meets is that it must be too much for a small project. It would be, if there were one setting. A folder you download has exactly one, so whoever wrote it had to choose who to disappoint, and it is usually the small project that gets buried and quietly drops the process in week three. This reads the work instead and sizes itself to it, which is the section further down.
|
|
46
46
|
|
|
47
|
-
Writing the process down was never the hard part. What a folder you download structurally cannot have is these
|
|
47
|
+
Writing the process down was never the hard part. What a folder you download structurally cannot have is these three things.
|
|
48
48
|
|
|
49
49
|
**1. Something other than you checks the work.** A document cannot audit itself, and the session that wrote the draft is the worst available reviewer of it. So Halfcycle's consistency check runs in a session that has never seen your draft, and the method makes that non-negotiable rather than advisory: after every round of fixes it runs again in another new session, never the one that asked for those fixes, because the reviewer that asked for a fix is the one who cannot see what the fix stranded. The guard checks are the harder edge — they run off your machine, on ours, on every real change, and one that fires either warns you or stops the work outright, depending on how serious the rule it broke is.
|
|
50
50
|
|
|
51
|
-
**2.
|
|
51
|
+
**2. The same gates on every contributor.** The requirement lives in the repository, so everybody inherits identical checks with nothing to set up, and a new hire is compliant in one command. No framework is single-player by accident; they are single-player by construction, and several people changing the same codebase faster than they can synchronise is the one problem a single-session rulebook cannot see.
|
|
52
52
|
|
|
53
|
-
**3.
|
|
54
|
-
|
|
55
|
-
**4. A record of how builds break that keeps growing.** This is the part that compounds, and it is why a fork is a snapshot that starts depreciating the day you take it. See below.
|
|
53
|
+
**3. A record of how builds break that keeps growing.** This is the part that compounds, and it is why a fork is a snapshot that starts depreciating the day you take it. See below.
|
|
56
54
|
|
|
57
55
|
---
|
|
58
56
|
|
|
@@ -132,7 +130,7 @@ Five layers, one direction, a loop at the bottom.
|
|
|
132
130
|
| **L1** | Decide | The product spec, drafted then interrogated, with nothing left hand-waved. |
|
|
133
131
|
| **L2** | Write it down | Architecture, a diagram, and the rules the system must never break, named so later work cites them. Diagram and prose must agree. |
|
|
134
132
|
| **L3** | Slice it | Phases that actually close. Oversized work gets split. Assumptions get labelled as assumptions. |
|
|
135
|
-
| **gate** | | **The readiness audit.** Eight checks over everything decided so far
|
|
133
|
+
| **gate** | | **The readiness audit.** Eight checks over everything decided so far. Your session runs each one against your own repository and Halfcycle judges the answers — the documents this review reads never leave your machine. Too many red is a hard stop. |
|
|
136
134
|
| **L4** | Design it | Feature specs. Every claim about existing code anchored to a real file and line or not asserted. A fresh reviewer that has never seen the spec, never the same one twice. |
|
|
137
135
|
| **L5** | Build it | Ordered work, then coordinator and worker agents in parallel. Checks on the real changes, an anti-tautology pass over the tests, reachability, and a cold walk of the deployed product by somebody who did not build it. |
|
|
138
136
|
|
|
@@ -152,13 +150,15 @@ This matters enough to be specific about.
|
|
|
152
150
|
|
|
153
151
|
**Source excerpts are off, at both ends, for every engagement today.** The wire format does have a field for them, so the honest version of this is a conditional statement rather than a flat one, and it rests on three things rather than on our good intentions: your runner does not attach an excerpt unless you turn a flag on, and that flag is off by default; anything that does arrive for an engagement that has not opted in is stripped on our side before the checks or any model see it; and there is no way to opt in today, because nothing in the product can set that flag. If that ever changes, our privacy policy changes with it, and it is the policy — not this paragraph — that states the guarantee.
|
|
154
152
|
|
|
155
|
-
**
|
|
153
|
+
**What the readiness audit submits, and what it never carries.** `halfcycle_submit_findings` is how your session sends the audit's answers: a probe's status, a count, a repo-relative path, a short name. Never a file body, a diff or a source excerpt — the wire carries no field for prose, so a path or a name is the most it can carry. What Halfcycle keeps is what your session submitted, the judgement it reached, and which account and machine sign-in the submission came from; the documents themselves never reach it.
|
|
154
|
+
|
|
155
|
+
**A promise, for the day a check does need to read something you wrote:** what you send it will be read and not kept: the finding and its evidence persist, the source does not, and training or benchmarking on it is foreclosed. It is written here now, labelled as a commitment rather than a feature, so the promise is public before the mechanism is.
|
|
156
156
|
|
|
157
157
|
**Nothing method-shaped is left behind.** The runbooks, templates and question sets reach the session that asks for them, scoped to the step you are on, and are never at rest in your repository.
|
|
158
158
|
|
|
159
159
|
## What you keep
|
|
160
160
|
|
|
161
|
-
Everything the method produces is yours, under your own `docs/`, in your git history, in plain markdown: the product spec, the architecture, the phase plans, the feature specs
|
|
161
|
+
Everything the method produces is yours, under your own `docs/`, in your git history, in plain markdown: the product spec, the architecture, the phase plans, the feature specs and the ordered work. It reads as documentation your team wrote, because it is.
|
|
162
162
|
|
|
163
163
|
Nothing you already had is changed. Existing settings are preserved, and a name collision is reported rather than resolved on your behalf.
|
|
164
164
|
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14549,6 +14549,11 @@ var HALFCYCLE_DIR_NAME = ".halfcycle";
|
|
|
14549
14549
|
var ENGAGEMENTS_DIR_NAME = "engagements";
|
|
14550
14550
|
var ENGAGEMENT_ENV_FILENAME = "env";
|
|
14551
14551
|
var ACCOUNT_STORE_FILENAME = "account.json";
|
|
14552
|
+
var ENGAGEMENT_ID_PATTERN = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
|
|
14553
|
+
var ENGAGEMENT_ID_SHAPE = new RegExp(ENGAGEMENT_ID_PATTERN);
|
|
14554
|
+
function isEngagementId(value) {
|
|
14555
|
+
return typeof value === "string" && ENGAGEMENT_ID_SHAPE.test(value);
|
|
14556
|
+
}
|
|
14552
14557
|
var ENGAGEMENT_ENV_HEADER = "# Halfcycle per-engagement credential \u2014 machine level, owner-only, never in a repository.";
|
|
14553
14558
|
function shq(value) {
|
|
14554
14559
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -14684,6 +14689,9 @@ function readConfig() {
|
|
|
14684
14689
|
function readNeverConfiguredReason() {
|
|
14685
14690
|
return process.env["HALFCYCLE_NOT_THIS_ACCOUNT"] === "1" ? "not-this-account" : "no-credential-yet";
|
|
14686
14691
|
}
|
|
14692
|
+
function isHalfcycleOwnCi() {
|
|
14693
|
+
return process.env["HALFCYCLE_INTERNAL_CI"] === "1";
|
|
14694
|
+
}
|
|
14687
14695
|
function readGuardOrigin() {
|
|
14688
14696
|
return resolveGuardOrigin(process.env);
|
|
14689
14697
|
}
|
|
@@ -14813,7 +14821,15 @@ var evaluationRequestSchema = external_exports.object({
|
|
|
14813
14821
|
* Omitting the field means "unknown", never "everything" — a client that
|
|
14814
14822
|
* says nothing is treated as an older one, not a capable one.
|
|
14815
14823
|
*/
|
|
14816
|
-
clientCapabilities: external_exports.array(external_exports.string()).optional()
|
|
14824
|
+
clientCapabilities: external_exports.array(external_exports.string()).optional(),
|
|
14825
|
+
/**
|
|
14826
|
+
* Whether this repository has any deploy file — a Dockerfile, a fly.toml or
|
|
14827
|
+
* a .env.example — anywhere in its tree. A check that compares code against
|
|
14828
|
+
* those files reports itself as not run when this is false.
|
|
14829
|
+
*
|
|
14830
|
+
* Omitting the field means "unknown": every check runs as it always has.
|
|
14831
|
+
*/
|
|
14832
|
+
repoHasDeployFiles: external_exports.boolean().optional()
|
|
14817
14833
|
}).strict();
|
|
14818
14834
|
|
|
14819
14835
|
// ../events/dist/result.js
|
|
@@ -14981,6 +14997,18 @@ var artefactRefSchema = external_exports.object({
|
|
|
14981
14997
|
name: external_exports.string(),
|
|
14982
14998
|
parent: external_exports.string()
|
|
14983
14999
|
}).strict();
|
|
15000
|
+
var coverageSchema = external_exports.object({
|
|
15001
|
+
asked: external_exports.number().int().nonnegative(),
|
|
15002
|
+
answered: external_exports.number().int().nonnegative()
|
|
15003
|
+
}).strict().superRefine((coverage, ctx) => {
|
|
15004
|
+
if (coverage.answered > coverage.asked) {
|
|
15005
|
+
ctx.addIssue({
|
|
15006
|
+
code: external_exports.ZodIssueCode.custom,
|
|
15007
|
+
path: ["answered"],
|
|
15008
|
+
message: "answered cannot be more than asked"
|
|
15009
|
+
});
|
|
15010
|
+
}
|
|
15011
|
+
});
|
|
14984
15012
|
var artefactRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
14985
15013
|
recordKind: external_exports.literal("artefact"),
|
|
14986
15014
|
artefactRef: artefactRefSchema,
|
|
@@ -15019,7 +15047,8 @@ var gateRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
|
15019
15047
|
gateKind: gateKindSchema,
|
|
15020
15048
|
verdict: gateVerdictSchema,
|
|
15021
15049
|
actor: external_exports.string(),
|
|
15022
|
-
outcomeReason: external_exports.string().max(2e3).optional()
|
|
15050
|
+
outcomeReason: external_exports.string().max(2e3).optional(),
|
|
15051
|
+
coverage: coverageSchema.optional()
|
|
15023
15052
|
}).strict().superRefine((rec, ctx) => {
|
|
15024
15053
|
const issue2 = outcomeReasonIssue(rec.verdict, rec.outcomeReason);
|
|
15025
15054
|
if (issue2) {
|
|
@@ -15032,7 +15061,8 @@ var interventionRecordSchema = stateRecordEnvelopeSchema.extend({
|
|
|
15032
15061
|
mechanism: mechanismSchema,
|
|
15033
15062
|
severity: severitySchema,
|
|
15034
15063
|
disposition: dispositionSchema,
|
|
15035
|
-
summary: external_exports.string().max(2e3)
|
|
15064
|
+
summary: external_exports.string().max(2e3),
|
|
15065
|
+
coverage: coverageSchema.optional()
|
|
15036
15066
|
}).strict();
|
|
15037
15067
|
var stateRecordSchema = external_exports.discriminatedUnion("recordKind", [
|
|
15038
15068
|
artefactRecordSchema,
|
|
@@ -15143,9 +15173,10 @@ var HOOK_REMEDY = `Fix it by running "npx halfcycle" in this repository: it may
|
|
|
15143
15173
|
var PERMISSIONS_BLOCK = "\n permissions:\n contents: read\n id-token: write\n\n";
|
|
15144
15174
|
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
15175
|
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}
|
|
15176
|
+
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} Binding a repository so a run means something is internal to Halfcycle \u2014 contact hello@halfcycle.ai to have this one bound. There is no secret to store.`;
|
|
15147
15177
|
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
15178
|
var CI_REMEDY = `This job cannot vouch for anything, because it ${CI_CANNOT_PROVE_PROJECT}. ${CI_REMEDY_LEAD} ${CI_OLDER_SETUP}`;
|
|
15179
|
+
var CI_NOT_SUPPORTED_MESSAGE = "[Halfcycle CI] This is Halfcycle-internal and is not supported in your CI.\n";
|
|
15149
15180
|
var NEVER_CONFIGURED_HEADLINE = "GUARD NEVER CONFIGURED";
|
|
15150
15181
|
var NOT_A_SERVICE_OUTAGE = "This is not a service outage.";
|
|
15151
15182
|
function neverConfiguredMessage(missing, remedy, label = "[Halfcycle]") {
|
|
@@ -15624,8 +15655,8 @@ function screenExcerpts(excerpts) {
|
|
|
15624
15655
|
}
|
|
15625
15656
|
|
|
15626
15657
|
// dist/extract/env-refs.js
|
|
15627
|
-
function extractEnvRefs(diffContent, context = {}) {
|
|
15628
|
-
const names = detectEnvReads(diffContent);
|
|
15658
|
+
function extractEnvRefs(diffContent, context = {}, filePath) {
|
|
15659
|
+
const names = detectEnvReads(diffContent, filePath !== void 0 && usesJsComments(filePath));
|
|
15629
15660
|
if (names.size === 0)
|
|
15630
15661
|
return [];
|
|
15631
15662
|
const result = [];
|
|
@@ -15634,19 +15665,31 @@ function extractEnvRefs(diffContent, context = {}) {
|
|
|
15634
15665
|
}
|
|
15635
15666
|
return result;
|
|
15636
15667
|
}
|
|
15637
|
-
|
|
15668
|
+
var JS_COMMENT_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro)$/i;
|
|
15669
|
+
function usesJsComments(filePath) {
|
|
15670
|
+
return JS_COMMENT_EXTENSIONS.test(filePath);
|
|
15671
|
+
}
|
|
15672
|
+
function detectEnvReads(diffContent, jsComments) {
|
|
15638
15673
|
const found = /* @__PURE__ */ new Set();
|
|
15639
15674
|
const dotPattern = /(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)/g;
|
|
15640
15675
|
const bracketPattern = /(?:process\.env|import\.meta\.env)\s*\[\s*['"]([A-Z_][A-Z0-9_]*)['"]\s*\]/g;
|
|
15641
|
-
const destructurePattern = /\{([^}]*)\}\s*=\s*(?:process\.env|import\.meta\.env)\b/g;
|
|
15642
|
-
for (const
|
|
15643
|
-
if (
|
|
15676
|
+
const destructurePattern = /\{([^{}]*)\}\s*=\s*(?:process\.env|import\.meta\.env)\b/g;
|
|
15677
|
+
for (const raw of diffContent.split("\n")) {
|
|
15678
|
+
if (raw.startsWith("-") && !raw.startsWith("---"))
|
|
15644
15679
|
continue;
|
|
15680
|
+
const isSource = raw.startsWith("+") && !raw.startsWith("+++") || raw.startsWith(" ");
|
|
15681
|
+
const line = jsComments && isSource ? raw.slice(1) : raw;
|
|
15682
|
+
const comment = jsComments && isSource ? commentedOut(line) : void 0;
|
|
15683
|
+
const live = (match) => comment === void 0 || !comment(match.index, match.index + match[0].length);
|
|
15645
15684
|
for (const match of line.matchAll(dotPattern))
|
|
15646
|
-
|
|
15685
|
+
if (live(match))
|
|
15686
|
+
found.add(match[1]);
|
|
15647
15687
|
for (const match of line.matchAll(bracketPattern))
|
|
15648
|
-
|
|
15688
|
+
if (live(match))
|
|
15689
|
+
found.add(match[1]);
|
|
15649
15690
|
for (const destructure of line.matchAll(destructurePattern)) {
|
|
15691
|
+
if (!live(destructure))
|
|
15692
|
+
continue;
|
|
15650
15693
|
for (const part of destructure[1].split(",")) {
|
|
15651
15694
|
const nameMatch = part.trim().match(/^([A-Z_][A-Z0-9_]*)\s*(?:[:=]|$)/);
|
|
15652
15695
|
if (nameMatch)
|
|
@@ -15656,6 +15699,28 @@ function detectEnvReads(diffContent) {
|
|
|
15656
15699
|
}
|
|
15657
15700
|
return found;
|
|
15658
15701
|
}
|
|
15702
|
+
var LINE_TERMINATOR = /[\r\u2028\u2029]/g;
|
|
15703
|
+
function commentedOut(line) {
|
|
15704
|
+
const lead = line.length - line.trimStart().length;
|
|
15705
|
+
let body;
|
|
15706
|
+
let close;
|
|
15707
|
+
let expression;
|
|
15708
|
+
if (line.startsWith("//", lead)) {
|
|
15709
|
+
body = lead + 2;
|
|
15710
|
+
LINE_TERMINATOR.lastIndex = body;
|
|
15711
|
+
close = LINE_TERMINATOR.exec(line)?.index ?? line.length;
|
|
15712
|
+
expression = "{";
|
|
15713
|
+
} else if (line.startsWith("/*", lead) || line.startsWith("{/*", lead)) {
|
|
15714
|
+
body = lead + (line[lead] === "{" ? 3 : 2);
|
|
15715
|
+
const found = line.indexOf("*/", body);
|
|
15716
|
+
close = found === -1 ? line.length : found;
|
|
15717
|
+
expression = line[lead] === "{" ? "${" : "{";
|
|
15718
|
+
} else {
|
|
15719
|
+
return void 0;
|
|
15720
|
+
}
|
|
15721
|
+
const opened = line.indexOf(expression, body);
|
|
15722
|
+
return (start, end) => start >= body && end <= close && (opened === -1 || opened >= start || opened >= close);
|
|
15723
|
+
}
|
|
15659
15724
|
function collectDeclarationFiles(context) {
|
|
15660
15725
|
const files = [];
|
|
15661
15726
|
if (context.dockerfileContent) {
|
|
@@ -15667,10 +15732,16 @@ function collectDeclarationFiles(context) {
|
|
|
15667
15732
|
if (context.envExampleContent) {
|
|
15668
15733
|
files.push({ kind: "env-example", path: ".env.example", content: context.envExampleContent });
|
|
15669
15734
|
}
|
|
15670
|
-
|
|
15671
|
-
files.push(
|
|
15735
|
+
for (const file2 of context.declarationFiles ?? [])
|
|
15736
|
+
files.push(file2);
|
|
15672
15737
|
return files;
|
|
15673
15738
|
}
|
|
15739
|
+
function repoHasDeployFiles(context) {
|
|
15740
|
+
const looked = context.declarationFiles !== void 0 || context.dockerfileContent !== void 0 || context.flyTomlContent !== void 0 || context.envExampleContent !== void 0;
|
|
15741
|
+
if (!looked)
|
|
15742
|
+
return void 0;
|
|
15743
|
+
return collectDeclarationFiles(context).length > 0;
|
|
15744
|
+
}
|
|
15674
15745
|
function resolveArgType(name, context) {
|
|
15675
15746
|
const files = collectDeclarationFiles(context);
|
|
15676
15747
|
const build = isBuildTimeSite(name, files);
|
|
@@ -15745,10 +15816,18 @@ function envExampleHas(name, content, argType) {
|
|
|
15745
15816
|
|
|
15746
15817
|
// dist/extract/strip-comments.js
|
|
15747
15818
|
function stripComments(code) {
|
|
15748
|
-
let out =
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15819
|
+
let out = "";
|
|
15820
|
+
let from = 0;
|
|
15821
|
+
for (; ; ) {
|
|
15822
|
+
const open = code.indexOf("/*", from);
|
|
15823
|
+
const close = open === -1 ? -1 : code.indexOf("*/", open + 2);
|
|
15824
|
+
if (close === -1) {
|
|
15825
|
+
out += code.slice(from, open === -1 ? code.length : open);
|
|
15826
|
+
break;
|
|
15827
|
+
}
|
|
15828
|
+
out += code.slice(from, open) + " ";
|
|
15829
|
+
from = close + 2;
|
|
15830
|
+
}
|
|
15752
15831
|
const lineComment = out.match(/(^|[^:])\/\//);
|
|
15753
15832
|
if (lineComment && lineComment.index !== void 0) {
|
|
15754
15833
|
const cut = lineComment.index + lineComment[1].length;
|
|
@@ -16422,26 +16501,16 @@ function stripDiffPrefix4(line) {
|
|
|
16422
16501
|
|
|
16423
16502
|
// dist/changeset.js
|
|
16424
16503
|
var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".rst", ".adoc"]);
|
|
16425
|
-
var
|
|
16426
|
-
"docs/",
|
|
16427
|
-
"/docs/",
|
|
16428
|
-
"skills/",
|
|
16429
|
-
"/skills/",
|
|
16430
|
-
"runbooks/",
|
|
16431
|
-
"/runbooks/",
|
|
16432
|
-
"method/",
|
|
16433
|
-
"/method/"
|
|
16434
|
-
];
|
|
16504
|
+
var DOC_DIR_NAMES = /* @__PURE__ */ new Set(["docs", "skills", "runbooks", "method"]);
|
|
16435
16505
|
function isCodeFile(filePath) {
|
|
16436
16506
|
const lower = filePath.toLowerCase();
|
|
16437
16507
|
for (const ext of DOC_EXTENSIONS) {
|
|
16438
16508
|
if (lower.endsWith(ext))
|
|
16439
16509
|
return false;
|
|
16440
16510
|
}
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
16444
|
-
}
|
|
16511
|
+
const dirs = lower.replace(/\\/g, "/").split("/").slice(0, -1);
|
|
16512
|
+
if (dirs.some((dir) => DOC_DIR_NAMES.has(dir)))
|
|
16513
|
+
return false;
|
|
16445
16514
|
return true;
|
|
16446
16515
|
}
|
|
16447
16516
|
var INSTALLER_OUTPUT_DIR = ".halfcycle/";
|
|
@@ -16479,7 +16548,7 @@ function buildChangeSet(files, context = {}) {
|
|
|
16479
16548
|
};
|
|
16480
16549
|
}
|
|
16481
16550
|
const astShapes = toAstShapes(detectBlastRadiusMarkers(filePath, diffContent));
|
|
16482
|
-
const envRefs = extractEnvRefs(diffContent, context);
|
|
16551
|
+
const envRefs = extractEnvRefs(diffContent, context, filePath);
|
|
16483
16552
|
const sqlWrites = extractSqlWrites(diffContent);
|
|
16484
16553
|
const thirdPartyEndpoints = extractThirdPartyEndpoints(diffContent, {
|
|
16485
16554
|
repoRoot: context.repoRoot
|
|
@@ -16517,6 +16586,15 @@ function deriveClientCapabilities(changeSet) {
|
|
|
16517
16586
|
}
|
|
16518
16587
|
return [...seen];
|
|
16519
16588
|
}
|
|
16589
|
+
function buildEvaluationRequest(engagementId, changeSet, context) {
|
|
16590
|
+
const hasDeployFiles = repoHasDeployFiles(context);
|
|
16591
|
+
return {
|
|
16592
|
+
engagementId,
|
|
16593
|
+
changeSet,
|
|
16594
|
+
clientCapabilities: deriveClientCapabilities(changeSet),
|
|
16595
|
+
...hasDeployFiles === void 0 ? {} : { repoHasDeployFiles: hasDeployFiles }
|
|
16596
|
+
};
|
|
16597
|
+
}
|
|
16520
16598
|
function produceExcerpts(diffContent, descriptors, caps) {
|
|
16521
16599
|
const excerpts = [];
|
|
16522
16600
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -16817,24 +16895,21 @@ async function runCi() {
|
|
|
16817
16895
|
guardServiceUrl: readGuardOrigin().origin,
|
|
16818
16896
|
guardServiceToken: acquired.credential.guardServiceToken,
|
|
16819
16897
|
guardEngagementId: acquired.credential.guardEngagementId,
|
|
16820
|
-
changeSet
|
|
16898
|
+
changeSet,
|
|
16899
|
+
context
|
|
16821
16900
|
});
|
|
16822
16901
|
}
|
|
16823
16902
|
}
|
|
16824
16903
|
if (!configResult.config) {
|
|
16825
16904
|
await emitRunRecord({ runType: "ci", outcome: "unconfigured", changeSet });
|
|
16826
|
-
process.stderr.write(ciNeverConfiguredMessage());
|
|
16905
|
+
process.stderr.write(isHalfcycleOwnCi() ? ciNeverConfiguredMessage() : CI_NOT_SUPPORTED_MESSAGE);
|
|
16827
16906
|
return 1;
|
|
16828
16907
|
}
|
|
16829
|
-
return await evaluateAndAct({ ...configResult.config, changeSet });
|
|
16908
|
+
return await evaluateAndAct({ ...configResult.config, changeSet, context });
|
|
16830
16909
|
}
|
|
16831
16910
|
async function evaluateAndAct(args2) {
|
|
16832
|
-
const { guardServiceUrl, guardServiceToken, guardEngagementId, changeSet } = args2;
|
|
16833
|
-
const clientResult = await evaluate(guardServiceUrl, guardServiceToken,
|
|
16834
|
-
engagementId: guardEngagementId,
|
|
16835
|
-
changeSet,
|
|
16836
|
-
clientCapabilities: deriveClientCapabilities(changeSet)
|
|
16837
|
-
});
|
|
16911
|
+
const { guardServiceUrl, guardServiceToken, guardEngagementId, changeSet, context } = args2;
|
|
16912
|
+
const clientResult = await evaluate(guardServiceUrl, guardServiceToken, buildEvaluationRequest(guardEngagementId, changeSet, context));
|
|
16838
16913
|
if (!clientResult.ok) {
|
|
16839
16914
|
const failure = classifyFailure(clientResult);
|
|
16840
16915
|
await emitRunRecord({
|
|
@@ -16846,7 +16921,7 @@ async function evaluateAndAct(args2) {
|
|
|
16846
16921
|
credentialRefused: failure.kind === "credential"
|
|
16847
16922
|
});
|
|
16848
16923
|
if (failure.kind === "credential") {
|
|
16849
|
-
process.stderr.write(credentialRejectedMessage(failure.statusCode, failure.message, CI_REMEDY, "[Halfcycle CI]"));
|
|
16924
|
+
process.stderr.write(isHalfcycleOwnCi() ? credentialRejectedMessage(failure.statusCode, failure.message, CI_REMEDY, "[Halfcycle CI]") : CI_NOT_SUPPORTED_MESSAGE);
|
|
16850
16925
|
return 1;
|
|
16851
16926
|
}
|
|
16852
16927
|
if (failure.kind === "service") {
|
|
@@ -16951,7 +17026,6 @@ import { homedir as homedir2, platform } from "node:os";
|
|
|
16951
17026
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
16952
17027
|
var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
|
|
16953
17028
|
var TOKEN_READ_KEY = "GUARD_SERVICE_TOKEN";
|
|
16954
|
-
var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16955
17029
|
function composeEngagementEnvPath(engagementId, home) {
|
|
16956
17030
|
return join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, ENGAGEMENT_ENV_FILENAME);
|
|
16957
17031
|
}
|
|
@@ -16979,7 +17053,7 @@ function readStoredEngagementToken(envFilePath) {
|
|
|
16979
17053
|
return value && value.trim() !== "" ? value : void 0;
|
|
16980
17054
|
}
|
|
16981
17055
|
function writeRefreshedToken(opts) {
|
|
16982
|
-
if (!
|
|
17056
|
+
if (!isEngagementId(opts.engagementId)) {
|
|
16983
17057
|
return { written: false, reason: "the engagement this session names is not one this store can be keyed by" };
|
|
16984
17058
|
}
|
|
16985
17059
|
let declaredReal;
|
|
@@ -17179,11 +17253,7 @@ async function runPostToolUse() {
|
|
|
17179
17253
|
guardServiceUrl,
|
|
17180
17254
|
token: guardServiceToken,
|
|
17181
17255
|
engagementId: guardEngagementId,
|
|
17182
|
-
request:
|
|
17183
|
-
engagementId: guardEngagementId,
|
|
17184
|
-
changeSet,
|
|
17185
|
-
clientCapabilities: deriveClientCapabilities(changeSet)
|
|
17186
|
-
}
|
|
17256
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17187
17257
|
});
|
|
17188
17258
|
const clientResult = evaluation.result;
|
|
17189
17259
|
if (!clientResult.ok) {
|
|
@@ -17468,11 +17538,7 @@ async function runSessionDiff(input, kind) {
|
|
|
17468
17538
|
guardServiceUrl,
|
|
17469
17539
|
token: guardServiceToken,
|
|
17470
17540
|
engagementId: guardEngagementId,
|
|
17471
|
-
request:
|
|
17472
|
-
engagementId: guardEngagementId,
|
|
17473
|
-
changeSet,
|
|
17474
|
-
clientCapabilities: deriveClientCapabilities(changeSet)
|
|
17475
|
-
}
|
|
17541
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17476
17542
|
});
|
|
17477
17543
|
const clientResult = evaluation.result;
|
|
17478
17544
|
if (!clientResult.ok) {
|
|
@@ -17866,6 +17932,7 @@ function readStdin3() {
|
|
|
17866
17932
|
}
|
|
17867
17933
|
main().catch((err) => {
|
|
17868
17934
|
process.stderr.write(`halfcycle-runner: unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
17935
|
+
Contact hello@halfcycle.ai if this keeps happening.
|
|
17869
17936
|
`);
|
|
17870
17937
|
process.exit(1);
|
|
17871
17938
|
});
|
package/dist/bin.d.ts
CHANGED
|
@@ -11,13 +11,19 @@
|
|
|
11
11
|
* halfcycle build-record <phase> — assemble the phase Build Record (D1)
|
|
12
12
|
* halfcycle open-phase <phase> — open a phase, with its entry decision
|
|
13
13
|
* halfcycle close-phase <phase> — record a phase's acceptance and close it
|
|
14
|
-
* halfcycle
|
|
15
|
-
* halfcycle ci unbind <owner>/<repo> — withdraw that trust
|
|
14
|
+
* halfcycle uninstall [--remove-credential] — take back what the installer wrote here
|
|
16
15
|
*
|
|
17
|
-
*
|
|
16
|
+
* NO `ci` VERB (T-25, operator decision 2026-09-22(c)) — `ci bind`/`ci unbind`
|
|
17
|
+
* generalised a fix for Halfcycle's own dogfood CI into a client-facing feature;
|
|
18
|
+
* setting up a project's CI is project scope, not something this product does for
|
|
19
|
+
* a client. See `ci-oidc-token-exchange.md`.
|
|
20
|
+
*
|
|
21
|
+
* `bareTarget` BELOW IS DERIVED FROM `CLI_VERBS` (T-21). The bare form
|
|
18
22
|
* (`npx halfcycle` / `npx halfcycle ./repo`) treats the first positional as an
|
|
19
|
-
* install target, so a
|
|
20
|
-
* it
|
|
23
|
+
* install target, so a verb it did not recognise would not be "unknown command" —
|
|
24
|
+
* it would silently be an install into a directory named after the verb. It was a
|
|
25
|
+
* second, hand-typed list of verbs until `uninstall` joined, which is the verb whose
|
|
26
|
+
* misreading would be worst: `halfcycle uninstall` INSTALLING. One list now.
|
|
21
27
|
*
|
|
22
28
|
* TWO GUARDS ON THAT SHAPE (W3-F-4, walk 4). The bare-form-installs behaviour is
|
|
23
29
|
* deliberate and stays — it is the primary distribution path — but walk 4 ran the
|
package/dist/bin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG"}
|