halfcycle 0.5.1 → 0.5.3
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 +332 -33
- package/dist/cli.js +285 -104
- package/dist/cli.js.map +3 -3
- package/dist/index.js +221 -44
- package/dist/index.js.map +3 -3
- package/dist/install-plan.d.ts.map +1 -1
- package/dist/install.d.ts +2 -0
- package/dist/install.d.ts.map +1 -1
- package/dist/review.d.ts +3 -3
- package/dist/review.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -148,6 +148,9 @@ var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
|
148
148
|
var USER_PROMPT_SCOPE_REL = ".claude/hooks/user-prompt-scope.sh";
|
|
149
149
|
var USER_PROMPT_FINDINGS_REL = ".claude/hooks/user-prompt-findings.sh";
|
|
150
150
|
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
151
|
+
var CONTEXT_BUDGET_CHECK_REL = ".claude/hooks/context-budget-check.sh";
|
|
152
|
+
var CONTEXT_DRIFT_CHECK_REL = ".claude/hooks/context-drift-check.sh";
|
|
153
|
+
var CONTEXT_DRIFT_MATCHER = "Edit|Write";
|
|
151
154
|
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
152
155
|
var PRE_TOOL_USE_MATCHER = "^(Bash|Read|Grep|Write|Edit|MultiEdit|NotebookEdit|Agent|PowerShell)$";
|
|
153
156
|
function hookCommand(script, verb) {
|
|
@@ -224,6 +227,20 @@ var HALFCYCLE_HOOK_WIRING = [
|
|
|
224
227
|
timeout: 5,
|
|
225
228
|
command: hookCommand(SESSION_END_MARKER_REL),
|
|
226
229
|
script: SESSION_END_MARKER_REL
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
event: "SessionStart",
|
|
233
|
+
matcher: null,
|
|
234
|
+
timeout: 10,
|
|
235
|
+
command: hookCommand(CONTEXT_BUDGET_CHECK_REL),
|
|
236
|
+
script: CONTEXT_BUDGET_CHECK_REL
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
event: "PostToolUse",
|
|
240
|
+
matcher: CONTEXT_DRIFT_MATCHER,
|
|
241
|
+
timeout: 5,
|
|
242
|
+
command: hookCommand(CONTEXT_DRIFT_CHECK_REL),
|
|
243
|
+
script: CONTEXT_DRIFT_CHECK_REL
|
|
227
244
|
}
|
|
228
245
|
];
|
|
229
246
|
var RETIRED_HOOK_COMMANDS = [
|
|
@@ -233,7 +250,9 @@ var RETIRED_HOOK_COMMANDS = [
|
|
|
233
250
|
"bash ./.claude/hooks/guard-runner.sh hook post-tool-use",
|
|
234
251
|
"bash ./.claude/hooks/guard-runner.sh hook stop",
|
|
235
252
|
"bash ./.claude/hooks/guard-runner.sh hook subagent-stop",
|
|
236
|
-
"bash ./.claude/hooks/session-end-marker.sh"
|
|
253
|
+
"bash ./.claude/hooks/session-end-marker.sh",
|
|
254
|
+
"bash ./.claude/hooks/context-budget-check.sh",
|
|
255
|
+
"bash ./.claude/hooks/context-drift-check.sh"
|
|
237
256
|
];
|
|
238
257
|
var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
|
|
239
258
|
...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
|
|
@@ -395,15 +414,15 @@ function readReviewPart(raw) {
|
|
|
395
414
|
const name = raw["name"];
|
|
396
415
|
const built = raw["built"];
|
|
397
416
|
const decidable = raw["decidable"];
|
|
398
|
-
const
|
|
417
|
+
const awaitingAnswer = raw["awaitingAnswer"];
|
|
399
418
|
const sentBack = raw["sentBack"];
|
|
400
419
|
if (!isNonEmptyString(part) || !isNonEmptyString(name))
|
|
401
420
|
return null;
|
|
402
421
|
if (typeof built !== "boolean" || typeof decidable !== "boolean")
|
|
403
422
|
return null;
|
|
404
|
-
if (typeof
|
|
423
|
+
if (typeof awaitingAnswer !== "boolean" || typeof sentBack !== "boolean")
|
|
405
424
|
return null;
|
|
406
|
-
return { part, name, built, decidable, awaitingAnswer
|
|
425
|
+
return { part, name, built, decidable, awaitingAnswer, sentBack };
|
|
407
426
|
}
|
|
408
427
|
function isPlainObject(value) {
|
|
409
428
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
@@ -1099,7 +1118,9 @@ var CODEX_HOOK_WIRING = [
|
|
|
1099
1118
|
codexHookRow("PostToolUse", CODEX_WRITE_CAPABLE_TOOLS.join("|"), 60, GUARD_RUNNER_REL, "hook post-tool-use --agent codex"),
|
|
1100
1119
|
codexHookRow("Stop", null, 60, GUARD_RUNNER_REL, "hook stop --agent codex"),
|
|
1101
1120
|
codexHookRow("SubagentStop", null, 60, GUARD_RUNNER_REL, "hook subagent-stop --agent codex"),
|
|
1102
|
-
codexHookRow("SessionEnd", null, 3, SESSION_END_MARKER_REL)
|
|
1121
|
+
codexHookRow("SessionEnd", null, 3, SESSION_END_MARKER_REL),
|
|
1122
|
+
codexHookRow("SessionStart", null, 10, CONTEXT_BUDGET_CHECK_REL),
|
|
1123
|
+
codexHookRow("PostToolUse", CODEX_WRITE_CAPABLE_TOOLS.join("|"), 5, CONTEXT_DRIFT_CHECK_REL)
|
|
1103
1124
|
];
|
|
1104
1125
|
var RETIRED_CODEX_HOOK_COMMANDS = [];
|
|
1105
1126
|
function codexHookCommandSet(retired) {
|
|
@@ -4482,6 +4503,30 @@ var phaseReviewViewSchema = z20.discriminatedUnion("state", [
|
|
|
4482
4503
|
phaseReviewOpenSchema,
|
|
4483
4504
|
phaseReviewClosedSchema
|
|
4484
4505
|
]);
|
|
4506
|
+
function reviewPartAwaitingAnswer(part) {
|
|
4507
|
+
return part.questions.some((q) => q.agentAnswer === void 0);
|
|
4508
|
+
}
|
|
4509
|
+
function reviewPartSentBackIn(part, round) {
|
|
4510
|
+
return part.sentBack.some((s) => s.round === round);
|
|
4511
|
+
}
|
|
4512
|
+
function shownReviewEntryOf(view, shownAt) {
|
|
4513
|
+
const parts = view.parts.map((p) => ({
|
|
4514
|
+
part: p.part,
|
|
4515
|
+
name: p.name,
|
|
4516
|
+
built: p.built,
|
|
4517
|
+
decidable: p.decidable,
|
|
4518
|
+
awaitingAnswer: reviewPartAwaitingAnswer(p),
|
|
4519
|
+
sentBack: reviewPartSentBackIn(p, view.round)
|
|
4520
|
+
}));
|
|
4521
|
+
return {
|
|
4522
|
+
phase: view.phase,
|
|
4523
|
+
number: view.number,
|
|
4524
|
+
round: view.round,
|
|
4525
|
+
reviewVersion: view.reviewVersion,
|
|
4526
|
+
shownAt,
|
|
4527
|
+
parts
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4485
4530
|
|
|
4486
4531
|
// ../events/dist/build-record-v3.js
|
|
4487
4532
|
var BUILD_RECORD_V3_FORMAT = "halfcycle-build-record/v3";
|
|
@@ -5125,8 +5170,35 @@ function ownerWaitClosedLineOf(item, question2) {
|
|
|
5125
5170
|
}
|
|
5126
5171
|
var OWNER_WAIT_DECLARATIONS_READ_FAILED = "Halfcycle could not read the decisions that waited for you.";
|
|
5127
5172
|
|
|
5128
|
-
// ../events/dist/
|
|
5173
|
+
// ../events/dist/owner-wait-answer.js
|
|
5129
5174
|
import { z as z32 } from "zod";
|
|
5175
|
+
var ownerWaitShortIdSchema = z32.string().regex(/^[0-9a-f]{8}$/);
|
|
5176
|
+
var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
|
|
5177
|
+
var ownerWaitAnswerActSchema = z32.enum(OWNER_WAIT_ANSWER_ACTS);
|
|
5178
|
+
var ownerWaitChatRequestSchema = z32.discriminatedUnion("act", [
|
|
5179
|
+
z32.object({ act: z32.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
|
|
5180
|
+
z32.object({ act: z32.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
|
|
5181
|
+
]);
|
|
5182
|
+
function bareOutcome3(outcome) {
|
|
5183
|
+
return z32.object({ outcome: z32.literal(outcome) }).strict();
|
|
5184
|
+
}
|
|
5185
|
+
var ownerWaitChatResultSchema = z32.discriminatedUnion("outcome", [
|
|
5186
|
+
z32.object({
|
|
5187
|
+
outcome: z32.literal("recorded"),
|
|
5188
|
+
act: ownerWaitAnswerActSchema,
|
|
5189
|
+
question: z32.string().min(1)
|
|
5190
|
+
}).strict(),
|
|
5191
|
+
z32.object({ outcome: z32.literal("refused"), problems: z32.array(systemDescriptionProblemSchema).min(1) }).strict(),
|
|
5192
|
+
bareOutcome3("not-owner"),
|
|
5193
|
+
bareOutcome3("no-owner"),
|
|
5194
|
+
bareOutcome3("not-found"),
|
|
5195
|
+
bareOutcome3("ambiguous"),
|
|
5196
|
+
bareOutcome3("not-offered"),
|
|
5197
|
+
bareOutcome3("unavailable")
|
|
5198
|
+
]);
|
|
5199
|
+
|
|
5200
|
+
// ../events/dist/pacing.js
|
|
5201
|
+
import { z as z33 } from "zod";
|
|
5130
5202
|
var CLAUDE_PLANS = Object.freeze([
|
|
5131
5203
|
Object.freeze({ id: "pro", words: "Pro", startingAgents: 2 }),
|
|
5132
5204
|
Object.freeze({ id: "max-5x", words: "Max (5\xD7)", startingAgents: 4 }),
|
|
@@ -5137,7 +5209,7 @@ var CLAUDE_PLANS = Object.freeze([
|
|
|
5137
5209
|
Object.freeze({ id: "not-sure", words: "Not sure", startingAgents: 2 })
|
|
5138
5210
|
]);
|
|
5139
5211
|
var CLAUDE_PLAN_IDS = Object.freeze(CLAUDE_PLANS.map((plan) => plan.id));
|
|
5140
|
-
var claudePlanSchema =
|
|
5212
|
+
var claudePlanSchema = z33.enum(CLAUDE_PLAN_IDS);
|
|
5141
5213
|
var CLAUDE_PLAN_UNKNOWN = "not-sure";
|
|
5142
5214
|
function claudePlanOf(id) {
|
|
5143
5215
|
return CLAUDE_PLANS.find((plan) => plan.id === id) ?? CLAUDE_PLANS.find((plan) => plan.id === CLAUDE_PLAN_UNKNOWN) ?? CLAUDE_PLANS[0];
|
|
@@ -5146,21 +5218,21 @@ function claudePlanKnown(id) {
|
|
|
5146
5218
|
return id !== null && id !== CLAUDE_PLAN_UNKNOWN;
|
|
5147
5219
|
}
|
|
5148
5220
|
var CLAUDE_PLAN_SET_IN = ["setup", "console"];
|
|
5149
|
-
var claudePlanSetInSchema =
|
|
5221
|
+
var claudePlanSetInSchema = z33.enum(CLAUDE_PLAN_SET_IN);
|
|
5150
5222
|
var KEEP_FREE_PERCENTS = [0, 20];
|
|
5151
|
-
var keepFreePercentSchema =
|
|
5223
|
+
var keepFreePercentSchema = z33.union([z33.literal(KEEP_FREE_PERCENTS[0]), z33.literal(KEEP_FREE_PERCENTS[1])]);
|
|
5152
5224
|
var PACE_AGENTS_MAX = 20;
|
|
5153
5225
|
var PACE_WINDOW_KINDS = ["five-hour", "seven-day"];
|
|
5154
|
-
var paceWindowKindSchema =
|
|
5226
|
+
var paceWindowKindSchema = z33.enum(PACE_WINDOW_KINDS);
|
|
5155
5227
|
var PACE_WINDOW_WORDS = Object.freeze({
|
|
5156
5228
|
"five-hour": "5-hour window",
|
|
5157
5229
|
"seven-day": "weekly window"
|
|
5158
5230
|
});
|
|
5159
5231
|
var PACE_STATES = ["full", "slowing", "holding"];
|
|
5160
|
-
var paceStateSchema =
|
|
5161
|
-
var claudePlanRequestSchema =
|
|
5162
|
-
var keepFreeRequestSchema =
|
|
5163
|
-
var pacingSettingsSchema =
|
|
5232
|
+
var paceStateSchema = z33.enum(PACE_STATES);
|
|
5233
|
+
var claudePlanRequestSchema = z33.object({ claudePlan: claudePlanSchema }).strict();
|
|
5234
|
+
var keepFreeRequestSchema = z33.object({ keepFreePercent: keepFreePercentSchema }).strict();
|
|
5235
|
+
var pacingSettingsSchema = z33.object({
|
|
5164
5236
|
claudePlan: claudePlanSchema.nullable(),
|
|
5165
5237
|
claudePlanSetIn: claudePlanSetInSchema.nullable(),
|
|
5166
5238
|
keepFreePercent: keepFreePercentSchema
|
|
@@ -5168,43 +5240,43 @@ var pacingSettingsSchema = z32.object({
|
|
|
5168
5240
|
message: "a plan says where it was given, and only a plan does",
|
|
5169
5241
|
path: ["claudePlanSetIn"]
|
|
5170
5242
|
});
|
|
5171
|
-
var pacingBasisTaskSchema =
|
|
5243
|
+
var pacingBasisTaskSchema = z33.object({
|
|
5172
5244
|
id: buildTaskIdSchema,
|
|
5173
|
-
dependsOn:
|
|
5174
|
-
budgetMinutes:
|
|
5175
|
-
human:
|
|
5245
|
+
dependsOn: z33.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5246
|
+
budgetMinutes: z33.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
|
|
5247
|
+
human: z33.boolean()
|
|
5176
5248
|
}).strict();
|
|
5177
|
-
var pacingBasisSchema =
|
|
5178
|
-
outcome:
|
|
5249
|
+
var pacingBasisSchema = z33.object({
|
|
5250
|
+
outcome: z33.literal("basis"),
|
|
5179
5251
|
claudePlan: claudePlanSchema.nullable(),
|
|
5180
5252
|
keepFreePercent: keepFreePercentSchema,
|
|
5181
|
-
settingsUrl:
|
|
5253
|
+
settingsUrl: z33.string().min(1),
|
|
5182
5254
|
phase: phaseNameSchema.nullable(),
|
|
5183
|
-
tasks:
|
|
5184
|
-
medianTaskMinutes:
|
|
5185
|
-
medianTaskTokens:
|
|
5255
|
+
tasks: z33.array(pacingBasisTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
|
|
5256
|
+
medianTaskMinutes: z33.number().nonnegative().nullable(),
|
|
5257
|
+
medianTaskTokens: z33.number().nonnegative().nullable()
|
|
5186
5258
|
}).strict();
|
|
5187
|
-
var paceSuggestionSchema =
|
|
5188
|
-
number:
|
|
5189
|
-
minutes:
|
|
5190
|
-
pauses:
|
|
5259
|
+
var paceSuggestionSchema = z33.object({
|
|
5260
|
+
number: z33.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
5261
|
+
minutes: z33.number().int().nonnegative().nullable(),
|
|
5262
|
+
pauses: z33.number().int().nonnegative(),
|
|
5191
5263
|
claudePlan: claudePlanSchema.nullable(),
|
|
5192
5264
|
keepFreePercent: keepFreePercentSchema,
|
|
5193
|
-
settingsUrl:
|
|
5265
|
+
settingsUrl: z33.string().min(1).nullable(),
|
|
5194
5266
|
knownWindow: paceWindowKindSchema.nullable()
|
|
5195
5267
|
}).strict();
|
|
5196
5268
|
var PACE_NOT_PACED_REASONS = ["not-configured", "unreadable"];
|
|
5197
|
-
var paceRunResultSchema =
|
|
5198
|
-
|
|
5199
|
-
outcome:
|
|
5269
|
+
var paceRunResultSchema = z33.discriminatedUnion("outcome", [
|
|
5270
|
+
z33.object({
|
|
5271
|
+
outcome: z33.literal("paced"),
|
|
5200
5272
|
pace: paceStateSchema,
|
|
5201
|
-
number:
|
|
5202
|
-
allowedNow:
|
|
5273
|
+
number: z33.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
5274
|
+
allowedNow: z33.number().int().nonnegative(),
|
|
5203
5275
|
windowKind: paceWindowKindSchema.optional(),
|
|
5204
5276
|
until: utcIso8601.optional(),
|
|
5205
5277
|
suggestion: paceSuggestionSchema.optional()
|
|
5206
5278
|
}).strict(),
|
|
5207
|
-
|
|
5279
|
+
z33.object({ outcome: z33.literal("not-paced"), reason: z33.enum(PACE_NOT_PACED_REASONS) }).strict()
|
|
5208
5280
|
]);
|
|
5209
5281
|
function paceAgentsOf(n) {
|
|
5210
5282
|
return n === 1 ? "1 agent" : `${String(n)} agents`;
|
|
@@ -5289,15 +5361,15 @@ function paceAllowedNowOf(k) {
|
|
|
5289
5361
|
var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
|
|
5290
5362
|
|
|
5291
5363
|
// ../events/dist/autonomy.js
|
|
5292
|
-
import { z as
|
|
5364
|
+
import { z as z34 } from "zod";
|
|
5293
5365
|
var AUTONOMY_CHANGED_IN = ["console", "chat"];
|
|
5294
|
-
var autonomyChangedInSchema =
|
|
5295
|
-
var autonomySettingsSchema =
|
|
5296
|
-
on:
|
|
5366
|
+
var autonomyChangedInSchema = z34.enum(AUTONOMY_CHANGED_IN);
|
|
5367
|
+
var autonomySettingsSchema = z34.object({
|
|
5368
|
+
on: z34.boolean(),
|
|
5297
5369
|
// whether builds run on their own
|
|
5298
|
-
mayPublish:
|
|
5370
|
+
mayPublish: z34.boolean(),
|
|
5299
5371
|
// whether builds may publish or release without waiting
|
|
5300
|
-
maySpend:
|
|
5372
|
+
maySpend: z34.boolean(),
|
|
5301
5373
|
// whether builds may spend money or sign up without waiting
|
|
5302
5374
|
changedAt: utcIso8601.nullable(),
|
|
5303
5375
|
// when the setting last changed; null if never
|
|
@@ -5307,37 +5379,37 @@ var autonomySettingsSchema = z33.object({
|
|
|
5307
5379
|
message: "says when and where it changed, or neither",
|
|
5308
5380
|
path: ["changedIn"]
|
|
5309
5381
|
});
|
|
5310
|
-
var autonomyChangeRequestSchema =
|
|
5311
|
-
on:
|
|
5382
|
+
var autonomyChangeRequestSchema = z34.object({
|
|
5383
|
+
on: z34.boolean(),
|
|
5312
5384
|
// the switch
|
|
5313
|
-
mayPublish:
|
|
5385
|
+
mayPublish: z34.boolean(),
|
|
5314
5386
|
// the publish box
|
|
5315
|
-
maySpend:
|
|
5387
|
+
maySpend: z34.boolean()
|
|
5316
5388
|
// the spend box
|
|
5317
5389
|
}).strict();
|
|
5318
5390
|
var AUTONOMY_CHAT_ACTS = ["on", "off"];
|
|
5319
|
-
var autonomyChatActSchema =
|
|
5320
|
-
var autonomyChatRequestSchema =
|
|
5391
|
+
var autonomyChatActSchema = z34.enum(AUTONOMY_CHAT_ACTS);
|
|
5392
|
+
var autonomyChatRequestSchema = z34.object({
|
|
5321
5393
|
act: autonomyChatActSchema
|
|
5322
5394
|
// what the owner typed
|
|
5323
5395
|
}).strict();
|
|
5324
|
-
var autonomyChatResultSchema =
|
|
5325
|
-
|
|
5326
|
-
outcome:
|
|
5327
|
-
on:
|
|
5396
|
+
var autonomyChatResultSchema = z34.discriminatedUnion("outcome", [
|
|
5397
|
+
z34.object({
|
|
5398
|
+
outcome: z34.literal("recorded"),
|
|
5399
|
+
on: z34.boolean()
|
|
5328
5400
|
// whether builds now run on their own
|
|
5329
5401
|
}).strict(),
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5402
|
+
z34.object({ outcome: z34.literal("not-owner") }).strict(),
|
|
5403
|
+
z34.object({ outcome: z34.literal("no-owner") }).strict(),
|
|
5404
|
+
z34.object({ outcome: z34.literal("not-offered") }).strict(),
|
|
5405
|
+
z34.object({ outcome: z34.literal("unavailable") }).strict()
|
|
5334
5406
|
]);
|
|
5335
|
-
var autonomyStateSchema =
|
|
5336
|
-
on:
|
|
5407
|
+
var autonomyStateSchema = z34.object({
|
|
5408
|
+
on: z34.boolean(),
|
|
5337
5409
|
// whether builds run on their own
|
|
5338
|
-
mayPublish:
|
|
5410
|
+
mayPublish: z34.boolean(),
|
|
5339
5411
|
// whether builds may publish or release without waiting
|
|
5340
|
-
maySpend:
|
|
5412
|
+
maySpend: z34.boolean()
|
|
5341
5413
|
// whether builds may spend money or sign up without waiting
|
|
5342
5414
|
}).strict();
|
|
5343
5415
|
var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
@@ -5346,8 +5418,8 @@ var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
|
5346
5418
|
}).strict();
|
|
5347
5419
|
var AUTONOMY_STATE_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
5348
5420
|
var AUTONOMY_ACTIVITY_KINDS = ["went-ahead", "waiting", "approved", "closed", "waiting-on-owner"];
|
|
5349
|
-
var autonomyActivityKindSchema =
|
|
5350
|
-
var autonomyActivityRowSchema =
|
|
5421
|
+
var autonomyActivityKindSchema = z34.enum(AUTONOMY_ACTIVITY_KINDS);
|
|
5422
|
+
var autonomyActivityRowSchema = z34.object({
|
|
5351
5423
|
kind: autonomyActivityKindSchema,
|
|
5352
5424
|
// what happened
|
|
5353
5425
|
phase: scopePhaseSchema,
|
|
@@ -5360,6 +5432,8 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5360
5432
|
// whether the agent says it can be undone
|
|
5361
5433
|
how: statementText(STATEMENT_MAX).optional(),
|
|
5362
5434
|
// how to undo it, when it can be
|
|
5435
|
+
wait: ownerWaitShortIdSchema.optional(),
|
|
5436
|
+
// the short id the owner types to answer a waiting decision
|
|
5363
5437
|
at: utcIso8601
|
|
5364
5438
|
// when it happened
|
|
5365
5439
|
}).strict().superRefine((row, ctx) => {
|
|
@@ -5386,6 +5460,9 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5386
5460
|
});
|
|
5387
5461
|
}
|
|
5388
5462
|
}
|
|
5463
|
+
if (row.kind !== "waiting" && row.wait !== void 0) {
|
|
5464
|
+
ctx.addIssue({ code: "custom", message: "only a waiting decision is answered by typing", path: ["wait"] });
|
|
5465
|
+
}
|
|
5389
5466
|
if (row.kind === "waiting" && (row.undoable !== void 0 || row.how !== void 0)) {
|
|
5390
5467
|
ctx.addIssue({ code: "custom", message: "a waiting decision has not been made yet", path: ["undoable"] });
|
|
5391
5468
|
}
|
|
@@ -5393,11 +5470,11 @@ var autonomyActivityRowSchema = z33.object({
|
|
|
5393
5470
|
ctx.addIssue({ code: "custom", message: "says how to undo it exactly when it can be undone", path: ["how"] });
|
|
5394
5471
|
}
|
|
5395
5472
|
});
|
|
5396
|
-
var autonomyActivitySchema =
|
|
5397
|
-
rows:
|
|
5473
|
+
var autonomyActivitySchema = z34.object({
|
|
5474
|
+
rows: z34.array(autonomyActivityRowSchema)
|
|
5398
5475
|
// newest first
|
|
5399
5476
|
}).strict();
|
|
5400
|
-
var savedIdSchema =
|
|
5477
|
+
var savedIdSchema = z34.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lower-case letters, digits and single hyphens");
|
|
5401
5478
|
var SAVED_REF_PREFIX = "refs/halfcycle/saved/";
|
|
5402
5479
|
function savedRefOf(id) {
|
|
5403
5480
|
return `${SAVED_REF_PREFIX}${id}`;
|
|
@@ -5436,33 +5513,33 @@ function isGitFolderSegment(segment) {
|
|
|
5436
5513
|
return t === ".git" || /^git~\d+$/.test(t);
|
|
5437
5514
|
}
|
|
5438
5515
|
var SAVED_PATH_KEPT_IN = ["commit", "copy"];
|
|
5439
|
-
var savedPathSchema =
|
|
5440
|
-
path:
|
|
5516
|
+
var savedPathSchema = z34.object({
|
|
5517
|
+
path: z34.string().max(4096).refine(isProjectRelativePath, { message: "must be a path inside the project" }),
|
|
5441
5518
|
// the file, relative to the project
|
|
5442
|
-
keptIn:
|
|
5519
|
+
keptIn: z34.enum(SAVED_PATH_KEPT_IN)
|
|
5443
5520
|
// in the save's commit, or copied beside this file
|
|
5444
5521
|
}).strict();
|
|
5445
|
-
var savedCommitSchema =
|
|
5446
|
-
var savedBranchSchema =
|
|
5447
|
-
name:
|
|
5522
|
+
var savedCommitSchema = z34.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash");
|
|
5523
|
+
var savedBranchSchema = z34.object({
|
|
5524
|
+
name: z34.string().min(1).max(255).refine((n) => !hasControlCharacter(n) && !n.startsWith("-") && !n.includes("..") && n !== "HEAD", {
|
|
5448
5525
|
message: "must be a branch name"
|
|
5449
5526
|
}),
|
|
5450
5527
|
// the branch's name
|
|
5451
5528
|
commit: savedCommitSchema
|
|
5452
5529
|
// the commit it pointed at
|
|
5453
5530
|
}).strict();
|
|
5454
|
-
var savedSchema =
|
|
5531
|
+
var savedSchema = z34.object({
|
|
5455
5532
|
id: savedIdSchema,
|
|
5456
5533
|
// what restore is typed with
|
|
5457
5534
|
savedAt: utcIso8601,
|
|
5458
5535
|
// when the copy was made
|
|
5459
|
-
ref:
|
|
5536
|
+
ref: z34.string().min(1),
|
|
5460
5537
|
// where the saved commit is kept
|
|
5461
|
-
what:
|
|
5538
|
+
what: z34.string().min(1).max(2e3),
|
|
5462
5539
|
// what the action would have changed, in words
|
|
5463
|
-
paths:
|
|
5540
|
+
paths: z34.array(savedPathSchema),
|
|
5464
5541
|
// every saved file
|
|
5465
|
-
branches:
|
|
5542
|
+
branches: z34.array(savedBranchSchema)
|
|
5466
5543
|
// every saved line of work
|
|
5467
5544
|
}).strict().refine((s) => s.ref === savedRefOf(s.id), { message: "must be the ref of this save", path: ["ref"] });
|
|
5468
5545
|
function autonomyA13(accepted, carried) {
|
|
@@ -5472,8 +5549,9 @@ function autonomyA14(count, parts, acceptLine) {
|
|
|
5472
5549
|
const typed = acceptLine === null ? 'say "review what was built" and type the lines it gives.' : `say "review what was built", then type ${acceptLine} to accept them with those parts, or type the reject line it gives for a task's tests.`;
|
|
5473
5550
|
return `The phase waits for you: ${String(count)} tests the agents said pass were not run by Halfcycle, so ${namesOf(parts)} wait for you too. Only you can accept or reject those tests. In your own coding agent's session, ` + typed;
|
|
5474
5551
|
}
|
|
5475
|
-
function autonomyA15(count) {
|
|
5476
|
-
|
|
5552
|
+
function autonomyA15(count, acceptLine) {
|
|
5553
|
+
const typed = acceptLine === null ? 'say "review what was built" and type the lines it gives.' : `type ${acceptLine} to accept them and close the phase, or say "review what was built" to decide one differently.`;
|
|
5554
|
+
return `The phase waits for you: ${String(count)} parts were built without an approved scope or outside the plan, and only you can decide them. In your own coding agent's session, ${typed}`;
|
|
5477
5555
|
}
|
|
5478
5556
|
function namesOf(names) {
|
|
5479
5557
|
if (names.length <= 1)
|
|
@@ -6431,6 +6509,8 @@ function kinds() {
|
|
|
6431
6509
|
[".claude/hooks/user-prompt-findings.sh", "check-script"],
|
|
6432
6510
|
[".claude/hooks/session-end-marker.sh", "check-script"],
|
|
6433
6511
|
[".claude/hooks/session-start-marker.sh", "check-script"],
|
|
6512
|
+
[".claude/hooks/context-budget-check.sh", "check-script"],
|
|
6513
|
+
[".claude/hooks/context-drift-check.sh", "check-script"],
|
|
6434
6514
|
[SETTINGS_REL, "settings"],
|
|
6435
6515
|
[MCP_REGISTRATION_REL, "server-entry"],
|
|
6436
6516
|
[GITIGNORE_REL, "ignore-list"],
|
|
@@ -8425,13 +8505,133 @@ var MCP_HEADERS_KNOWN_HEADER_HASHES = [
|
|
|
8425
8505
|
function planMcpHeadersHelperRemoval(targetRepo) {
|
|
8426
8506
|
return decideRetiredRemoval(targetRepo, RETIRED_MCP_HEADERS_HELPER_REL, 1, MCP_HEADERS_KNOWN_HEADER_HASHES);
|
|
8427
8507
|
}
|
|
8508
|
+
var GENERATED_CONTEXT_BUDGET_HEADER = "# Context budget check \u2014 generated by";
|
|
8509
|
+
function generateContextBudgetCheck() {
|
|
8510
|
+
return `#!/usr/bin/env bash
|
|
8511
|
+
${GENERATED_CONTEXT_BUDGET_HEADER} the Halfcycle installer.
|
|
8512
|
+
# Runs when a session starts. It warns, and never blocks, when the instructions
|
|
8513
|
+
# every session loads have grown past their budget, so they can be trimmed
|
|
8514
|
+
# before every later session pays for them:
|
|
8515
|
+
# - AGENTS.md at the project root, over 16384 bytes;
|
|
8516
|
+
# - this project's memory index in Claude Code, over 20480 bytes or 150 lines
|
|
8517
|
+
# (Claude Code cuts the index off at about 25 KB or 200 lines when it loads it).
|
|
8518
|
+
|
|
8519
|
+
cat >/dev/null 2>&1 || true
|
|
8520
|
+
|
|
8521
|
+
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd -P )" || exit 0
|
|
8522
|
+
PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd -P )" || exit 0
|
|
8523
|
+
|
|
8524
|
+
NOTES=""
|
|
8525
|
+
|
|
8526
|
+
AGENTS_FILE="$PROJECT_ROOT/AGENTS.md"
|
|
8527
|
+
if [ -f "$AGENTS_FILE" ]; then
|
|
8528
|
+
BYTES=$(wc -c < "$AGENTS_FILE" | tr -d ' ')
|
|
8529
|
+
if [ "\${BYTES:-0}" -gt 16384 ]; then
|
|
8530
|
+
NOTES="\${NOTES}AGENTS.md is \${BYTES} bytes, over its budget of 16384. "
|
|
8531
|
+
fi
|
|
8532
|
+
fi
|
|
8533
|
+
|
|
8534
|
+
CONFIG_DIR="\${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
8535
|
+
SLUG=$(printf '%s' "\${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}" | sed 's/[^A-Za-z0-9]/-/g')
|
|
8536
|
+
INDEX="$CONFIG_DIR/projects/$SLUG/memory/MEMORY.md"
|
|
8537
|
+
if [ -f "$INDEX" ]; then
|
|
8538
|
+
BYTES=$(wc -c < "$INDEX" | tr -d ' ')
|
|
8539
|
+
LINES=$(wc -l < "$INDEX" | tr -d ' ')
|
|
8540
|
+
if [ "\${BYTES:-0}" -gt 20480 ] || [ "\${LINES:-0}" -gt 150 ]; then
|
|
8541
|
+
NOTES="\${NOTES}The memory index is \${BYTES} bytes and \${LINES} lines, over its budget of 20480 bytes and 150 lines. "
|
|
8542
|
+
fi
|
|
8543
|
+
fi
|
|
8544
|
+
|
|
8545
|
+
if [ -z "$NOTES" ]; then exit 0; fi
|
|
8546
|
+
|
|
8547
|
+
MESSAGE="Context budget: \${NOTES}Run the context-update command to fold and trim it."
|
|
8548
|
+
printf '{"systemMessage":"%s","hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\\n' "$MESSAGE" "$MESSAGE"
|
|
8549
|
+
exit 0
|
|
8550
|
+
`;
|
|
8551
|
+
}
|
|
8552
|
+
var GENERATED_CONTEXT_DRIFT_HEADER = "# Context drift check \u2014 generated by";
|
|
8553
|
+
function generateContextDriftCheck() {
|
|
8554
|
+
return `#!/usr/bin/env bash
|
|
8555
|
+
${GENERATED_CONTEXT_DRIFT_HEADER} the Halfcycle installer.
|
|
8556
|
+
# Context drift detector \u2014 runs after each file edit. If a trigger file was modified,
|
|
8557
|
+
# it asks the session to run the context-update command before the session ends.
|
|
8558
|
+
|
|
8559
|
+
set -euo pipefail
|
|
8560
|
+
|
|
8561
|
+
INPUT=$(cat)
|
|
8562
|
+
|
|
8563
|
+
# First line: the event's name, for the reply. Then one line per edited file, made
|
|
8564
|
+
# absolute against the event's cwd: the edit's file_path, or every path a patch names.
|
|
8565
|
+
# With no python3 on the machine it says nothing, rather than fail after every edit.
|
|
8566
|
+
PARSED=$(HOOK_INPUT="$INPUT" python3 - <<'PYEOF'
|
|
8567
|
+
import json, os, re
|
|
8568
|
+
try:
|
|
8569
|
+
data = json.loads(os.environ["HOOK_INPUT"])
|
|
8570
|
+
except Exception:
|
|
8571
|
+
raise SystemExit(0)
|
|
8572
|
+
tool_input = data.get("tool_input") or {}
|
|
8573
|
+
cwd = data.get("cwd") or os.getcwd()
|
|
8574
|
+
if tool_input.get("file_path"):
|
|
8575
|
+
paths = [tool_input["file_path"]]
|
|
8576
|
+
else:
|
|
8577
|
+
headers = r"^\\*\\*\\* (?:Add File|Update File|Delete File|Move to): (.+)$"
|
|
8578
|
+
paths = re.findall(headers, str(tool_input.get("command", "")), re.M)
|
|
8579
|
+
print(data.get("hook_event_name", ""))
|
|
8580
|
+
for p in paths:
|
|
8581
|
+
print(os.path.realpath(os.path.join(cwd, p.strip())))
|
|
8582
|
+
PYEOF
|
|
8583
|
+
) || exit 0
|
|
8584
|
+
|
|
8585
|
+
if [ -z "$PARSED" ]; then exit 0; fi
|
|
8586
|
+
HOOK_EVENT=$(printf '%s\\n' "$PARSED" | head -n 1)
|
|
8587
|
+
|
|
8588
|
+
# Portable PROJECT_ROOT derivation: the script lives two folders below the root
|
|
8589
|
+
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd -P )"
|
|
8590
|
+
PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd -P )"
|
|
8591
|
+
|
|
8592
|
+
REASONS=""
|
|
8593
|
+
while IFS= read -r FILE_PATH; do
|
|
8594
|
+
[ -n "$FILE_PATH" ] || continue
|
|
8595
|
+
case "$FILE_PATH" in
|
|
8596
|
+
"$PROJECT_ROOT"/*) ;;
|
|
8597
|
+
*) continue ;;
|
|
8598
|
+
esac
|
|
8599
|
+
|
|
8600
|
+
REL_PATH="\${FILE_PATH#$PROJECT_ROOT/}"
|
|
8601
|
+
|
|
8602
|
+
# Skip the instructions files and each agent's own folders (editing them needs no reminder)
|
|
8603
|
+
case "$REL_PATH" in
|
|
8604
|
+
CLAUDE.md|AGENTS.md|README.md|.claude/*|.codex/*|.agents/*|dist/*|node_modules/*|.git/*)
|
|
8605
|
+
continue ;;
|
|
8606
|
+
esac
|
|
8607
|
+
|
|
8608
|
+
REASON=""
|
|
8609
|
+
case "$REL_PATH" in
|
|
8610
|
+
package.json|package-lock.json) REASON="package.json changed \u2014 check Tech Stack / Commands sections" ;;
|
|
8611
|
+
wrangler.jsonc|wrangler.toml) REASON="wrangler config changed \u2014 check Deployment section" ;;
|
|
8612
|
+
astro.config.*|next.config.*|vite.config.*) REASON="build config changed \u2014 check Tech Stack / Architecture" ;;
|
|
8613
|
+
src/pages/api/*|src/routes/api/*|pages/api/*) REASON="API route changed \u2014 check API Endpoints" ;;
|
|
8614
|
+
scripts/*) REASON="scripts/ changed \u2014 check Commands section" ;;
|
|
8615
|
+
tsconfig.json|.eslintrc*|.prettierrc*) REASON="tool config changed \u2014 check Code Conventions" ;;
|
|
8616
|
+
esac
|
|
8617
|
+
if [ -n "$REASON" ]; then REASONS="\${REASONS}\${REL_PATH} was modified: \${REASON}. "; fi
|
|
8618
|
+
done < <(printf '%s\\n' "$PARSED" | tail -n +2)
|
|
8619
|
+
|
|
8620
|
+
if [ -z "$REASONS" ]; then exit 0; fi
|
|
8621
|
+
|
|
8622
|
+
REMINDER="Context drift detected. \${REASONS}Before ending this session, run the context-update command to audit and update AGENTS.md and the memory folder."
|
|
8623
|
+
python3 -c 'import json, sys; print(json.dumps({"hookSpecificOutput": {"hookEventName": sys.argv[1], "additionalContext": sys.argv[2]}}))' "$HOOK_EVENT" "$REMINDER"
|
|
8624
|
+
`;
|
|
8625
|
+
}
|
|
8428
8626
|
var OWNED_GENERATED_HEADERS = {
|
|
8429
8627
|
".claude/hooks/guard-runner.sh": GENERATED_GUARD_RUNNER_HEADER,
|
|
8430
8628
|
".claude/hooks/user-prompt-reminder.sh": GENERATED_USER_PROMPT_REMINDER_HEADER,
|
|
8431
8629
|
".claude/hooks/user-prompt-scope.sh": GENERATED_USER_PROMPT_SCOPE_HEADER,
|
|
8432
8630
|
".claude/hooks/user-prompt-findings.sh": GENERATED_USER_PROMPT_FINDINGS_HEADER,
|
|
8433
8631
|
".claude/hooks/session-end-marker.sh": GENERATED_SESSION_END_HEADER,
|
|
8434
|
-
".claude/hooks/session-start-marker.sh": GENERATED_SESSION_START_HEADER
|
|
8632
|
+
".claude/hooks/session-start-marker.sh": GENERATED_SESSION_START_HEADER,
|
|
8633
|
+
".claude/hooks/context-budget-check.sh": GENERATED_CONTEXT_BUDGET_HEADER,
|
|
8634
|
+
".claude/hooks/context-drift-check.sh": GENERATED_CONTEXT_DRIFT_HEADER
|
|
8435
8635
|
};
|
|
8436
8636
|
function hookScriptsThisVersionWrites() {
|
|
8437
8637
|
return [
|
|
@@ -8440,7 +8640,9 @@ function hookScriptsThisVersionWrites() {
|
|
|
8440
8640
|
[".claude/hooks/user-prompt-reminder.sh", generateUserPromptReminderHook()],
|
|
8441
8641
|
[".claude/hooks/user-prompt-scope.sh", generateUserPromptScopeHook()],
|
|
8442
8642
|
[".claude/hooks/user-prompt-findings.sh", generateUserPromptFindingsHook()],
|
|
8443
|
-
[".claude/hooks/session-start-marker.sh", generateSessionStartMarker()]
|
|
8643
|
+
[".claude/hooks/session-start-marker.sh", generateSessionStartMarker()],
|
|
8644
|
+
[".claude/hooks/context-budget-check.sh", generateContextBudgetCheck()],
|
|
8645
|
+
[".claude/hooks/context-drift-check.sh", generateContextDriftCheck()]
|
|
8444
8646
|
];
|
|
8445
8647
|
}
|
|
8446
8648
|
function generateMcpRegistration(existing, mcpOrigin) {
|
|
@@ -14044,20 +14246,14 @@ var CHANGE_WORDS = {
|
|
|
14044
14246
|
remove: "planned to be removed",
|
|
14045
14247
|
"not-in-plan": "not in the plan"
|
|
14046
14248
|
};
|
|
14047
|
-
function awaitingAnswer(part) {
|
|
14048
|
-
return part.questions.some((q) => q.agentAnswer === void 0);
|
|
14049
|
-
}
|
|
14050
|
-
function sentBackInRound(part, round) {
|
|
14051
|
-
return part.sentBack.some((s) => s.round === round);
|
|
14052
|
-
}
|
|
14053
14249
|
function partStatus(part, round) {
|
|
14054
14250
|
const settled = settledStatus(part);
|
|
14055
14251
|
if (settled !== void 0) {
|
|
14056
14252
|
return part.givenIn === "autonomy" ? `${settled}, ${DECIDED_BY_AUTONOMY_SENTENCE}` : settled;
|
|
14057
14253
|
}
|
|
14058
|
-
if (
|
|
14254
|
+
if (reviewPartAwaitingAnswer(part))
|
|
14059
14255
|
return "Waiting for the agent's answer";
|
|
14060
|
-
if (
|
|
14256
|
+
if (reviewPartSentBackIn(part, round))
|
|
14061
14257
|
return "Sent back, waiting for the next round";
|
|
14062
14258
|
return "Waits for your decision";
|
|
14063
14259
|
}
|
|
@@ -14232,22 +14428,7 @@ var AN3_LINE = "halfcycle review: the answer must be one paragraph of at most 1,
|
|
|
14232
14428
|
var AN4_LINE = "halfcycle review: the answer looks like it holds a password or key, so it was not sent. Write it again without it.";
|
|
14233
14429
|
var AN5_LINE = "halfcycle review: the answer was not accepted. Reword it in plain words and run this again.";
|
|
14234
14430
|
function shownReviewEntry(view, shownAt) {
|
|
14235
|
-
|
|
14236
|
-
part: p.part,
|
|
14237
|
-
name: p.name,
|
|
14238
|
-
built: p.built,
|
|
14239
|
-
decidable: p.decidable,
|
|
14240
|
-
awaitingAnswer: awaitingAnswer(p),
|
|
14241
|
-
sentBack: sentBackInRound(p, view.round)
|
|
14242
|
-
}));
|
|
14243
|
-
return {
|
|
14244
|
-
phase: view.phase,
|
|
14245
|
-
number: view.number,
|
|
14246
|
-
round: view.round,
|
|
14247
|
-
reviewVersion: view.reviewVersion,
|
|
14248
|
-
shownAt,
|
|
14249
|
-
parts
|
|
14250
|
-
};
|
|
14431
|
+
return shownReviewEntryOf(view, shownAt);
|
|
14251
14432
|
}
|
|
14252
14433
|
function writeShownReview(engagementId, entry, home) {
|
|
14253
14434
|
const written = updateShownRecord(engagementId, (current) => ({
|
|
@@ -14719,7 +14900,7 @@ function closeStepReport(phase, attempt, engagementId) {
|
|
|
14719
14900
|
exitCode = 0;
|
|
14720
14901
|
break;
|
|
14721
14902
|
case "autonomy-waits":
|
|
14722
|
-
stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name), typedAcceptLine(phase)) : autonomyA15(answer.count));
|
|
14903
|
+
stdout.push(answer.reason === "unchecked-claims" ? autonomyA14(answer.count, answer.parts.map((p) => p.name), typedAcceptLine(phase)) : autonomyA15(answer.count, typedAcceptLine(phase)));
|
|
14723
14904
|
exitCode = 0;
|
|
14724
14905
|
break;
|
|
14725
14906
|
default:
|