halfcycle 0.10.0 → 0.11.1

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.
@@ -17302,6 +17302,28 @@ var timeTaskSummarySchema = external_exports.object({
17302
17302
  usage: timeUsageTotalsSchema
17303
17303
  }).strict();
17304
17304
  var TIME_KINDS_MAX = buildTaskKindSchema.options.length + 1;
17305
+ var timeSharePercentSchema = external_exports.number().nonnegative().finite();
17306
+ var timeShareKindSchema = external_exports.object({
17307
+ kind: external_exports.enum(TIME_ROW_KEYS),
17308
+ percent: timeSharePercentSchema
17309
+ }).strict();
17310
+ var timeShareKindsSchema = external_exports.array(timeShareKindSchema).max(TIME_ROW_KEYS.length).superRefine((kinds, ctx) => {
17311
+ const seen = /* @__PURE__ */ new Set();
17312
+ kinds.forEach((entry, i) => {
17313
+ if (seen.has(entry.kind))
17314
+ ctx.addIssue({ code: "custom", path: [i, "kind"], message: "lists one kind twice" });
17315
+ seen.add(entry.kind);
17316
+ });
17317
+ });
17318
+ var timeSummaryShareSchema = external_exports.discriminatedUnion("state", [
17319
+ external_exports.object({ state: external_exports.literal("unknown") }).strict(),
17320
+ external_exports.object({
17321
+ state: external_exports.literal("known"),
17322
+ weekCeilingTokens: external_exports.number().int().positive(),
17323
+ spentPercent: timeSharePercentSchema,
17324
+ kinds: timeShareKindsSchema
17325
+ }).strict()
17326
+ ]);
17305
17327
  var timeSummarySchema = external_exports.object({
17306
17328
  phase: phaseNameSchema,
17307
17329
  closed: external_exports.boolean(),
@@ -17319,7 +17341,8 @@ var timeSummarySchema = external_exports.object({
17319
17341
  notTiedToATask: external_exports.object({
17320
17342
  totals: timeTotalsSchema,
17321
17343
  usage: timeUsageTotalsSchema
17322
- }).strict()
17344
+ }).strict(),
17345
+ share: timeSummaryShareSchema.optional()
17323
17346
  }).strict().superRefine((v, ctx) => {
17324
17347
  if (v.closed !== (v.closedAt !== null)) {
17325
17348
  ctx.addIssue({ code: "custom", path: ["closedAt"], message: "must be set exactly when the phase is closed" });
@@ -17384,6 +17407,16 @@ function timeDurationLabelOf(seconds) {
17384
17407
  return `${String(hours)} h ${String(minutes).padStart(2, "0")} min`;
17385
17408
  }
17386
17409
 
17410
+ // ../events/dist/repository-facts.js
17411
+ var REPOSITORY_FACTS = Object.freeze(["database", "browser-app", "inbound-webhooks"]);
17412
+ var repositoryFactSchema = external_exports.enum(REPOSITORY_FACTS);
17413
+ var repositoryFactsSchema = external_exports.array(repositoryFactSchema).max(REPOSITORY_FACTS.length).refine((facts) => new Set(facts).size === facts.length, { message: "must name each fact once" });
17414
+ var REPOSITORY_FACT_WORDS = Object.freeze({
17415
+ database: "database",
17416
+ "browser-app": "web front end",
17417
+ "inbound-webhooks": "incoming webhooks"
17418
+ });
17419
+
17387
17420
  // ../events/dist/code-link.js
17388
17421
  var codeLinkAddressSchema = external_exports.string().regex(GITHUB_REPOSITORY_ADDRESS, { message: "must be a GitHub repository address: https://github.com/<owner>/<repo>" }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key, so it is not sent" });
17389
17422
  var codeLinkFactsSchema = external_exports.object({
@@ -18113,7 +18146,7 @@ var phaseWorkSchema = external_exports.object({
18113
18146
  var STANDING_RULE_FILE = "docs/invariants.md";
18114
18147
  var CHANGE_MOVED_BY = Object.freeze(["sort", "person", "self"]);
18115
18148
  var changeMovedBySchema = external_exports.enum(CHANGE_MOVED_BY);
18116
- var PHASE_PATH_SET_BY = Object.freeze(["open", "person", "self"]);
18149
+ var PHASE_PATH_SET_BY = Object.freeze(["open", "person", "self", "size"]);
18117
18150
  var phasePathSetBySchema = external_exports.enum(PHASE_PATH_SET_BY);
18118
18151
  var SMALL_CHANGE_OUTCOMES = Object.freeze(["done", "moved-up", "stopped"]);
18119
18152
  var smallChangeOutcomeSchema = external_exports.enum(SMALL_CHANGE_OUTCOMES);
@@ -18684,789 +18717,963 @@ function shownReviewEntryOf(view, shownAt) {
18684
18717
  };
18685
18718
  }
18686
18719
 
18687
- // ../events/dist/client-version.js
18688
- var CLIENT_VERSION_HEADER = "x-halfcycle-client";
18689
- var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
18690
- var HEADLESS_CANNOT_APPROVE = "(a headless session cannot approve, by design)";
18691
- var TYPED_REVIEW_OFF_SENTENCE = "Reviewing by typing is switched off for now.";
18692
- var CLIENT_UPDATE_SENTENCE = `To approve by typing in a session, Halfcycle needs updating: run \`${HALFCYCLE_COMMAND}\` in this project's folder, then reopen your session and ask again.`;
18693
-
18694
- // ../events/dist/device-auth.js
18695
- var utcIso86012 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
18696
- var DEVICE_AUTH_STATUS = {
18697
- /** The poll: minted, nobody has acted yet. Keep polling. 200. */
18698
- PENDING: "pending",
18699
- /** The poll: approved, and the credential is in this body. Once only. 200. */
18700
- APPROVED: "approved",
18701
- /** Confirm: the decision was recorded (approve or decline). 200. */
18702
- RECORDED: "recorded",
18703
- /** The human refused at the confirmation page. 400 on the poll. */
18704
- DECLINED: "declined",
18705
- /** The code's absolute expiry has passed. 400. */
18706
- EXPIRED: "expired",
18707
- /** Never minted, or already spent. 400. */
18708
- UNKNOWN: "unknown",
18709
- /** Confirm: neither `approve` nor `decline` was named. Nothing was bound. 400. */
18710
- INVALID_DECISION: "invalid-decision",
18711
- /** Confirm: an approval arrived with no signed-in identity. 400. */
18712
- IDENTITY_REQUIRED: "identity-required",
18713
- /** This deployment has no `SIGNIN_BASE_URL` / `DEVICE_AUTH_CONFIRM_SECRET`. 503. */
18714
- NOT_CONFIGURED: "not-configured",
18715
- /** Confirm: the shared confirmation secret was missing or wrong. 401. */
18716
- UNAUTHORIZED: "unauthorized",
18717
- /** Confirm: the code was already approved, declined or claimed. 409. */
18718
- ALREADY_DECIDED: "already-decided",
18719
- /**
18720
- * Confirm: an approval named a signed-in identity whose account has not accepted
18721
- * the terms of service and privacy policy currently in force. 400.
18722
- *
18723
- * NOT BOUND TO THE DEVICE CODE. Unlike every other refusal in this const, this one
18724
- * says nothing about the code itself — the code is still `pending` after this
18725
- * response, exactly as it was before the request, so the SAME userCode can be
18726
- * confirmed again once the caller has recorded acceptance. `deviceConfirmRequestSchema`'s
18727
- * `acceptTerms` is how a second confirm says it did.
18728
- */
18729
- TERMS_REQUIRED: "terms-required"
18730
- };
18731
- var DEVICE_POLL_REFUSALS = [
18732
- DEVICE_AUTH_STATUS.DECLINED,
18733
- DEVICE_AUTH_STATUS.EXPIRED,
18734
- DEVICE_AUTH_STATUS.UNKNOWN
18735
- ];
18736
- var deviceAuthStartResponseSchema = external_exports.object({
18737
- deviceCode: external_exports.string().min(1),
18738
- userCode: external_exports.string().min(1),
18739
- verificationUrl: external_exports.string().min(1),
18740
- expiresAt: utcIso86012,
18741
- pollIntervalMs: external_exports.number().int().positive()
18742
- }).strict();
18743
- var devicePollPendingSchema = external_exports.object({
18744
- status: external_exports.literal(DEVICE_AUTH_STATUS.PENDING),
18745
- pollIntervalMs: external_exports.number().int().positive()
18746
- }).strict();
18747
- var devicePollApprovedSchema = external_exports.object({
18748
- status: external_exports.literal(DEVICE_AUTH_STATUS.APPROVED),
18749
- accountId: external_exports.string().min(1),
18750
- credential: external_exports.string().min(1),
18751
- expiresAt: utcIso86012
18752
- }).strict();
18753
- var deviceAuthRefusalSchema = external_exports.object({
18754
- status: external_exports.enum([
18755
- DEVICE_AUTH_STATUS.DECLINED,
18756
- DEVICE_AUTH_STATUS.EXPIRED,
18757
- DEVICE_AUTH_STATUS.UNKNOWN,
18758
- DEVICE_AUTH_STATUS.INVALID_DECISION,
18759
- DEVICE_AUTH_STATUS.IDENTITY_REQUIRED,
18760
- DEVICE_AUTH_STATUS.NOT_CONFIGURED,
18761
- DEVICE_AUTH_STATUS.UNAUTHORIZED,
18762
- DEVICE_AUTH_STATUS.ALREADY_DECIDED,
18763
- DEVICE_AUTH_STATUS.TERMS_REQUIRED
18764
- ]),
18765
- message: external_exports.string().min(1)
18766
- }).strict();
18767
- var devicePollResponseSchema = external_exports.union([
18768
- devicePollPendingSchema,
18769
- devicePollApprovedSchema,
18770
- deviceAuthRefusalSchema
18771
- ]);
18772
- var deviceConfirmRequestSchema = external_exports.object({
18773
- userCode: external_exports.string().min(1),
18774
- decision: external_exports.enum(["approve", "decline"]),
18775
- externalAuthId: external_exports.string().min(1).optional(),
18776
- email: external_exports.string().optional(),
18777
- acceptTerms: external_exports.boolean().optional()
18778
- }).strict();
18779
- var deviceConfirmRecordedSchema = external_exports.object({
18780
- status: external_exports.literal(DEVICE_AUTH_STATUS.RECORDED),
18781
- accountId: external_exports.string().min(1).optional()
18782
- }).strict();
18783
- var deviceConfirmResponseSchema = external_exports.union([
18784
- deviceConfirmRecordedSchema,
18785
- deviceAuthRefusalSchema
18786
- ]);
18787
- var DASHBOARD_URL = "https://app.halfcycle.ai";
18788
-
18789
- // ../events/dist/project-page.js
18790
- function projectPageUrl(engagementId) {
18791
- return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
18720
+ // ../events/dist/stop-words.js
18721
+ var NOTHING_NEEDED = "Nothing is needed from you.";
18722
+ function isOneLine(v) {
18723
+ return !/[\r\n]/.test(v);
18792
18724
  }
18793
- function systemViewUrl(engagementId, phase) {
18794
- const page = projectPageUrl(engagementId);
18795
- if (page === void 0)
18796
- return void 0;
18797
- return `${page}/console/system-view?${new URLSearchParams({ phase }).toString()}`;
18725
+ function asksSomething(v) {
18726
+ return v.includes("?");
18798
18727
  }
18799
- function requestAccessSentence(engagementId) {
18800
- const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
18801
- return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
18728
+ function saysNothingIsNeeded(yourPart) {
18729
+ return yourPart === null || typeof yourPart === "string" && yourPart.trim() === NOTHING_NEEDED;
18802
18730
  }
18803
-
18804
- // ../events/dist/scope-notices.js
18805
- var scopePartNameSchema = external_exports.string().min(1).max(SYSTEM_PART_NAME_MAX);
18806
- var scopeNoteEntrySchema = external_exports.object({
18807
- part: scopePartNameSchema.optional(),
18808
- note: external_exports.string().min(1).max(SCOPE_NOTE_MAX)
18809
- }).strict();
18810
- var scopeSentBackSchema = external_exports.object({
18811
- phase: scopePhaseSchema,
18812
- number: external_exports.number().int().min(1),
18813
- notes: external_exports.array(scopeNoteEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX + 1)
18814
- }).strict().superRefine((entry, ctx) => {
18815
- entry.notes.forEach((n, i) => {
18816
- if (n.part === void 0 && i !== entry.notes.length - 1) {
18817
- ctx.addIssue({
18818
- code: external_exports.ZodIssueCode.custom,
18819
- path: ["notes", i],
18820
- message: "the note on the whole phase comes after every note on a part"
18821
- });
18822
- }
18823
- });
18824
- });
18825
- var scopeWaitingSchema = external_exports.object({
18826
- phase: scopePhaseSchema,
18827
- number: external_exports.number().int().min(1),
18828
- changedParts: external_exports.array(scopePartNameSchema).max(PHASE_PLAN_CHANGES_MAX)
18829
- }).strict();
18830
- var scopeNoticesSchema = external_exports.object({
18831
- sentBack: external_exports.array(scopeSentBackSchema),
18832
- waiting: external_exports.array(scopeWaitingSchema),
18833
- typedApprovalOffered: external_exports.boolean()
18834
- }).strict().refine((v) => v.sentBack.length > 0 || v.waiting.length > 0, {
18835
- message: "carried only when a phase was sent back or waits for the owner"
18731
+ var stopPartSchema = external_exports.string().refine((v) => v.trim().length > 0, { message: "must say something" }).refine(isOneLine, { message: "must be one line" });
18732
+ var stopWordsSchema = external_exports.object({
18733
+ happened: stopPartSchema,
18734
+ yourPart: stopPartSchema.nullable(),
18735
+ next: stopPartSchema
18736
+ }).strict().refine((w) => !(saysNothingIsNeeded(w.yourPart) && (asksSomething(w.happened) || asksSomething(w.next))), {
18737
+ message: "asks the person something, so it cannot say nothing is needed from them",
18738
+ path: ["yourPart"]
18836
18739
  });
18837
- var scopeApprovedNoticeSchema = external_exports.object({ phase: scopePhaseSchema }).strict();
18838
- var scopeApprovedNoticesSchema = external_exports.array(scopeApprovedNoticeSchema);
18839
- function approvedInConsoleSentence(phase) {
18840
- return `Scope for ${phaseWords(phase)} was approved in the console; continuing. Say so to the person, then carry on with the next step; they do not need to tell you.`;
18841
- }
18842
- function quotedParts(parts) {
18843
- return parts.map((p) => `"${p}"`).join(", ");
18844
- }
18845
- function approvalSentence(phase, offerTyping) {
18846
- if (offerTyping === "off")
18847
- return TYPED_APPROVAL_OFF_SENTENCE;
18848
- if (offerTyping === "update")
18849
- return CLIENT_UPDATE_SENTENCE;
18850
- return `In their own interactive session ${HEADLESS_CANNOT_APPROVE}, they ask to see the plan, check each part, and then type: approve ${phase}.`;
18740
+ function stopTextOf(words) {
18741
+ return `${words.happened} ${words.yourPart ?? NOTHING_NEEDED} ${words.next}`;
18851
18742
  }
18852
- function phaseWords(phase) {
18853
- return `phase "${phase}"`;
18743
+
18744
+ // ../events/dist/pacing.js
18745
+ var CAREFUL_START_AGENTS = 3;
18746
+ var KEEP_FREE_MAX = 90;
18747
+ var keepFreePercentSchema = external_exports.number().int().min(0).max(KEEP_FREE_MAX);
18748
+ var KEEP_FREE_DEFAULT = 20;
18749
+ var PACE_AGENTS_MAX = 20;
18750
+ var FIVE_HOUR_WINDOW_MINUTES = 300;
18751
+ var SEVEN_DAY_WINDOW_MINUTES = 10080;
18752
+ var PACE_WINDOW_MINUTES_MAX = 525600;
18753
+ var MINUTE_KEY = /^([1-9][0-9]{0,6})-minute$/;
18754
+ function isPaceWindowKind(value) {
18755
+ if (value === "five-hour" || value === "seven-day")
18756
+ return true;
18757
+ if (typeof value !== "string")
18758
+ return false;
18759
+ const match = MINUTE_KEY.exec(value);
18760
+ if (match === null)
18761
+ return false;
18762
+ const minutes = Number(match[1]);
18763
+ return minutes <= PACE_WINDOW_MINUTES_MAX && minutes !== FIVE_HOUR_WINDOW_MINUTES && minutes !== SEVEN_DAY_WINDOW_MINUTES;
18854
18764
  }
18855
- function sentBackNotice(entry) {
18856
- return [
18857
- `Before anything else: the project's owner sent the scope of ${phaseWords(entry.phase)} back with these notes.`,
18858
- ...entry.notes.map((n) => n.part === void 0 ? `- ${n.note}` : `- "${n.part}": ${n.note}`),
18859
- `Change the plan to answer every note, then send it again: say "plan the next phase" to start. It then waits for the owner's approval again.`
18860
- ].join("\n");
18765
+ var paceWindowKindSchema = external_exports.custom((value) => isPaceWindowKind(value), {
18766
+ message: "not a usage window key"
18767
+ });
18768
+ function paceWindowMinutesOf(kind) {
18769
+ if (kind === "five-hour")
18770
+ return FIVE_HOUR_WINDOW_MINUTES;
18771
+ if (kind === "seven-day")
18772
+ return SEVEN_DAY_WINDOW_MINUTES;
18773
+ return Number(MINUTE_KEY.exec(kind)?.[1] ?? 0);
18861
18774
  }
18862
- function systemViewSentence(offerTyping, address) {
18863
- if (address === void 0)
18864
- return [];
18865
- return [offerTyping === "yes" ? `Or they approve it in the System view: ${address}` : `They approve it in the System view: ${address}`];
18775
+ function paceWindowKindOf(minutes) {
18776
+ if (!Number.isInteger(minutes) || minutes < 1 || minutes > PACE_WINDOW_MINUTES_MAX)
18777
+ return null;
18778
+ if (minutes === FIVE_HOUR_WINDOW_MINUTES)
18779
+ return "five-hour";
18780
+ if (minutes === SEVEN_DAY_WINDOW_MINUTES)
18781
+ return "seven-day";
18782
+ return `${String(minutes)}-minute`;
18866
18783
  }
18867
- function waitingNotice(entry, offerTyping, engagementId) {
18868
- return [
18869
- ...entry.changedParts.length > 0 ? [`These parts changed since the owner last decided on this phase, and wait for their approval: ${quotedParts(entry.changedParts)}.`] : [],
18870
- `The scope of ${phaseWords(entry.phase)} is waiting for the project's owner to approve it.`,
18871
- approvalSentence(entry.phase, offerTyping),
18872
- ...systemViewSentence(offerTyping, systemViewUrl(engagementId, entry.phase))
18873
- ].join(" ");
18784
+ function paceWindowWordsOf(kind) {
18785
+ const minutes = paceWindowMinutesOf(kind);
18786
+ if (minutes === FIVE_HOUR_WINDOW_MINUTES)
18787
+ return "5-hour window";
18788
+ if (minutes === SEVEN_DAY_WINDOW_MINUTES)
18789
+ return "weekly window";
18790
+ if (minutes % 1440 === 0)
18791
+ return `${String(minutes / 1440)}-day window`;
18792
+ if (minutes % 60 === 0)
18793
+ return `${String(minutes / 60)}-hour window`;
18794
+ return `${String(minutes)}-minute window`;
18874
18795
  }
18875
- function scopeNoticeText(scope, options) {
18876
- const omit2 = options.omitActivePhase === true ? options.activePhase : void 0;
18877
- return {
18878
- sentBack: scope.sentBack.map(sentBackNotice),
18879
- waiting: scope.waiting.filter((w) => omit2 === void 0 || w.phase !== omit2).map((w) => waitingNotice(w, options.offerTyping, options.engagementId))
18880
- };
18796
+ function paceShareWordsOf(keepFreePercent) {
18797
+ return keepFreePercent === 20 ? "a fifth" : `${String(keepFreePercent)}%`;
18881
18798
  }
18882
-
18883
- // ../events/dist/review-notices.js
18884
- var reviewNoticeQuestionSchema = external_exports.object({
18885
- phase: scopePhaseSchema,
18886
- number: reviewPhaseNumberSchema,
18887
- part: systemPartIdSchema,
18888
- partName: systemPartSchema.shape.name,
18889
- question: reviewNoteSchema
18890
- }).strict();
18891
- var reviewNoticeSentBackSchema = external_exports.object({
18892
- phase: scopePhaseSchema,
18893
- number: reviewPhaseNumberSchema,
18894
- part: systemPartIdSchema,
18895
- partName: systemPartSchema.shape.name,
18896
- note: reviewNoteSchema
18799
+ var PACE_STATES = ["full", "slowing", "holding"];
18800
+ var paceStateSchema = external_exports.enum(PACE_STATES);
18801
+ var PACE_SHARE_GIVEN_IN = ["chat", "agent", "relayed"];
18802
+ var paceShareGivenInSchema = external_exports.enum(PACE_SHARE_GIVEN_IN);
18803
+ var paceShareRequestSchema = external_exports.object({ keepFreePercent: keepFreePercentSchema, givenIn: paceShareGivenInSchema }).strict();
18804
+ var paceShareResultSchema = external_exports.discriminatedUnion("outcome", [
18805
+ external_exports.object({
18806
+ outcome: external_exports.literal("recorded"),
18807
+ keepFreePercent: keepFreePercentSchema,
18808
+ moreAllowance: external_exports.boolean().optional()
18809
+ }).strict(),
18810
+ external_exports.object({ outcome: external_exports.literal("needs-the-person"), keepFreePercent: keepFreePercentSchema }).strict(),
18811
+ external_exports.object({ outcome: external_exports.literal("not-owner") }).strict(),
18812
+ external_exports.object({ outcome: external_exports.literal("no-owner") }).strict(),
18813
+ external_exports.object({ outcome: external_exports.literal("unavailable") }).strict()
18814
+ ]);
18815
+ var pacingBasisTaskSchema = external_exports.object({
18816
+ id: buildTaskIdSchema,
18817
+ dependsOn: external_exports.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
18818
+ budgetMinutes: external_exports.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
18819
+ human: external_exports.boolean(),
18820
+ kind: buildTaskKindSchema.optional()
18897
18821
  }).strict();
18898
- var reviewNoticesSchema = external_exports.object({
18899
- questions: external_exports.array(reviewNoticeQuestionSchema).max(REVIEW_PARTS_MAX),
18900
- sentBack: external_exports.array(reviewNoticeSentBackSchema).max(REVIEW_PARTS_MAX)
18901
- }).strict().refine((v) => v.questions.length > 0 || v.sentBack.length > 0, {
18902
- message: "carried only when a question waits or a part was sent back"
18903
- });
18904
- function phaseCalled(phase) {
18905
- return `phase "${phase}"`;
18906
- }
18907
- function questionNotice(entry) {
18908
- const answer = `${HALFCYCLE_COMMAND} review ${quoteForShell(entry.phase)} --answer ${quoteForShell(entry.part)} "<your answer>"`;
18909
- return `Before anything else: the project's owner asked about "${entry.partName}" in the review of ${phaseCalled(entry.phase)}: ${entry.question} Answer it with ${answer}.`;
18910
- }
18911
- function sentBackNotice2(entry) {
18912
- return `Before anything else: the project's owner sent "${entry.partName}" in ${phaseCalled(entry.phase)} back to building: ${entry.note} Rebuild it: add or reopen the tasks that build it. For each task that rebuilds it, add a task that reviews the rebuild, held by a different agent from the one that built it. When each review has passed, report the build with the time each task finished, then run the close step again.`;
18822
+ var PACE_DEFAULT_TASK_MINUTES = 30;
18823
+ var PACE_DURATION_ROUND_MINUTES = 10;
18824
+ function taskMinutesOf(task, medianTaskMinutes) {
18825
+ if (task.human)
18826
+ return 0;
18827
+ if (task.budgetMinutes !== void 0)
18828
+ return task.budgetMinutes;
18829
+ if (medianTaskMinutes !== null && medianTaskMinutes > 0)
18830
+ return medianTaskMinutes;
18831
+ return PACE_DEFAULT_TASK_MINUTES;
18913
18832
  }
18914
- function reviewNoticeText(review) {
18915
- return [...review.questions.map(questionNotice), ...review.sentBack.map(sentBackNotice2)];
18833
+ function paceDurationRoundedOf(minutes) {
18834
+ return Math.ceil(minutes / PACE_DURATION_ROUND_MINUTES) * PACE_DURATION_ROUND_MINUTES;
18916
18835
  }
18917
-
18918
- // ../events/dist/credential-wire.js
18919
- var engagementCredentialResponseSchema = external_exports.object({
18920
- engagementId: external_exports.string(),
18921
- sessionToken: external_exports.string(),
18922
- mcpUrl: external_exports.string(),
18923
- guardUrl: external_exports.string(),
18924
- controlTelemetryUrl: external_exports.string(),
18925
- methodVersion: external_exports.string().optional(),
18926
- consoleUrl: external_exports.string().optional()
18927
- }).strict();
18928
- var ciTokenExchangeResponseSchema = external_exports.object({
18929
- token: external_exports.string(),
18930
- expiresAt: utcIso8601,
18931
- engagementId: external_exports.string()
18932
- }).strict();
18933
- var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
18934
- var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
18935
- var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
18936
- var CREDENTIAL_PLACEHOLDERS = [
18937
- NO_CREDENTIAL_PLACEHOLDER,
18938
- NOT_THIS_ACCOUNT_PLACEHOLDER,
18939
- UPDATE_CLAUDE_CODE_PLACEHOLDER
18940
- ];
18941
- var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
18942
- 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.';
18943
- 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}.`;
18944
- 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.";
18945
- var CANNOT_REACH_SENTENCE = "Halfcycle is not connected: its server could not be reached, so its tools are unavailable in this session. Check your network, then reopen the project.";
18946
- var SERVICE_PROBLEM_SENTENCE = "Halfcycle is not connected: its service had a problem just now, so nothing in this session is being checked. Reopen the project to try again.";
18947
- var FOLDER_NOT_TRUSTED_SENTENCE = "Folder not yet trusted. Open this folder once in interactive Claude Code and trust it; then headless runs work.";
18948
- var SCOPE_UNCHECKED_SENTENCE = "If this project needs approval before building, that cannot be checked either, so work done now shows as built without approval unless already approved.";
18949
- var PLACEHOLDER_SENTENCES = Object.freeze({
18950
- [NO_CREDENTIAL_PLACEHOLDER]: NO_CREDENTIAL_SENTENCE,
18951
- [NOT_THIS_ACCOUNT_PLACEHOLDER]: NOT_THIS_ACCOUNT_SENTENCE,
18952
- [UPDATE_CLAUDE_CODE_PLACEHOLDER]: UPDATE_CLAUDE_CODE_SENTENCE
18953
- });
18954
-
18955
- // ../events/dist/scope-chat.js
18956
- var chatScopeApproveRequestSchema = external_exports.object({
18957
- act: external_exports.literal("approve"),
18958
- reference: phaseIdentitySchema,
18959
- planVersion: planVersionSchema
18960
- }).strict();
18961
- var chatScopeSendBackRequestSchema = external_exports.object({
18962
- act: external_exports.literal("send-back"),
18963
- reference: phaseIdentitySchema,
18964
- planVersion: planVersionSchema,
18965
- note: scopeNoteWireSchema.optional()
18966
- }).strict();
18967
- var chatScopeRequestSchema = external_exports.discriminatedUnion("act", [
18968
- chatScopeApproveRequestSchema,
18969
- chatScopeSendBackRequestSchema
18970
- ]);
18971
- var CHAT_SCOPE_ACTS = SCOPE_ACTS;
18972
- var chatScopeRecordedSchema = external_exports.object({
18973
- outcome: external_exports.literal("recorded"),
18974
- act: external_exports.enum(CHAT_SCOPE_ACTS),
18975
- phase: scopePhaseSchema,
18976
- planVersion: planVersionSchema
18836
+ var pacingBasisSchema = external_exports.object({
18837
+ outcome: external_exports.literal("basis"),
18838
+ claudePlan: external_exports.string().nullable().optional(),
18839
+ keepFreePercent: keepFreePercentSchema,
18840
+ settingsUrl: external_exports.string().optional(),
18841
+ phase: phaseNameSchema.nullable(),
18842
+ tasks: external_exports.array(pacingBasisTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
18843
+ medianTaskMinutes: external_exports.number().nonnegative().nullable(),
18844
+ medianTaskTokens: external_exports.number().nonnegative().nullable()
18977
18845
  }).strict();
18978
- var chatScopeAlreadyApprovedSchema = external_exports.object({
18979
- outcome: external_exports.literal("already-approved"),
18980
- replanNeeded: external_exports.boolean()
18846
+ var paceSuggestionSchema = external_exports.object({
18847
+ number: external_exports.number().int().min(1).max(PACE_AGENTS_MAX),
18848
+ minutes: external_exports.number().int().nonnegative().nullable(),
18849
+ pauses: external_exports.number().int().nonnegative(),
18850
+ keepFreePercent: keepFreePercentSchema,
18851
+ knownWindow: paceWindowKindSchema.nullable(),
18852
+ expectedTokens: external_exports.number().int().nonnegative().optional(),
18853
+ limitTokens: external_exports.number().int().positive().optional(),
18854
+ multiple: external_exports.number().positive().nullable().optional(),
18855
+ firstGuess: external_exports.boolean().optional(),
18856
+ limitSetBy: external_exports.enum(["default", "person", "other"]).optional(),
18857
+ weekCeilingTokens: external_exports.number().int().positive().optional(),
18858
+ workMinutes: external_exports.number().int().nonnegative().nullable().optional()
18981
18859
  }).strict();
18982
- var chatScopeRefusedSchema = external_exports.object({
18983
- outcome: external_exports.literal("refused"),
18984
- problems: external_exports.array(systemDescriptionProblemSchema).min(1)
18860
+ var PACE_NOT_PACED_REASONS = ["not-configured", "unreadable"];
18861
+ var limitHeldSchema = external_exports.object({
18862
+ used: external_exports.number().int().nonnegative(),
18863
+ limit: external_exports.number().int().positive(),
18864
+ raiseBy: external_exports.number().int().nonnegative(),
18865
+ working: external_exports.number().int().nonnegative(),
18866
+ weekCeilingTokens: external_exports.number().int().positive().optional()
18985
18867
  }).strict();
18986
- var chatAmbiguousSchema = external_exports.object({
18987
- outcome: external_exports.literal("ambiguous"),
18988
- phases: external_exports.array(scopePhaseSchema).min(2)
18868
+ var paceUsageSchema = external_exports.object({
18869
+ used: external_exports.number().int().nonnegative(),
18870
+ limit: external_exports.number().int().positive(),
18871
+ weekCeilingTokens: external_exports.number().int().positive().optional()
18989
18872
  }).strict();
18990
- function bareOutcome(outcome) {
18991
- return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
18992
- }
18993
- var chatScopeResultSchema = external_exports.discriminatedUnion("outcome", [
18994
- chatScopeRecordedSchema,
18995
- chatScopeAlreadyApprovedSchema,
18996
- chatScopeRefusedSchema,
18997
- chatAmbiguousSchema,
18998
- bareOutcome("show-first"),
18999
- bareOutcome("plan-changed"),
19000
- bareOutcome("build-started"),
19001
- bareOutcome("not-owner"),
19002
- bareOutcome("no-owner"),
19003
- bareOutcome("not-offered"),
19004
- bareOutcome("unavailable")
18873
+ var paceRunResultSchema = external_exports.discriminatedUnion("outcome", [
18874
+ external_exports.object({
18875
+ outcome: external_exports.literal("paced"),
18876
+ pace: paceStateSchema,
18877
+ number: external_exports.number().int().min(1).max(PACE_AGENTS_MAX),
18878
+ allowedNow: external_exports.number().int().nonnegative(),
18879
+ windowKind: paceWindowKindSchema.optional(),
18880
+ until: utcIso8601.optional(),
18881
+ keepFreePercent: keepFreePercentSchema.optional(),
18882
+ suggestion: paceSuggestionSchema.optional(),
18883
+ limit: external_exports.literal("reached").optional(),
18884
+ limitHeld: limitHeldSchema.optional(),
18885
+ usage: paceUsageSchema.optional()
18886
+ }).strict(),
18887
+ external_exports.object({ outcome: external_exports.literal("not-paced"), reason: external_exports.enum(PACE_NOT_PACED_REASONS) }).strict()
19005
18888
  ]);
19006
-
19007
- // ../events/dist/review-chat.js
19008
- var chatReviewAcceptRequestSchema = external_exports.object({
19009
- act: external_exports.literal("accept"),
19010
- reference: phaseIdentitySchema,
19011
- reviewVersion: external_exports.number().int().min(0),
19012
- parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
19013
- waitingClaims: external_exports.literal(true).optional()
19014
- }).strict();
19015
- function notedRequest(act) {
19016
- return external_exports.object({
19017
- act: external_exports.literal(act),
19018
- reference: phaseIdentitySchema,
19019
- reviewVersion: external_exports.number().int().min(0),
19020
- part: systemPartIdSchema,
19021
- note: reviewNoteWireSchema
19022
- }).strict();
18889
+ function tokenCountWordsOf(n) {
18890
+ if (n < 1e3)
18891
+ return String(Math.max(0, Math.round(n)));
18892
+ const rounded = Number(n.toPrecision(2));
18893
+ const [unit, word] = rounded >= 1e9 ? [1e9, "billion"] : rounded >= 1e6 ? [1e6, "million"] : [1e3, "thousand"];
18894
+ return `${String(Number((rounded / unit).toPrecision(2)))} ${word}`;
19023
18895
  }
19024
- var chatReviewQuestionRequestSchema = notedRequest("question");
19025
- var chatReviewSendBackRequestSchema = notedRequest("send-back");
19026
- var chatReviewCarryRequestSchema = notedRequest("carry");
19027
- var chatReviewDropRequestSchema = notedRequest("drop");
19028
- var chatReviewRejectRequestSchema = external_exports.object({
19029
- act: external_exports.literal("reject"),
19030
- reference: phaseIdentitySchema,
19031
- reviewVersion: external_exports.number().int().min(0),
19032
- task: buildTaskIdSchema,
19033
- note: reviewNoteWireSchema
19034
- }).strict();
19035
- var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
19036
- chatReviewAcceptRequestSchema,
19037
- chatReviewQuestionRequestSchema,
19038
- chatReviewSendBackRequestSchema,
19039
- chatReviewCarryRequestSchema,
19040
- chatReviewDropRequestSchema,
19041
- chatReviewRejectRequestSchema
19042
- ]);
19043
- function bareOutcome2(outcome) {
19044
- return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
18896
+ function paceAgentsOf(n) {
18897
+ return n === 1 ? "1 agent" : `${String(n)} agents`;
19045
18898
  }
19046
- function notTheConsoleNotDecidable(option) {
19047
- return option !== reviewNotDecidableSchema;
18899
+ var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
18900
+ var MONTH_NAMES = [
18901
+ "January",
18902
+ "February",
18903
+ "March",
18904
+ "April",
18905
+ "May",
18906
+ "June",
18907
+ "July",
18908
+ "August",
18909
+ "September",
18910
+ "October",
18911
+ "November",
18912
+ "December"
18913
+ ];
18914
+ var SIX_DAYS_MS = 6 * 24 * 60 * 60 * 1e3;
18915
+ function paceClockOf(at, now) {
18916
+ const hours = at.getHours();
18917
+ const clock = `${String(hours % 12 === 0 ? 12 : hours % 12)}:${String(at.getMinutes()).padStart(2, "0")} ${hours < 12 ? "am" : "pm"}`;
18918
+ const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate();
18919
+ if (sameDay)
18920
+ return clock;
18921
+ const day = DAY_NAMES[at.getDay()] ?? "";
18922
+ if (at.getTime() - now.getTime() > SIX_DAYS_MS) {
18923
+ return `${day} ${String(at.getDate())} ${MONTH_NAMES[at.getMonth()] ?? ""} ${clock}`;
18924
+ }
18925
+ return `${day} ${clock}`;
19048
18926
  }
19049
- var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
19050
- reviewPartsNotDecidableSchema,
19051
- ...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
19052
- bareOutcome2("show-first"),
19053
- bareOutcome2("not-offered"),
19054
- chatAmbiguousSchema,
19055
- bareOutcome2("nothing-waiting")
19056
- ]);
19057
- var reviewAnswerRequestSchema = external_exports.object({
19058
- part: systemPartIdSchema,
19059
- answer: statementText(REVIEW_ANSWER_MAX)
18927
+ var PACE_P5 = "This build may use all of your allowance. It will not slow down to leave room for you.";
18928
+ function paceS1Of(window, k, until, now, keepFreePercent) {
18929
+ return `Halfcycle is slowing this build so you keep about ${paceShareWordsOf(keepFreePercent)} of this ${paceWindowWordsOf(window)} for your own work. It will run at most ${paceAgentsOf(k)} at once until ${paceClockOf(until, now)}.`;
18930
+ }
18931
+ function paceS2Of(window, k, until, now, keepFreePercent) {
18932
+ const head = `Halfcycle has paused new work in this build until ${paceClockOf(until, now)}, when your ${paceWindowWordsOf(window)} resets, so you keep about ${paceShareWordsOf(keepFreePercent)} of it for your own work.`;
18933
+ if (k === 0)
18934
+ return head;
18935
+ return k === 1 ? `${head} The 1 agent already working will finish its task.` : `${head} The ${String(k)} agents already working will finish their tasks.`;
18936
+ }
18937
+ function paceS3Of(window, n) {
18938
+ return `Your ${paceWindowWordsOf(window)} has reset. Halfcycle is starting new work again, up to ${paceAgentsOf(n)} at once.`;
18939
+ }
18940
+ function paceOfferTextOf(input) {
18941
+ const canFaster = input.number < PACE_AGENTS_MAX;
18942
+ const canUseMore = input.keepFreePercent > 0;
18943
+ if (!canFaster && !canUseMore)
18944
+ return null;
18945
+ const head = `You have used only ${String(Math.floor(input.percentUsed))}% of this ${paceWindowWordsOf(input.window)}, and more than half of it has passed.`;
18946
+ const useUpTo = `"use up to ${String(Math.min(100, 100 - input.keepFreePercent + 10))}%" to let this build use more of it`;
18947
+ if (canFaster && canUseMore)
18948
+ return `${head} Say "faster" for one more agent, or ${useUpTo}.`;
18949
+ if (canFaster)
18950
+ return `${head} Say "faster" for one more agent.`;
18951
+ return `${head} Say ${useUpTo}.`;
18952
+ }
18953
+ function paceF1Of(k) {
18954
+ return `This build runs at most ${paceAgentsOf(k)} at once.`;
18955
+ }
18956
+ function paceAgentLineOf(pace, allowance, until, now) {
18957
+ const fewer = `once fewer than ${paceAgentsOf(allowance)} are working.`;
18958
+ if (until === null)
18959
+ return `Start it ${fewer}`;
18960
+ if (pace === "holding")
18961
+ return `Start it after ${paceClockOf(until, now)}.`;
18962
+ return `Start it after ${paceClockOf(until, now)}, or ${fewer}`;
18963
+ }
18964
+ var PACE_NOTHING_CHANGED = "Nothing was changed.";
18965
+ function paceHeldStartTextOf(input) {
18966
+ const { pace, allowance, working, window, until, now, keepFreePercent } = input;
18967
+ if (pace === "full" || window === null || until === null) {
18968
+ return `${paceF1Of(allowance)} ${paceAgentLineOf("full", allowance, null, now)} ${PACE_NOTHING_CHANGED}`;
18969
+ }
18970
+ const head = pace === "slowing" ? paceS1Of(window, allowance, until, now, keepFreePercent) : paceS2Of(window, working, until, now, keepFreePercent);
18971
+ return `${head} ${paceAgentLineOf(pace, allowance, until, now)} ${PACE_NOTHING_CHANGED}`;
18972
+ }
18973
+ var PACE_FIRST_READ = "Reading this computer's usage records for the first time. This can take a minute.";
18974
+ var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
18975
+ var PACE_ONLY_PERSON_RAISES = 'Only the person can raise the number of agents, by typing "faster" or "run 5 agents" in the chat. Nothing was changed.';
18976
+ function paceStatusLineOf(percent, window) {
18977
+ return `Halfcycle \xB7 ${String(Math.round(percent))}% of this ${paceWindowWordsOf(window)} used`;
18978
+ }
18979
+ var STARTING_TOKENS_PER_TASK = 9e6;
18980
+ var LIMIT_MULTIPLE_DEFAULT = 2;
18981
+ var LIMIT_MULTIPLE_RANGE = { min: 1, max: 20 };
18982
+ var LIMIT_FIGURE_RANGE = { min: 1e6, max: 1e11 };
18983
+ var LIMIT_HISTORY_MAX = 20;
18984
+ var LIMIT_ROUND_TOKENS = 1e3;
18985
+ var LIMIT_COUNTED_KINDS = ["build", "review", "check"];
18986
+ var LIMIT_PARTS = ["half", "three-quarters", "nine-tenths", "limit"];
18987
+ var limitPartSchema = external_exports.enum(LIMIT_PARTS);
18988
+ var LIMIT_PART_SHARE = {
18989
+ half: 0.5,
18990
+ "three-quarters": 0.75,
18991
+ "nine-tenths": 0.9,
18992
+ limit: 1
18993
+ };
18994
+ var LIMIT_SET_BY = ["default", "person", "other"];
18995
+ var limitSetBySchema = external_exports.enum(LIMIT_SET_BY);
18996
+ var LIMIT_TYPED_BY = ["person", "not-attended", "cannot-tell"];
18997
+ var limitTypedBySchema = external_exports.enum(LIMIT_TYPED_BY);
18998
+ var limitHistoryEntrySchema = external_exports.object({
18999
+ at: utcIso8601,
19000
+ kind: external_exports.enum(["raised", "set", "stopped"]),
19001
+ limit: external_exports.number().int().positive(),
19002
+ by: limitTypedBySchema,
19003
+ relayed: external_exports.literal(true).optional()
19060
19004
  }).strict();
19061
- var reviewAnswerRecordedSchema = external_exports.object({
19062
- outcome: external_exports.literal("recorded"),
19063
- name: systemPartSchema.shape.name
19005
+ var PHASE_LIMIT_FILENAME = "limit.json";
19006
+ var phaseLimitFileSchema = external_exports.object({
19007
+ phase: phaseNameSchema,
19008
+ expected: external_exports.number().int().nonnegative(),
19009
+ expectedMinutes: external_exports.number().nonnegative().nullable(),
19010
+ limit: external_exports.number().int().positive(),
19011
+ multiple: external_exports.number().positive().nullable(),
19012
+ setBy: limitSetBySchema,
19013
+ firstGuess: external_exports.boolean(),
19014
+ shown: external_exports.array(limitPartSchema).max(LIMIT_PARTS.length),
19015
+ awaiting: external_exports.boolean(),
19016
+ history: external_exports.array(limitHistoryEntrySchema).max(LIMIT_HISTORY_MAX)
19064
19017
  }).strict();
19065
- var reviewAnswerResultSchema = external_exports.discriminatedUnion("outcome", [
19066
- reviewAnswerRecordedSchema,
19067
- bareOutcome2("no-question"),
19068
- reviewRefusedSchema,
19069
- bareOutcome2("unavailable")
19070
- ]);
19018
+ function phaseLimitFileOf(raw, phase) {
19019
+ const parsed = phaseLimitFileSchema.safeParse(raw);
19020
+ if (!parsed.success)
19021
+ return null;
19022
+ return phase === null || parsed.data.phase === phase ? parsed.data : null;
19023
+ }
19024
+ var phaseUsageFileSchema = external_exports.object({
19025
+ phase: phaseNameSchema,
19026
+ openedAt: utcIso8601,
19027
+ tokens: external_exports.number().int().nonnegative(),
19028
+ at: utcIso8601
19029
+ }).strict();
19030
+ function limitPartLineOf(part, used, weekCeilingTokens = null) {
19031
+ return limitPartShareTextOf(part, used, weekCeilingTokens);
19032
+ }
19033
+ function limitReachedPartsOf(input) {
19034
+ const { figure, caveat } = limitReachedFigureOf(input.limit, input.weekCeilingTokens);
19035
+ const finish = input.working === 0 ? "" : input.working === 1 ? " The 1 agent already working will finish its task." : ` The ${String(input.working)} agents already working will finish their tasks.`;
19036
+ return {
19037
+ happened: `This phase has reached its usage limit${figure}. No new work will start.${finish}`,
19038
+ yourPart: 'Say "keep going" to raise the limit and carry on, or "stop" to leave the phase paused here.',
19039
+ next: `Nothing is lost either way; the work waits for your answer.${caveat}`
19040
+ };
19041
+ }
19042
+ function limitReachedLineOf(input) {
19043
+ return stopTextOf(limitReachedPartsOf(input));
19044
+ }
19045
+ function limitHeldStartTextOf(limit, weekCeilingTokens = null) {
19046
+ return `${limitHeldShareTextOf(limit, weekCeilingTokens)} Ask the person to say "keep going" or "stop", and start nothing until they have. ${PACE_NOTHING_CHANGED}`;
19047
+ }
19071
19048
 
19072
- // ../events/dist/owner-waits.js
19073
- var OWNER_WAIT_ITEMS = Object.freeze([
19074
- Object.freeze({ id: "delete-users-data", words: "Deleting or replacing data people already have." }),
19075
- Object.freeze({
19076
- id: "change-promises",
19077
- words: "Changing what the product promises its users, such as pricing, terms or privacy."
19078
- }),
19079
- Object.freeze({ id: "spend-or-sign-up", words: "Anything that costs money or signs up for a service." }),
19080
- Object.freeze({ id: "publish-or-release", words: "Publishing or releasing to users." }),
19081
- Object.freeze({ id: "reshape-shared-data", words: "Dropping or reshaping stored data that other parts read." }),
19082
- Object.freeze({ id: "change-standing-rule", words: "Changing a standing rule." })
19083
- ]);
19084
- var OWNER_WAIT_ITEM_IDS = Object.freeze(OWNER_WAIT_ITEMS.map((item) => item.id));
19085
- var ownerWaitItemIdSchema = external_exports.enum(OWNER_WAIT_ITEM_IDS);
19086
- var OWNER_WAIT_FIXED_COUNT = OWNER_WAIT_ITEMS.length;
19087
- var OWNER_WAIT_ADDITIONS_MAX = 20;
19088
- var OWNER_WAIT_TEXT_MAX = 240;
19089
- var ownerWaitItemRefSchema = external_exports.union([
19090
- external_exports.number().int().min(1),
19091
- external_exports.string().uuid({ message: "must be a number from 1 to 6, or the id shown beside an added item" })
19092
- ]);
19093
- var ownerWaitText = descriptionText(OWNER_WAIT_TEXT_MAX);
19094
- var ownerWaitAddRequestSchema = external_exports.object({
19095
- engagementId: external_exports.string().min(1),
19096
- text: ownerWaitText,
19097
- reason: ownerWaitText
19049
+ // ../events/dist/plan-usage.js
19050
+ var windowPercentSchema = external_exports.number().min(0).max(100);
19051
+ var planUsageWindowSchema = external_exports.object({
19052
+ usedPercent: windowPercentSchema,
19053
+ resetsAt: utcIso8601
19098
19054
  }).strict();
19099
- var ownerWaitAddResultSchema = external_exports.discriminatedUnion("outcome", [
19100
- external_exports.object({ outcome: external_exports.literal("added"), id: external_exports.string().uuid(), addedAt: utcIso8601 }).strict(),
19101
- external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.literal("too-many") }).strict()
19102
- ]);
19103
- var ownerWaitUndoableSchema = external_exports.enum(["yes", "no"]);
19104
- var ownerWaitDeclareRequestSchema = external_exports.object({
19105
- engagementId: external_exports.string().min(1),
19106
- item: ownerWaitItemRefSchema,
19107
- question: ownerWaitText,
19108
- recommendation: ownerWaitText,
19109
- task: buildTaskIdSchema.optional(),
19110
- by: agentNameSchema.optional(),
19111
- undoable: ownerWaitUndoableSchema.optional(),
19112
- how: statementText(STATEMENT_MAX).optional()
19113
- }).strict().refine((r) => r.undoable === "yes" === (r.how !== void 0), {
19114
- message: "says how to undo it exactly when it can be undone",
19115
- path: ["how"]
19116
- });
19117
- var OWNER_WAIT_DECLARE_REFUSALS = ["no-open-phase", "unknown-item", "say-if-undoable"];
19118
- var ownerWaitDeclareResultSchema = external_exports.discriminatedUnion("outcome", [
19119
- external_exports.object({ outcome: external_exports.literal("recorded"), id: external_exports.string().uuid(), say: external_exports.string().min(1) }).strict(),
19120
- external_exports.object({
19121
- outcome: external_exports.literal("go-ahead"),
19122
- id: external_exports.string().uuid(),
19123
- callId: external_exports.number().int().positive(),
19124
- say: external_exports.string().min(1)
19125
- }).strict(),
19126
- external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.enum(OWNER_WAIT_DECLARE_REFUSALS) }).strict()
19127
- ]);
19128
- var ownerWaitAddedItemSchema = external_exports.object({
19129
- id: external_exports.string().uuid(),
19130
- words: external_exports.string().min(1),
19131
- reason: external_exports.string().min(1),
19132
- addedAt: utcIso8601
19055
+ var planUsageWeekSchema = planUsageWindowSchema.extend({
19056
+ ceilingTokens: external_exports.number().int().positive().nullable()
19133
19057
  }).strict();
19134
- var ownerWaitListSchema = external_exports.object({
19135
- fixed: external_exports.array(external_exports.object({ id: ownerWaitItemIdSchema, words: external_exports.string().min(1) }).strict()).length(OWNER_WAIT_FIXED_COUNT),
19136
- added: external_exports.array(ownerWaitAddedItemSchema).max(OWNER_WAIT_ADDITIONS_MAX)
19058
+ var planUsageReportSchema = external_exports.object({
19059
+ fiveHour: planUsageWindowSchema.nullable(),
19060
+ week: planUsageWeekSchema.nullable(),
19061
+ readAt: utcIso8601
19137
19062
  }).strict();
19138
- var ownerWaitDeclarationSchema = external_exports.object({
19139
- id: external_exports.string().uuid(),
19140
- fixedItem: ownerWaitItemIdSchema.nullable(),
19141
- addition: external_exports.object({ id: external_exports.string().uuid(), words: external_exports.string().min(1) }).strict().nullable(),
19142
- question: external_exports.string().min(1),
19143
- recommendation: external_exports.string().min(1),
19144
- task: buildTaskIdSchema.nullable(),
19145
- declaredAt: utcIso8601
19146
- }).strict().refine((d) => d.fixedItem === null !== (d.addition === null), {
19147
- message: "names exactly one item: a fixed one or an added one",
19148
- path: ["fixedItem"]
19063
+ var planKeepFreePercentSchema = external_exports.number().int().min(0).max(90);
19064
+ var workMinutesSchema = external_exports.number().int().nonnegative().nullable();
19065
+ var planUsagePhaseSchema = external_exports.object({
19066
+ phase: phaseNameSchema,
19067
+ spentPercent: timeSharePercentSchema,
19068
+ expectedPercent: timeSharePercentSchema.optional(),
19069
+ kinds: timeShareKindsSchema
19070
+ }).strict();
19071
+ var PLAN_USAGE_PHASES_MAX = 100;
19072
+ var planUsageUnknownSchema = external_exports.object({
19073
+ state: external_exports.literal("unknown"),
19074
+ windowsRead: external_exports.boolean(),
19075
+ workMinutes: workMinutesSchema,
19076
+ keepFreePercent: planKeepFreePercentSchema
19077
+ }).strict();
19078
+ var planUsageKnownSchema = external_exports.object({
19079
+ state: external_exports.literal("known"),
19080
+ workMinutes: workMinutesSchema,
19081
+ keepFreePercent: planKeepFreePercentSchema,
19082
+ weekCeilingTokens: external_exports.number().int().positive(),
19083
+ fiveHourPercent: windowPercentSchema.nullable(),
19084
+ weekPercent: windowPercentSchema.nullable(),
19085
+ readAt: utcIso8601,
19086
+ phases: external_exports.array(planUsagePhaseSchema).max(PLAN_USAGE_PHASES_MAX)
19087
+ }).strict().superRefine((v, ctx) => {
19088
+ const seen = /* @__PURE__ */ new Set();
19089
+ v.phases.forEach((entry, i) => {
19090
+ if (seen.has(entry.phase))
19091
+ ctx.addIssue({ code: "custom", path: ["phases", i, "phase"], message: "lists one phase twice" });
19092
+ seen.add(entry.phase);
19093
+ });
19149
19094
  });
19150
- var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
19151
- var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off on their settings page in the console first; it cannot be done by typing in chat.`;
19152
-
19153
- // ../events/dist/owner-wait-answer.js
19154
- var OWNER_WAIT_SHORT_ID_LENGTH = 8;
19155
- var ownerWaitShortIdSchema = external_exports.string().regex(/^[0-9a-f]{8}$/);
19156
- function ownerWaitShortIdOf(declarationId) {
19157
- return declarationId.slice(0, OWNER_WAIT_SHORT_ID_LENGTH).toLowerCase();
19095
+ var planUsageSchema = external_exports.discriminatedUnion("state", [planUsageUnknownSchema, planUsageKnownSchema]);
19096
+ function agentWorkMinutesOf(tasks, medianTaskMinutes) {
19097
+ if (tasks === null || tasks.length === 0)
19098
+ return null;
19099
+ const sum2 = tasks.reduce((total, task) => total + taskMinutesOf(task, medianTaskMinutes), 0);
19100
+ return paceDurationRoundedOf(sum2);
19158
19101
  }
19159
- var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
19160
- var ownerWaitAnswerActSchema = external_exports.enum(OWNER_WAIT_ANSWER_ACTS);
19161
- function ownerWaitApproveLineOf(declarationId) {
19162
- return `approve wait ${ownerWaitShortIdOf(declarationId)}`;
19102
+ function weekPercentOf(tokens, weekCeilingTokens) {
19103
+ return weekCeilingTokens > 0 ? tokens / weekCeilingTokens * 100 : 0;
19163
19104
  }
19164
- function ownerWaitDeclineLineOf(declarationId) {
19165
- return `decline wait ${ownerWaitShortIdOf(declarationId)}: <why>`;
19105
+ function planSharePercentWordsOf(percent) {
19106
+ return percent < 0.5 ? "under 1%" : `${String(Math.round(percent))}%`;
19166
19107
  }
19167
- var ownerWaitChatRequestSchema = external_exports.discriminatedUnion("act", [
19168
- external_exports.object({ act: external_exports.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
19169
- external_exports.object({ act: external_exports.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
19170
- ]);
19171
- function bareOutcome3(outcome) {
19172
- return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
19108
+ function aboutShareOf(percent) {
19109
+ return percent < 0.5 ? planSharePercentWordsOf(percent) : `about ${planSharePercentWordsOf(percent)}`;
19173
19110
  }
19174
- var ownerWaitChatResultSchema = external_exports.discriminatedUnion("outcome", [
19175
- external_exports.object({
19176
- outcome: external_exports.literal("recorded"),
19177
- act: ownerWaitAnswerActSchema,
19178
- question: external_exports.string().min(1)
19179
- }).strict(),
19180
- external_exports.object({ outcome: external_exports.literal("refused"), problems: external_exports.array(systemDescriptionProblemSchema).min(1) }).strict(),
19181
- bareOutcome3("not-owner"),
19182
- bareOutcome3("no-owner"),
19183
- bareOutcome3("not-found"),
19184
- bareOutcome3("ambiguous"),
19185
- bareOutcome3("not-offered"),
19186
- bareOutcome3("unavailable")
19187
- ]);
19188
-
19189
- // ../events/dist/pacing.js
19190
- var CAREFUL_START_AGENTS = 3;
19191
- var KEEP_FREE_MAX = 90;
19192
- var keepFreePercentSchema = external_exports.number().int().min(0).max(KEEP_FREE_MAX);
19193
- var KEEP_FREE_DEFAULT = 20;
19194
- var PACE_AGENTS_MAX = 20;
19195
- var FIVE_HOUR_WINDOW_MINUTES = 300;
19196
- var SEVEN_DAY_WINDOW_MINUTES = 10080;
19197
- var PACE_WINDOW_MINUTES_MAX = 525600;
19198
- var MINUTE_KEY = /^([1-9][0-9]{0,6})-minute$/;
19199
- function isPaceWindowKind(value) {
19200
- if (value === "five-hour" || value === "seven-day")
19201
- return true;
19202
- if (typeof value !== "string")
19203
- return false;
19204
- const match = MINUTE_KEY.exec(value);
19205
- if (match === null)
19206
- return false;
19207
- const minutes = Number(match[1]);
19208
- return minutes <= PACE_WINDOW_MINUTES_MAX && minutes !== FIVE_HOUR_WINDOW_MINUTES && minutes !== SEVEN_DAY_WINDOW_MINUTES;
19111
+ var PLAN_USAGE_CAVEAT = "These figures are estimates. Many things affect usage, and some of them are outside Halfcycle's control.";
19112
+ function ofYourWeek(tokens, weekCeilingTokens) {
19113
+ return `${aboutShareOf(weekPercentOf(tokens, weekCeilingTokens))} of your week`;
19209
19114
  }
19210
- var paceWindowKindSchema = external_exports.custom((value) => isPaceWindowKind(value), {
19211
- message: "not a usage window key"
19212
- });
19213
- function paceWindowMinutesOf(kind) {
19214
- if (kind === "five-hour")
19215
- return FIVE_HOUR_WINDOW_MINUTES;
19216
- if (kind === "seven-day")
19217
- return SEVEN_DAY_WINDOW_MINUTES;
19218
- return Number(MINUTE_KEY.exec(kind)?.[1] ?? 0);
19115
+ function ceilingKnown(weekCeilingTokens) {
19116
+ return typeof weekCeilingTokens === "number" && weekCeilingTokens > 0;
19219
19117
  }
19220
- function paceWindowKindOf(minutes) {
19221
- if (!Number.isInteger(minutes) || minutes < 1 || minutes > PACE_WINDOW_MINUTES_MAX)
19222
- return null;
19223
- if (minutes === FIVE_HOUR_WINDOW_MINUTES)
19224
- return "five-hour";
19225
- if (minutes === SEVEN_DAY_WINDOW_MINUTES)
19226
- return "seven-day";
19227
- return `${String(minutes)}-minute`;
19118
+ function figureThenCaveat(sentence) {
19119
+ return `${sentence} ${PLAN_USAGE_CAVEAT}`;
19228
19120
  }
19229
- function paceWindowWordsOf(kind) {
19230
- const minutes = paceWindowMinutesOf(kind);
19231
- if (minutes === FIVE_HOUR_WINDOW_MINUTES)
19232
- return "5-hour window";
19233
- if (minutes === SEVEN_DAY_WINDOW_MINUTES)
19234
- return "weekly window";
19235
- if (minutes % 1440 === 0)
19236
- return `${String(minutes / 1440)}-day window`;
19237
- if (minutes % 60 === 0)
19238
- return `${String(minutes / 60)}-hour window`;
19239
- return `${String(minutes)}-minute window`;
19121
+ function limitPartShareTextOf(part, used, weekCeilingTokens) {
19122
+ const words = part === "half" ? "half" : part === "three-quarters" ? "three quarters" : "nine tenths";
19123
+ const head = ceilingKnown(weekCeilingTokens) ? figureThenCaveat(`This phase has used ${words} of its usage limit: ${ofYourWeek(used, weekCeilingTokens)}.`) : `This phase has used ${words} of its usage limit.`;
19124
+ return part === "nine-tenths" ? `${head} New work stops at the limit.` : head;
19240
19125
  }
19241
- function paceShareWordsOf(keepFreePercent) {
19242
- return keepFreePercent === 20 ? "a fifth" : `${String(keepFreePercent)}%`;
19126
+ function limitReachedFigureOf(limit, weekCeilingTokens) {
19127
+ if (!ceilingKnown(weekCeilingTokens))
19128
+ return { figure: "", caveat: "" };
19129
+ return { figure: `: ${ofYourWeek(limit, weekCeilingTokens)}`, caveat: ` ${PLAN_USAGE_CAVEAT}` };
19243
19130
  }
19244
- var PACE_STATES = ["full", "slowing", "holding"];
19245
- var paceStateSchema = external_exports.enum(PACE_STATES);
19246
- var PACE_SHARE_GIVEN_IN = ["chat", "agent", "relayed"];
19247
- var paceShareGivenInSchema = external_exports.enum(PACE_SHARE_GIVEN_IN);
19248
- var paceShareRequestSchema = external_exports.object({ keepFreePercent: keepFreePercentSchema, givenIn: paceShareGivenInSchema }).strict();
19249
- var paceShareResultSchema = external_exports.discriminatedUnion("outcome", [
19250
- external_exports.object({
19251
- outcome: external_exports.literal("recorded"),
19252
- keepFreePercent: keepFreePercentSchema,
19253
- moreAllowance: external_exports.boolean().optional()
19254
- }).strict(),
19255
- external_exports.object({ outcome: external_exports.literal("needs-the-person"), keepFreePercent: keepFreePercentSchema }).strict(),
19256
- external_exports.object({ outcome: external_exports.literal("not-owner") }).strict(),
19257
- external_exports.object({ outcome: external_exports.literal("no-owner") }).strict(),
19258
- external_exports.object({ outcome: external_exports.literal("unavailable") }).strict()
19259
- ]);
19260
- var pacingBasisTaskSchema = external_exports.object({
19261
- id: buildTaskIdSchema,
19262
- dependsOn: external_exports.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
19263
- budgetMinutes: external_exports.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
19264
- human: external_exports.boolean(),
19265
- kind: buildTaskKindSchema.optional()
19266
- }).strict();
19267
- var pacingBasisSchema = external_exports.object({
19268
- outcome: external_exports.literal("basis"),
19269
- claudePlan: external_exports.string().nullable().optional(),
19270
- keepFreePercent: keepFreePercentSchema,
19271
- settingsUrl: external_exports.string().optional(),
19272
- phase: phaseNameSchema.nullable(),
19273
- tasks: external_exports.array(pacingBasisTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
19274
- medianTaskMinutes: external_exports.number().nonnegative().nullable(),
19275
- medianTaskTokens: external_exports.number().nonnegative().nullable()
19276
- }).strict();
19277
- var paceSuggestionSchema = external_exports.object({
19278
- number: external_exports.number().int().min(1).max(PACE_AGENTS_MAX),
19279
- minutes: external_exports.number().int().nonnegative().nullable(),
19280
- pauses: external_exports.number().int().nonnegative(),
19281
- keepFreePercent: keepFreePercentSchema,
19282
- knownWindow: paceWindowKindSchema.nullable(),
19283
- expectedTokens: external_exports.number().int().nonnegative().optional(),
19284
- limitTokens: external_exports.number().int().positive().optional(),
19285
- multiple: external_exports.number().positive().nullable().optional(),
19286
- firstGuess: external_exports.boolean().optional(),
19287
- limitSetBy: external_exports.enum(["default", "person", "other"]).optional()
19288
- }).strict();
19289
- var PACE_NOT_PACED_REASONS = ["not-configured", "unreadable"];
19290
- var limitHeldSchema = external_exports.object({
19291
- used: external_exports.number().int().nonnegative(),
19292
- limit: external_exports.number().int().positive(),
19293
- raiseBy: external_exports.number().int().nonnegative(),
19294
- working: external_exports.number().int().nonnegative()
19295
- }).strict();
19296
- var paceUsageSchema = external_exports.object({
19297
- used: external_exports.number().int().nonnegative(),
19298
- limit: external_exports.number().int().positive()
19299
- }).strict();
19300
- var paceRunResultSchema = external_exports.discriminatedUnion("outcome", [
19301
- external_exports.object({
19302
- outcome: external_exports.literal("paced"),
19303
- pace: paceStateSchema,
19304
- number: external_exports.number().int().min(1).max(PACE_AGENTS_MAX),
19305
- allowedNow: external_exports.number().int().nonnegative(),
19306
- windowKind: paceWindowKindSchema.optional(),
19307
- until: utcIso8601.optional(),
19308
- keepFreePercent: keepFreePercentSchema.optional(),
19309
- suggestion: paceSuggestionSchema.optional(),
19310
- limit: external_exports.literal("reached").optional(),
19311
- limitHeld: limitHeldSchema.optional(),
19312
- usage: paceUsageSchema.optional()
19313
- }).strict(),
19314
- external_exports.object({ outcome: external_exports.literal("not-paced"), reason: external_exports.enum(PACE_NOT_PACED_REASONS) }).strict()
19315
- ]);
19316
- function tokenCountWordsOf(n) {
19317
- if (n < 1e3)
19318
- return String(Math.max(0, Math.round(n)));
19319
- const rounded = Number(n.toPrecision(2));
19320
- const [unit, word] = rounded >= 1e9 ? [1e9, "billion"] : rounded >= 1e6 ? [1e6, "million"] : [1e3, "thousand"];
19321
- return `${String(Number((rounded / unit).toPrecision(2)))} ${word}`;
19131
+ function limitKeepGoingShareText(limit, used, weekCeilingTokens) {
19132
+ if (!ceilingKnown(weekCeilingTokens))
19133
+ return "Done. The limit is raised. New work can start again.";
19134
+ return `${figureThenCaveat(`Done. The limit is now ${ofYourWeek(limit, weekCeilingTokens)}.`)} This phase has used ${aboutShareOf(weekPercentOf(used, weekCeilingTokens))}. New work can start again.`;
19322
19135
  }
19323
- function paceAgentsOf(n) {
19324
- return n === 1 ? "1 agent" : `${String(n)} agents`;
19136
+ function limitAlreadyUsedShareSuffix(limit, used, weekCeilingTokens) {
19137
+ if (limit > used)
19138
+ return "";
19139
+ return ceilingKnown(weekCeilingTokens) ? ` This phase has already used ${ofYourWeek(used, weekCeilingTokens)}, so no new work will start until you raise it.` : " This phase has already used that much, so no new work will start until you raise it.";
19325
19140
  }
19326
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
19327
- var MONTH_NAMES = [
19328
- "January",
19329
- "February",
19330
- "March",
19331
- "April",
19332
- "May",
19333
- "June",
19334
- "July",
19335
- "August",
19336
- "September",
19337
- "October",
19338
- "November",
19339
- "December"
19340
- ];
19341
- var SIX_DAYS_MS = 6 * 24 * 60 * 60 * 1e3;
19342
- function paceClockOf(at, now) {
19343
- const hours = at.getHours();
19344
- const clock = `${String(hours % 12 === 0 ? 12 : hours % 12)}:${String(at.getMinutes()).padStart(2, "0")} ${hours < 12 ? "am" : "pm"}`;
19345
- const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate();
19346
- if (sameDay)
19347
- return clock;
19348
- const day = DAY_NAMES[at.getDay()] ?? "";
19349
- if (at.getTime() - now.getTime() > SIX_DAYS_MS) {
19350
- return `${day} ${String(at.getDate())} ${MONTH_NAMES[at.getMonth()] ?? ""} ${clock}`;
19141
+ function limitTimesShareText(limit, typed, used, weekCeilingTokens) {
19142
+ const suffix = limitAlreadyUsedShareSuffix(limit, used, weekCeilingTokens);
19143
+ if (!ceilingKnown(weekCeilingTokens)) {
19144
+ return `Done. The limit for this phase is now ${typed} times what it is expected to use.${suffix}`;
19351
19145
  }
19352
- return `${day} ${clock}`;
19353
- }
19354
- var PACE_P5 = "This build may use all of your allowance. It will not slow down to leave room for you.";
19355
- function paceS1Of(window, k, until, now, keepFreePercent) {
19356
- return `Halfcycle is slowing this build so you keep about ${paceShareWordsOf(keepFreePercent)} of this ${paceWindowWordsOf(window)} for your own work. It will run at most ${paceAgentsOf(k)} at once until ${paceClockOf(until, now)}.`;
19357
- }
19358
- function paceS2Of(window, k, until, now, keepFreePercent) {
19359
- const head = `Halfcycle has paused new work in this build until ${paceClockOf(until, now)}, when your ${paceWindowWordsOf(window)} resets, so you keep about ${paceShareWordsOf(keepFreePercent)} of it for your own work.`;
19360
- if (k === 0)
19361
- return head;
19362
- return k === 1 ? `${head} The 1 agent already working will finish its task.` : `${head} The ${String(k)} agents already working will finish their tasks.`;
19363
- }
19364
- function paceS3Of(window, n) {
19365
- return `Your ${paceWindowWordsOf(window)} has reset. Halfcycle is starting new work again, up to ${paceAgentsOf(n)} at once.`;
19366
- }
19367
- function paceOfferTextOf(input) {
19368
- const canFaster = input.number < PACE_AGENTS_MAX;
19369
- const canUseMore = input.keepFreePercent > 0;
19370
- if (!canFaster && !canUseMore)
19371
- return null;
19372
- const head = `You have used only ${String(Math.floor(input.percentUsed))}% of this ${paceWindowWordsOf(input.window)}, and more than half of it has passed.`;
19373
- const useUpTo = `"use up to ${String(Math.min(100, 100 - input.keepFreePercent + 10))}%" to let this build use more of it`;
19374
- if (canFaster && canUseMore)
19375
- return `${head} Say "faster" for one more agent, or ${useUpTo}.`;
19376
- if (canFaster)
19377
- return `${head} Say "faster" for one more agent.`;
19378
- return `${head} Say ${useUpTo}.`;
19146
+ return `${figureThenCaveat(`Done. The limit for this phase is now ${ofYourWeek(limit, weekCeilingTokens)}.`)} That is ${typed} times what it is expected to use.${suffix}`;
19379
19147
  }
19380
- function paceF1Of(k) {
19381
- return `This build runs at most ${paceAgentsOf(k)} at once.`;
19148
+ function limitFigureShareText(limit, used, weekCeilingTokens) {
19149
+ const suffix = limitAlreadyUsedShareSuffix(limit, used, weekCeilingTokens);
19150
+ if (!ceilingKnown(weekCeilingTokens))
19151
+ return `Done. The limit for this phase is set.${suffix}`;
19152
+ return `${figureThenCaveat(`Done. The limit for this phase is now ${ofYourWeek(limit, weekCeilingTokens)}.`)}${suffix}`;
19382
19153
  }
19383
- function paceAgentLineOf(pace, allowance, until, now) {
19384
- const fewer = `once fewer than ${paceAgentsOf(allowance)} are working.`;
19385
- if (until === null)
19386
- return `Start it ${fewer}`;
19387
- if (pace === "holding")
19388
- return `Start it after ${paceClockOf(until, now)}.`;
19389
- return `Start it after ${paceClockOf(until, now)}, or ${fewer}`;
19154
+ function limitHeldShareTextOf(limit, weekCeilingTokens) {
19155
+ if (!ceilingKnown(weekCeilingTokens))
19156
+ return "This phase has reached its usage limit, so no new agent starts.";
19157
+ return figureThenCaveat(`This phase has reached its usage limit of ${ofYourWeek(limit, weekCeilingTokens)}, so no new agent starts.`);
19390
19158
  }
19391
- var PACE_NOTHING_CHANGED = "Nothing was changed.";
19392
- function paceHeldStartTextOf(input) {
19393
- const { pace, allowance, working, window, until, now, keepFreePercent } = input;
19394
- if (pace === "full" || window === null || until === null) {
19395
- return `${paceF1Of(allowance)} ${paceAgentLineOf("full", allowance, null, now)} ${PACE_NOTHING_CHANGED}`;
19396
- }
19397
- const head = pace === "slowing" ? paceS1Of(window, allowance, until, now, keepFreePercent) : paceS2Of(window, working, until, now, keepFreePercent);
19398
- return `${head} ${paceAgentLineOf(pace, allowance, until, now)} ${PACE_NOTHING_CHANGED}`;
19159
+ function limitNoExpectedShareText(weekCeilingTokens) {
19160
+ return ceilingKnown(weekCeilingTokens) ? 'Nothing changed: there is no expected usage yet, so say a share of the week, like "limit it to 10% of the week".' : "Nothing changed: there is no expected usage for this phase yet.";
19399
19161
  }
19400
- var PACE_FIRST_READ = "Reading this computer's usage records for the first time. This can take a minute.";
19401
- var PACE_AGENTS_TOO_MANY = `Run at most ${String(PACE_AGENTS_MAX)} agents at once.`;
19402
- var PACE_ONLY_PERSON_RAISES = 'Only the person can raise the number of agents, by typing "faster" or "run 5 agents" in the chat. Nothing was changed.';
19403
- function paceStatusLineOf(percent, window) {
19404
- return `Halfcycle \xB7 ${String(Math.round(percent))}% of this ${paceWindowWordsOf(window)} used`;
19162
+ function limitTimesRangeShareText(range, weekCeilingTokens) {
19163
+ const head = `Nothing changed: a limit is between ${String(range.min)} and ${String(range.max)} times what is expected.`;
19164
+ return ceilingKnown(weekCeilingTokens) ? `${head} Say a share of the week instead, like "limit it to 10% of the week".` : head;
19405
19165
  }
19406
- var STARTING_TOKENS_PER_TASK = 9e6;
19407
- var LIMIT_MULTIPLE_DEFAULT = 2;
19408
- var LIMIT_MULTIPLE_RANGE = { min: 1, max: 20 };
19409
- var LIMIT_FIGURE_RANGE = { min: 1e6, max: 1e11 };
19410
- var LIMIT_HISTORY_MAX = 20;
19411
- var LIMIT_ROUND_TOKENS = 1e3;
19412
- var LIMIT_COUNTED_KINDS = ["build", "review", "check"];
19413
- var LIMIT_PARTS = ["half", "three-quarters", "nine-tenths", "limit"];
19414
- var limitPartSchema = external_exports.enum(LIMIT_PARTS);
19415
- var LIMIT_PART_SHARE = {
19416
- half: 0.5,
19417
- "three-quarters": 0.75,
19418
- "nine-tenths": 0.9,
19419
- limit: 1
19166
+ var LIMIT_SHARE_NOT_SET_TEXT = 'Nothing changed. To set a limit now, say "limit it to 2 times".';
19167
+
19168
+ // ../events/dist/client-version.js
19169
+ var CLIENT_VERSION_HEADER = "x-halfcycle-client";
19170
+ var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
19171
+ var HEADLESS_CANNOT_APPROVE = "(a headless session cannot approve, by design)";
19172
+ var TYPED_REVIEW_OFF_SENTENCE = "Reviewing by typing is switched off for now.";
19173
+ var CLIENT_UPDATE_SENTENCE = `To approve by typing in a session, Halfcycle needs updating: run \`${HALFCYCLE_COMMAND}\` in this project's folder, then reopen your session and ask again.`;
19174
+
19175
+ // ../events/dist/device-auth.js
19176
+ var utcIso86012 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
19177
+ var DEVICE_AUTH_STATUS = {
19178
+ /** The poll: minted, nobody has acted yet. Keep polling. 200. */
19179
+ PENDING: "pending",
19180
+ /** The poll: approved, and the credential is in this body. Once only. 200. */
19181
+ APPROVED: "approved",
19182
+ /** Confirm: the decision was recorded (approve or decline). 200. */
19183
+ RECORDED: "recorded",
19184
+ /** The human refused at the confirmation page. 400 on the poll. */
19185
+ DECLINED: "declined",
19186
+ /** The code's absolute expiry has passed. 400. */
19187
+ EXPIRED: "expired",
19188
+ /** Never minted, or already spent. 400. */
19189
+ UNKNOWN: "unknown",
19190
+ /** Confirm: neither `approve` nor `decline` was named. Nothing was bound. 400. */
19191
+ INVALID_DECISION: "invalid-decision",
19192
+ /** Confirm: an approval arrived with no signed-in identity. 400. */
19193
+ IDENTITY_REQUIRED: "identity-required",
19194
+ /** This deployment has no `SIGNIN_BASE_URL` / `DEVICE_AUTH_CONFIRM_SECRET`. 503. */
19195
+ NOT_CONFIGURED: "not-configured",
19196
+ /** Confirm: the shared confirmation secret was missing or wrong. 401. */
19197
+ UNAUTHORIZED: "unauthorized",
19198
+ /** Confirm: the code was already approved, declined or claimed. 409. */
19199
+ ALREADY_DECIDED: "already-decided",
19200
+ /**
19201
+ * Confirm: an approval named a signed-in identity whose account has not accepted
19202
+ * the terms of service and privacy policy currently in force. 400.
19203
+ *
19204
+ * NOT BOUND TO THE DEVICE CODE. Unlike every other refusal in this const, this one
19205
+ * says nothing about the code itself — the code is still `pending` after this
19206
+ * response, exactly as it was before the request, so the SAME userCode can be
19207
+ * confirmed again once the caller has recorded acceptance. `deviceConfirmRequestSchema`'s
19208
+ * `acceptTerms` is how a second confirm says it did.
19209
+ */
19210
+ TERMS_REQUIRED: "terms-required"
19420
19211
  };
19421
- var LIMIT_SET_BY = ["default", "person", "other"];
19422
- var limitSetBySchema = external_exports.enum(LIMIT_SET_BY);
19423
- var LIMIT_TYPED_BY = ["person", "not-attended", "cannot-tell"];
19424
- var limitTypedBySchema = external_exports.enum(LIMIT_TYPED_BY);
19425
- var limitHistoryEntrySchema = external_exports.object({
19426
- at: utcIso8601,
19427
- kind: external_exports.enum(["raised", "set", "stopped"]),
19428
- limit: external_exports.number().int().positive(),
19429
- by: limitTypedBySchema,
19430
- relayed: external_exports.literal(true).optional()
19212
+ var DEVICE_POLL_REFUSALS = [
19213
+ DEVICE_AUTH_STATUS.DECLINED,
19214
+ DEVICE_AUTH_STATUS.EXPIRED,
19215
+ DEVICE_AUTH_STATUS.UNKNOWN
19216
+ ];
19217
+ var deviceAuthStartResponseSchema = external_exports.object({
19218
+ deviceCode: external_exports.string().min(1),
19219
+ userCode: external_exports.string().min(1),
19220
+ verificationUrl: external_exports.string().min(1),
19221
+ expiresAt: utcIso86012,
19222
+ pollIntervalMs: external_exports.number().int().positive()
19431
19223
  }).strict();
19432
- var PHASE_LIMIT_FILENAME = "limit.json";
19433
- var phaseLimitFileSchema = external_exports.object({
19434
- phase: phaseNameSchema,
19435
- expected: external_exports.number().int().nonnegative(),
19436
- expectedMinutes: external_exports.number().nonnegative().nullable(),
19437
- limit: external_exports.number().int().positive(),
19438
- multiple: external_exports.number().positive().nullable(),
19439
- setBy: limitSetBySchema,
19440
- firstGuess: external_exports.boolean(),
19441
- shown: external_exports.array(limitPartSchema).max(LIMIT_PARTS.length),
19442
- awaiting: external_exports.boolean(),
19443
- history: external_exports.array(limitHistoryEntrySchema).max(LIMIT_HISTORY_MAX)
19224
+ var devicePollPendingSchema = external_exports.object({
19225
+ status: external_exports.literal(DEVICE_AUTH_STATUS.PENDING),
19226
+ pollIntervalMs: external_exports.number().int().positive()
19444
19227
  }).strict();
19445
- function phaseLimitFileOf(raw, phase) {
19446
- const parsed = phaseLimitFileSchema.safeParse(raw);
19447
- if (!parsed.success)
19448
- return null;
19449
- return phase === null || parsed.data.phase === phase ? parsed.data : null;
19228
+ var devicePollApprovedSchema = external_exports.object({
19229
+ status: external_exports.literal(DEVICE_AUTH_STATUS.APPROVED),
19230
+ accountId: external_exports.string().min(1),
19231
+ credential: external_exports.string().min(1),
19232
+ expiresAt: utcIso86012
19233
+ }).strict();
19234
+ var deviceAuthRefusalSchema = external_exports.object({
19235
+ status: external_exports.enum([
19236
+ DEVICE_AUTH_STATUS.DECLINED,
19237
+ DEVICE_AUTH_STATUS.EXPIRED,
19238
+ DEVICE_AUTH_STATUS.UNKNOWN,
19239
+ DEVICE_AUTH_STATUS.INVALID_DECISION,
19240
+ DEVICE_AUTH_STATUS.IDENTITY_REQUIRED,
19241
+ DEVICE_AUTH_STATUS.NOT_CONFIGURED,
19242
+ DEVICE_AUTH_STATUS.UNAUTHORIZED,
19243
+ DEVICE_AUTH_STATUS.ALREADY_DECIDED,
19244
+ DEVICE_AUTH_STATUS.TERMS_REQUIRED
19245
+ ]),
19246
+ message: external_exports.string().min(1)
19247
+ }).strict();
19248
+ var devicePollResponseSchema = external_exports.union([
19249
+ devicePollPendingSchema,
19250
+ devicePollApprovedSchema,
19251
+ deviceAuthRefusalSchema
19252
+ ]);
19253
+ var deviceConfirmRequestSchema = external_exports.object({
19254
+ userCode: external_exports.string().min(1),
19255
+ decision: external_exports.enum(["approve", "decline"]),
19256
+ externalAuthId: external_exports.string().min(1).optional(),
19257
+ email: external_exports.string().optional(),
19258
+ acceptTerms: external_exports.boolean().optional()
19259
+ }).strict();
19260
+ var deviceConfirmRecordedSchema = external_exports.object({
19261
+ status: external_exports.literal(DEVICE_AUTH_STATUS.RECORDED),
19262
+ accountId: external_exports.string().min(1).optional()
19263
+ }).strict();
19264
+ var deviceConfirmResponseSchema = external_exports.union([
19265
+ deviceConfirmRecordedSchema,
19266
+ deviceAuthRefusalSchema
19267
+ ]);
19268
+ var DASHBOARD_URL = "https://app.halfcycle.ai";
19269
+
19270
+ // ../events/dist/project-page.js
19271
+ function projectPageUrl(engagementId) {
19272
+ return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
19273
+ }
19274
+ function systemViewUrl(engagementId, phase) {
19275
+ const page = projectPageUrl(engagementId);
19276
+ if (page === void 0)
19277
+ return void 0;
19278
+ return `${page}/console/system-view?${new URLSearchParams({ phase }).toString()}`;
19279
+ }
19280
+ function requestAccessSentence(engagementId) {
19281
+ const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
19282
+ return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
19283
+ }
19284
+
19285
+ // ../events/dist/scope-notices.js
19286
+ var scopePartNameSchema = external_exports.string().min(1).max(SYSTEM_PART_NAME_MAX);
19287
+ var scopeNoteEntrySchema = external_exports.object({
19288
+ part: scopePartNameSchema.optional(),
19289
+ note: external_exports.string().min(1).max(SCOPE_NOTE_MAX)
19290
+ }).strict();
19291
+ var scopeSentBackSchema = external_exports.object({
19292
+ phase: scopePhaseSchema,
19293
+ number: external_exports.number().int().min(1),
19294
+ notes: external_exports.array(scopeNoteEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX + 1)
19295
+ }).strict().superRefine((entry, ctx) => {
19296
+ entry.notes.forEach((n, i) => {
19297
+ if (n.part === void 0 && i !== entry.notes.length - 1) {
19298
+ ctx.addIssue({
19299
+ code: external_exports.ZodIssueCode.custom,
19300
+ path: ["notes", i],
19301
+ message: "the note on the whole phase comes after every note on a part"
19302
+ });
19303
+ }
19304
+ });
19305
+ });
19306
+ var scopeWaitingSchema = external_exports.object({
19307
+ phase: scopePhaseSchema,
19308
+ number: external_exports.number().int().min(1),
19309
+ changedParts: external_exports.array(scopePartNameSchema).max(PHASE_PLAN_CHANGES_MAX)
19310
+ }).strict();
19311
+ var scopeNoticesSchema = external_exports.object({
19312
+ sentBack: external_exports.array(scopeSentBackSchema),
19313
+ waiting: external_exports.array(scopeWaitingSchema),
19314
+ typedApprovalOffered: external_exports.boolean()
19315
+ }).strict().refine((v) => v.sentBack.length > 0 || v.waiting.length > 0, {
19316
+ message: "carried only when a phase was sent back or waits for the owner"
19317
+ });
19318
+ var scopeApprovedNoticeSchema = external_exports.object({ phase: scopePhaseSchema }).strict();
19319
+ var scopeApprovedNoticesSchema = external_exports.array(scopeApprovedNoticeSchema);
19320
+ function approvedInConsoleSentence(phase) {
19321
+ return `Scope for ${phaseWords(phase)} was approved in the console; continuing. Say so to the person, then carry on with the next step; they do not need to tell you.`;
19322
+ }
19323
+ function quotedParts(parts) {
19324
+ return parts.map((p) => `"${p}"`).join(", ");
19325
+ }
19326
+ function approvalSentence(phase, offerTyping) {
19327
+ if (offerTyping === "off")
19328
+ return TYPED_APPROVAL_OFF_SENTENCE;
19329
+ if (offerTyping === "update")
19330
+ return CLIENT_UPDATE_SENTENCE;
19331
+ return `In their own interactive session ${HEADLESS_CANNOT_APPROVE}, they ask to see the plan, check each part, and then type: approve ${phase}.`;
19332
+ }
19333
+ function phaseWords(phase) {
19334
+ return `phase "${phase}"`;
19335
+ }
19336
+ function sentBackNotice(entry) {
19337
+ return [
19338
+ `Before anything else: the project's owner sent the scope of ${phaseWords(entry.phase)} back with these notes.`,
19339
+ ...entry.notes.map((n) => n.part === void 0 ? `- ${n.note}` : `- "${n.part}": ${n.note}`),
19340
+ `Change the plan to answer every note, then send it again: say "plan the next phase" to start. It then waits for the owner's approval again.`
19341
+ ].join("\n");
19342
+ }
19343
+ function systemViewSentence(offerTyping, address) {
19344
+ if (address === void 0)
19345
+ return [];
19346
+ return [offerTyping === "yes" ? `Or they approve it in the System view: ${address}` : `They approve it in the System view: ${address}`];
19347
+ }
19348
+ function waitingNotice(entry, offerTyping, engagementId) {
19349
+ return [
19350
+ ...entry.changedParts.length > 0 ? [`These parts changed since the owner last decided on this phase, and wait for their approval: ${quotedParts(entry.changedParts)}.`] : [],
19351
+ `The scope of ${phaseWords(entry.phase)} is waiting for the project's owner to approve it.`,
19352
+ approvalSentence(entry.phase, offerTyping),
19353
+ ...systemViewSentence(offerTyping, systemViewUrl(engagementId, entry.phase))
19354
+ ].join(" ");
19355
+ }
19356
+ function scopeNoticeText(scope, options) {
19357
+ const omit2 = options.omitActivePhase === true ? options.activePhase : void 0;
19358
+ return {
19359
+ sentBack: scope.sentBack.map(sentBackNotice),
19360
+ waiting: scope.waiting.filter((w) => omit2 === void 0 || w.phase !== omit2).map((w) => waitingNotice(w, options.offerTyping, options.engagementId))
19361
+ };
19362
+ }
19363
+
19364
+ // ../events/dist/review-notices.js
19365
+ var reviewNoticeQuestionSchema = external_exports.object({
19366
+ phase: scopePhaseSchema,
19367
+ number: reviewPhaseNumberSchema,
19368
+ part: systemPartIdSchema,
19369
+ partName: systemPartSchema.shape.name,
19370
+ question: reviewNoteSchema
19371
+ }).strict();
19372
+ var reviewNoticeSentBackSchema = external_exports.object({
19373
+ phase: scopePhaseSchema,
19374
+ number: reviewPhaseNumberSchema,
19375
+ part: systemPartIdSchema,
19376
+ partName: systemPartSchema.shape.name,
19377
+ note: reviewNoteSchema
19378
+ }).strict();
19379
+ var reviewNoticesSchema = external_exports.object({
19380
+ questions: external_exports.array(reviewNoticeQuestionSchema).max(REVIEW_PARTS_MAX),
19381
+ sentBack: external_exports.array(reviewNoticeSentBackSchema).max(REVIEW_PARTS_MAX)
19382
+ }).strict().refine((v) => v.questions.length > 0 || v.sentBack.length > 0, {
19383
+ message: "carried only when a question waits or a part was sent back"
19384
+ });
19385
+ function phaseCalled(phase) {
19386
+ return `phase "${phase}"`;
19387
+ }
19388
+ function questionNotice(entry) {
19389
+ const answer = `${HALFCYCLE_COMMAND} review ${quoteForShell(entry.phase)} --answer ${quoteForShell(entry.part)} "<your answer>"`;
19390
+ return `Before anything else: the project's owner asked about "${entry.partName}" in the review of ${phaseCalled(entry.phase)}: ${entry.question} Answer it with ${answer}.`;
19391
+ }
19392
+ function sentBackNotice2(entry) {
19393
+ return `Before anything else: the project's owner sent "${entry.partName}" in ${phaseCalled(entry.phase)} back to building: ${entry.note} Rebuild it: add or reopen the tasks that build it. For each task that rebuilds it, add a task that reviews the rebuild, held by a different agent from the one that built it. When each review has passed, report the build with the time each task finished, then run the close step again.`;
19394
+ }
19395
+ function reviewNoticeText(review) {
19396
+ return [...review.questions.map(questionNotice), ...review.sentBack.map(sentBackNotice2)];
19397
+ }
19398
+
19399
+ // ../events/dist/credential-wire.js
19400
+ var engagementCredentialResponseSchema = external_exports.object({
19401
+ engagementId: external_exports.string(),
19402
+ sessionToken: external_exports.string(),
19403
+ mcpUrl: external_exports.string(),
19404
+ guardUrl: external_exports.string(),
19405
+ controlTelemetryUrl: external_exports.string(),
19406
+ methodVersion: external_exports.string().optional(),
19407
+ consoleUrl: external_exports.string().optional()
19408
+ }).strict();
19409
+ var ciTokenExchangeResponseSchema = external_exports.object({
19410
+ token: external_exports.string(),
19411
+ expiresAt: utcIso8601,
19412
+ engagementId: external_exports.string()
19413
+ }).strict();
19414
+ var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
19415
+ var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
19416
+ var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
19417
+ var CREDENTIAL_PLACEHOLDERS = [
19418
+ NO_CREDENTIAL_PLACEHOLDER,
19419
+ NOT_THIS_ACCOUNT_PLACEHOLDER,
19420
+ UPDATE_CLAUDE_CODE_PLACEHOLDER
19421
+ ];
19422
+ var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
19423
+ 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.';
19424
+ 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}.`;
19425
+ 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.";
19426
+ var CANNOT_REACH_SENTENCE = "Halfcycle is not connected: its server could not be reached, so its tools are unavailable in this session. Check your network, then reopen the project.";
19427
+ var SERVICE_PROBLEM_SENTENCE = "Halfcycle is not connected: its service had a problem just now, so nothing in this session is being checked. Reopen the project to try again.";
19428
+ var FOLDER_NOT_TRUSTED_SENTENCE = "Folder not yet trusted. Open this folder once in interactive Claude Code and trust it; then headless runs work.";
19429
+ var SCOPE_UNCHECKED_SENTENCE = "If this project needs approval before building, that cannot be checked either, so work done now shows as built without approval unless already approved.";
19430
+ var PLACEHOLDER_SENTENCES = Object.freeze({
19431
+ [NO_CREDENTIAL_PLACEHOLDER]: NO_CREDENTIAL_SENTENCE,
19432
+ [NOT_THIS_ACCOUNT_PLACEHOLDER]: NOT_THIS_ACCOUNT_SENTENCE,
19433
+ [UPDATE_CLAUDE_CODE_PLACEHOLDER]: UPDATE_CLAUDE_CODE_SENTENCE
19434
+ });
19435
+
19436
+ // ../events/dist/scope-chat.js
19437
+ var chatScopeApproveRequestSchema = external_exports.object({
19438
+ act: external_exports.literal("approve"),
19439
+ reference: phaseIdentitySchema,
19440
+ planVersion: planVersionSchema
19441
+ }).strict();
19442
+ var chatScopeSendBackRequestSchema = external_exports.object({
19443
+ act: external_exports.literal("send-back"),
19444
+ reference: phaseIdentitySchema,
19445
+ planVersion: planVersionSchema,
19446
+ note: scopeNoteWireSchema.optional()
19447
+ }).strict();
19448
+ var chatScopeRequestSchema = external_exports.discriminatedUnion("act", [
19449
+ chatScopeApproveRequestSchema,
19450
+ chatScopeSendBackRequestSchema
19451
+ ]);
19452
+ var CHAT_SCOPE_ACTS = SCOPE_ACTS;
19453
+ var chatScopeRecordedSchema = external_exports.object({
19454
+ outcome: external_exports.literal("recorded"),
19455
+ act: external_exports.enum(CHAT_SCOPE_ACTS),
19456
+ phase: scopePhaseSchema,
19457
+ planVersion: planVersionSchema
19458
+ }).strict();
19459
+ var chatScopeAlreadyApprovedSchema = external_exports.object({
19460
+ outcome: external_exports.literal("already-approved"),
19461
+ replanNeeded: external_exports.boolean()
19462
+ }).strict();
19463
+ var chatScopeRefusedSchema = external_exports.object({
19464
+ outcome: external_exports.literal("refused"),
19465
+ problems: external_exports.array(systemDescriptionProblemSchema).min(1)
19466
+ }).strict();
19467
+ var chatAmbiguousSchema = external_exports.object({
19468
+ outcome: external_exports.literal("ambiguous"),
19469
+ phases: external_exports.array(scopePhaseSchema).min(2)
19470
+ }).strict();
19471
+ function bareOutcome(outcome) {
19472
+ return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
19473
+ }
19474
+ var chatScopeResultSchema = external_exports.discriminatedUnion("outcome", [
19475
+ chatScopeRecordedSchema,
19476
+ chatScopeAlreadyApprovedSchema,
19477
+ chatScopeRefusedSchema,
19478
+ chatAmbiguousSchema,
19479
+ bareOutcome("show-first"),
19480
+ bareOutcome("plan-changed"),
19481
+ bareOutcome("build-started"),
19482
+ bareOutcome("not-owner"),
19483
+ bareOutcome("no-owner"),
19484
+ bareOutcome("not-offered"),
19485
+ bareOutcome("unavailable")
19486
+ ]);
19487
+
19488
+ // ../events/dist/review-chat.js
19489
+ var chatReviewAcceptRequestSchema = external_exports.object({
19490
+ act: external_exports.literal("accept"),
19491
+ reference: phaseIdentitySchema,
19492
+ reviewVersion: external_exports.number().int().min(0),
19493
+ parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
19494
+ waitingClaims: external_exports.literal(true).optional()
19495
+ }).strict();
19496
+ function notedRequest(act) {
19497
+ return external_exports.object({
19498
+ act: external_exports.literal(act),
19499
+ reference: phaseIdentitySchema,
19500
+ reviewVersion: external_exports.number().int().min(0),
19501
+ part: systemPartIdSchema,
19502
+ note: reviewNoteWireSchema
19503
+ }).strict();
19504
+ }
19505
+ var chatReviewQuestionRequestSchema = notedRequest("question");
19506
+ var chatReviewSendBackRequestSchema = notedRequest("send-back");
19507
+ var chatReviewCarryRequestSchema = notedRequest("carry");
19508
+ var chatReviewDropRequestSchema = notedRequest("drop");
19509
+ var chatReviewRejectRequestSchema = external_exports.object({
19510
+ act: external_exports.literal("reject"),
19511
+ reference: phaseIdentitySchema,
19512
+ reviewVersion: external_exports.number().int().min(0),
19513
+ task: buildTaskIdSchema,
19514
+ note: reviewNoteWireSchema
19515
+ }).strict();
19516
+ var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
19517
+ chatReviewAcceptRequestSchema,
19518
+ chatReviewQuestionRequestSchema,
19519
+ chatReviewSendBackRequestSchema,
19520
+ chatReviewCarryRequestSchema,
19521
+ chatReviewDropRequestSchema,
19522
+ chatReviewRejectRequestSchema
19523
+ ]);
19524
+ function bareOutcome2(outcome) {
19525
+ return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
19526
+ }
19527
+ function notTheConsoleNotDecidable(option) {
19528
+ return option !== reviewNotDecidableSchema;
19529
+ }
19530
+ var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
19531
+ reviewPartsNotDecidableSchema,
19532
+ ...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
19533
+ bareOutcome2("show-first"),
19534
+ bareOutcome2("not-offered"),
19535
+ chatAmbiguousSchema,
19536
+ bareOutcome2("nothing-waiting")
19537
+ ]);
19538
+ var reviewAnswerRequestSchema = external_exports.object({
19539
+ part: systemPartIdSchema,
19540
+ answer: statementText(REVIEW_ANSWER_MAX)
19541
+ }).strict();
19542
+ var reviewAnswerRecordedSchema = external_exports.object({
19543
+ outcome: external_exports.literal("recorded"),
19544
+ name: systemPartSchema.shape.name
19545
+ }).strict();
19546
+ var reviewAnswerResultSchema = external_exports.discriminatedUnion("outcome", [
19547
+ reviewAnswerRecordedSchema,
19548
+ bareOutcome2("no-question"),
19549
+ reviewRefusedSchema,
19550
+ bareOutcome2("unavailable")
19551
+ ]);
19552
+
19553
+ // ../events/dist/owner-wait-answer.js
19554
+ var OWNER_WAIT_SHORT_ID_LENGTH = 8;
19555
+ var ownerWaitShortIdSchema = external_exports.string().regex(/^[0-9a-f]{8}$/);
19556
+ function ownerWaitShortIdOf(declarationId) {
19557
+ return declarationId.slice(0, OWNER_WAIT_SHORT_ID_LENGTH).toLowerCase();
19450
19558
  }
19451
- var phaseUsageFileSchema = external_exports.object({
19452
- phase: phaseNameSchema,
19453
- openedAt: utcIso8601,
19454
- tokens: external_exports.number().int().nonnegative(),
19455
- at: utcIso8601
19456
- }).strict();
19457
- function limitPartLineOf(part, used, limit) {
19458
- const words = part === "half" ? "half" : part === "three-quarters" ? "three quarters" : "nine tenths";
19459
- const line2 = `This phase has used ${words} of its usage limit: ${tokenCountWordsOf(used)} of ${tokenCountWordsOf(limit)} tokens.`;
19460
- return part === "nine-tenths" ? `${line2} New work stops at the limit.` : line2;
19559
+ var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
19560
+ var ownerWaitAnswerActSchema = external_exports.enum(OWNER_WAIT_ANSWER_ACTS);
19561
+ function ownerWaitApproveLineOf(declarationId) {
19562
+ return `approve wait ${ownerWaitShortIdOf(declarationId)}`;
19461
19563
  }
19462
- function limitReachedLineOf(used, limit, working, raiseBy) {
19463
- const head = `This phase has reached its usage limit: ${tokenCountWordsOf(used)} of ${tokenCountWordsOf(limit)} tokens. No new work will start.`;
19464
- const finish = working === 0 ? "" : working === 1 ? " The 1 agent already working will finish its task." : ` The ${String(working)} agents already working will finish their tasks.`;
19465
- return `${head}${finish} Say "keep going" to raise the limit by about ${tokenCountWordsOf(raiseBy)} tokens and carry on, or "stop" to leave the phase paused here. Nothing is lost either way.`;
19564
+ function ownerWaitDeclineLineOf(declarationId) {
19565
+ return `decline wait ${ownerWaitShortIdOf(declarationId)}: <why>`;
19466
19566
  }
19467
- function limitHeldStartTextOf(limit) {
19468
- return `This phase has reached its usage limit of ${tokenCountWordsOf(limit)} tokens, so no new agent starts. Ask the person to say "keep going" or "stop", and start nothing until they have. ${PACE_NOTHING_CHANGED}`;
19567
+ var ownerWaitChatRequestSchema = external_exports.discriminatedUnion("act", [
19568
+ external_exports.object({ act: external_exports.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
19569
+ external_exports.object({ act: external_exports.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
19570
+ ]);
19571
+ function bareOutcome3(outcome) {
19572
+ return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
19469
19573
  }
19574
+ var ownerWaitChatResultSchema = external_exports.discriminatedUnion("outcome", [
19575
+ external_exports.object({
19576
+ outcome: external_exports.literal("recorded"),
19577
+ act: ownerWaitAnswerActSchema,
19578
+ question: external_exports.string().min(1)
19579
+ }).strict(),
19580
+ external_exports.object({ outcome: external_exports.literal("refused"), problems: external_exports.array(systemDescriptionProblemSchema).min(1) }).strict(),
19581
+ bareOutcome3("not-owner"),
19582
+ bareOutcome3("no-owner"),
19583
+ bareOutcome3("not-found"),
19584
+ bareOutcome3("ambiguous"),
19585
+ bareOutcome3("not-offered"),
19586
+ bareOutcome3("unavailable")
19587
+ ]);
19588
+
19589
+ // ../events/dist/owner-waits.js
19590
+ var OWNER_WAIT_ITEMS = Object.freeze([
19591
+ Object.freeze({ id: "delete-users-data", words: "Deleting or replacing data people already have." }),
19592
+ Object.freeze({
19593
+ id: "change-promises",
19594
+ words: "Changing what the product promises its users, such as pricing, terms or privacy."
19595
+ }),
19596
+ Object.freeze({ id: "spend-or-sign-up", words: "Anything that costs money or signs up for a service." }),
19597
+ Object.freeze({ id: "publish-or-release", words: "Publishing or releasing to users." }),
19598
+ Object.freeze({ id: "reshape-shared-data", words: "Dropping or reshaping stored data that other parts read." }),
19599
+ Object.freeze({ id: "change-standing-rule", words: "Changing a standing rule." })
19600
+ ]);
19601
+ var OWNER_WAIT_ITEM_IDS = Object.freeze(OWNER_WAIT_ITEMS.map((item) => item.id));
19602
+ var ownerWaitItemIdSchema = external_exports.enum(OWNER_WAIT_ITEM_IDS);
19603
+ var OWNER_WAIT_FIXED_COUNT = OWNER_WAIT_ITEMS.length;
19604
+ var OWNER_WAIT_ADDITIONS_MAX = 20;
19605
+ var OWNER_WAIT_TEXT_MAX = 240;
19606
+ var ownerWaitItemRefSchema = external_exports.union([
19607
+ external_exports.number().int().min(1),
19608
+ external_exports.string().uuid({ message: "must be a number from 1 to 6, or the id shown beside an added item" })
19609
+ ]);
19610
+ var ownerWaitText = descriptionText(OWNER_WAIT_TEXT_MAX);
19611
+ var ownerWaitAddRequestSchema = external_exports.object({
19612
+ engagementId: external_exports.string().min(1),
19613
+ text: ownerWaitText,
19614
+ reason: ownerWaitText
19615
+ }).strict();
19616
+ var ownerWaitAddResultSchema = external_exports.discriminatedUnion("outcome", [
19617
+ external_exports.object({ outcome: external_exports.literal("added"), id: external_exports.string().uuid(), addedAt: utcIso8601 }).strict(),
19618
+ external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.literal("too-many") }).strict()
19619
+ ]);
19620
+ var ownerWaitUndoableSchema = external_exports.enum(["yes", "no"]);
19621
+ var ownerWaitDeclareRequestSchema = external_exports.object({
19622
+ engagementId: external_exports.string().min(1),
19623
+ item: ownerWaitItemRefSchema,
19624
+ question: ownerWaitText,
19625
+ recommendation: ownerWaitText,
19626
+ task: buildTaskIdSchema.optional(),
19627
+ by: agentNameSchema.optional(),
19628
+ undoable: ownerWaitUndoableSchema.optional(),
19629
+ how: statementText(STATEMENT_MAX).optional()
19630
+ }).strict().refine((r) => r.undoable === "yes" === (r.how !== void 0), {
19631
+ message: "says how to undo it exactly when it can be undone",
19632
+ path: ["how"]
19633
+ });
19634
+ var OWNER_WAIT_DECLARE_REFUSALS = ["no-open-phase", "unknown-item", "say-if-undoable"];
19635
+ var ownerWaitDeclareResultSchema = external_exports.discriminatedUnion("outcome", [
19636
+ external_exports.object({ outcome: external_exports.literal("recorded"), id: external_exports.string().uuid(), say: external_exports.string().min(1) }).strict(),
19637
+ external_exports.object({
19638
+ outcome: external_exports.literal("go-ahead"),
19639
+ id: external_exports.string().uuid(),
19640
+ callId: external_exports.number().int().positive(),
19641
+ say: external_exports.string().min(1)
19642
+ }).strict(),
19643
+ external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.enum(OWNER_WAIT_DECLARE_REFUSALS) }).strict()
19644
+ ]);
19645
+ var ownerWaitAddedItemSchema = external_exports.object({
19646
+ id: external_exports.string().uuid(),
19647
+ words: external_exports.string().min(1),
19648
+ reason: external_exports.string().min(1),
19649
+ addedAt: utcIso8601
19650
+ }).strict();
19651
+ var ownerWaitListSchema = external_exports.object({
19652
+ fixed: external_exports.array(external_exports.object({ id: ownerWaitItemIdSchema, words: external_exports.string().min(1) }).strict()).length(OWNER_WAIT_FIXED_COUNT),
19653
+ added: external_exports.array(ownerWaitAddedItemSchema).max(OWNER_WAIT_ADDITIONS_MAX)
19654
+ }).strict();
19655
+ var ownerWaitDeclarationSchema = external_exports.object({
19656
+ id: external_exports.string().uuid(),
19657
+ fixedItem: ownerWaitItemIdSchema.nullable(),
19658
+ addition: external_exports.object({ id: external_exports.string().uuid(), words: external_exports.string().min(1) }).strict().nullable(),
19659
+ question: external_exports.string().min(1),
19660
+ recommendation: external_exports.string().min(1),
19661
+ task: buildTaskIdSchema.nullable(),
19662
+ declaredAt: utcIso8601
19663
+ }).strict().refine((d) => d.fixedItem === null !== (d.addition === null), {
19664
+ message: "names exactly one item: a fixed one or an added one",
19665
+ path: ["fixedItem"]
19666
+ });
19667
+ var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
19668
+ var ownerWaitOpenSchema = external_exports.object({
19669
+ wait: ownerWaitShortIdSchema,
19670
+ task: buildTaskIdSchema.nullable(),
19671
+ question: external_exports.string().min(1),
19672
+ recommendation: external_exports.string().min(1),
19673
+ declaredAt: utcIso8601
19674
+ }).strict();
19675
+ var ownerWaitsOpenSchema = external_exports.object({ waits: external_exports.array(ownerWaitOpenSchema) }).strict();
19676
+ var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off on their settings page in the console first; it cannot be done by typing in chat.`;
19470
19677
 
19471
19678
  // ../events/dist/autonomy.js
19472
19679
  var AUTONOMY_CHANGED_IN = ["console", "chat"];
@@ -21650,6 +21857,110 @@ function toAstShapes(markers) {
21650
21857
  // dist/extract/context.js
21651
21858
  import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs";
21652
21859
  import { join as join4, relative as relative2, sep } from "node:path";
21860
+
21861
+ // dist/extract/repo-facts.js
21862
+ var DATABASE_FILE = /\.sql$|^schema\.prisma$|^drizzle\.config\.|^alembic\.ini$|^knexfile\./i;
21863
+ var BROWSER_FILE = /\.(tsx|jsx|vue|svelte|astro)$|^index\.html$|^(next|vite|nuxt|svelte|astro)\.config\.|^angular\.json$/i;
21864
+ var WEBHOOK_NAME = /webhook/i;
21865
+ var DATABASE_DEPS = /* @__PURE__ */ new Set([
21866
+ "pg",
21867
+ "mysql",
21868
+ "mysql2",
21869
+ "mongodb",
21870
+ "mongoose",
21871
+ "sqlite3",
21872
+ "better-sqlite3",
21873
+ "prisma",
21874
+ "@prisma/client",
21875
+ "drizzle-orm",
21876
+ "typeorm",
21877
+ "sequelize",
21878
+ "knex",
21879
+ "postgres",
21880
+ "psycopg",
21881
+ "psycopg2",
21882
+ "sqlalchemy",
21883
+ "pymysql",
21884
+ "asyncpg",
21885
+ "pymongo",
21886
+ "github.com/lib/pq",
21887
+ "github.com/jackc/pgx",
21888
+ "github.com/go-sql-driver/mysql",
21889
+ "gorm.io/gorm",
21890
+ "diesel",
21891
+ "sqlx",
21892
+ "activerecord"
21893
+ ]);
21894
+ var BROWSER_DEPS = /* @__PURE__ */ new Set([
21895
+ "react",
21896
+ "react-dom",
21897
+ "next",
21898
+ "vue",
21899
+ "nuxt",
21900
+ "svelte",
21901
+ "@sveltejs/kit",
21902
+ "@angular/core",
21903
+ "solid-js",
21904
+ "preact",
21905
+ "astro",
21906
+ "vite"
21907
+ ]);
21908
+ var MANIFEST_NAMES = /* @__PURE__ */ new Set(["package.json", "go.mod", "requirements.txt", "pyproject.toml", "Gemfile", "Cargo.toml"]);
21909
+ function isFactManifest(fileName) {
21910
+ return MANIFEST_NAMES.has(fileName);
21911
+ }
21912
+ function newFactCollector() {
21913
+ return { found: /* @__PURE__ */ new Set() };
21914
+ }
21915
+ function noteFileName(c, fileName, underDocs) {
21916
+ if (underDocs)
21917
+ return;
21918
+ if (DATABASE_FILE.test(fileName))
21919
+ c.found.add("database");
21920
+ if (BROWSER_FILE.test(fileName))
21921
+ c.found.add("browser-app");
21922
+ if (WEBHOOK_NAME.test(fileName))
21923
+ c.found.add("inbound-webhooks");
21924
+ }
21925
+ function noteDirName(c, dirName, underDocs) {
21926
+ if (underDocs)
21927
+ return;
21928
+ if (WEBHOOK_NAME.test(dirName))
21929
+ c.found.add("inbound-webhooks");
21930
+ if (dirName === "migrations")
21931
+ c.found.add("database");
21932
+ }
21933
+ function dependencyNames(fileName, content) {
21934
+ if (fileName === "package.json") {
21935
+ try {
21936
+ const parsed = JSON.parse(content);
21937
+ const names = [];
21938
+ for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
21939
+ const block = parsed[key];
21940
+ if (block !== null && typeof block === "object")
21941
+ names.push(...Object.keys(block));
21942
+ }
21943
+ return names;
21944
+ } catch {
21945
+ return [];
21946
+ }
21947
+ }
21948
+ return content.split(/[\s"'=<>~^,;:\[\]()]+/).filter((t) => t.length > 0);
21949
+ }
21950
+ function noteManifest(c, fileName, content) {
21951
+ for (const name of dependencyNames(fileName, content)) {
21952
+ const lower = name.toLowerCase();
21953
+ if (DATABASE_DEPS.has(lower))
21954
+ c.found.add("database");
21955
+ if (BROWSER_DEPS.has(lower))
21956
+ c.found.add("browser-app");
21957
+ }
21958
+ }
21959
+ function factsOf(c) {
21960
+ return REPOSITORY_FACTS.filter((f) => c.found.has(f));
21961
+ }
21962
+
21963
+ // dist/extract/context.js
21653
21964
  var EXCLUDED_DIRS2 = /* @__PURE__ */ new Set([
21654
21965
  "node_modules",
21655
21966
  ".git",
@@ -21663,6 +21974,7 @@ var EXCLUDED_DIRS2 = /* @__PURE__ */ new Set([
21663
21974
  "coverage",
21664
21975
  ".cache"
21665
21976
  ]);
21977
+ var PROSE_DIRS = /* @__PURE__ */ new Set(["docs", "skills", "runbooks", "method"]);
21666
21978
  function classifyFile(fileName) {
21667
21979
  if (fileName === "Dockerfile" || fileName.startsWith("Dockerfile."))
21668
21980
  return "dockerfile";
@@ -21672,7 +21984,7 @@ function classifyFile(fileName) {
21672
21984
  return "env-example";
21673
21985
  return null;
21674
21986
  }
21675
- function walk(dir, repoRoot, acc) {
21987
+ function walk(dir, repoRoot, acc, facts, underDocs = false) {
21676
21988
  let entries;
21677
21989
  try {
21678
21990
  entries = readdirSync2(dir, { withFileTypes: true });
@@ -21683,11 +21995,21 @@ function walk(dir, repoRoot, acc) {
21683
21995
  if (entry.isDirectory()) {
21684
21996
  if (EXCLUDED_DIRS2.has(entry.name))
21685
21997
  continue;
21686
- walk(join4(dir, entry.name), repoRoot, acc);
21998
+ const lower = entry.name.toLowerCase();
21999
+ noteDirName(facts, lower, underDocs);
22000
+ walk(join4(dir, entry.name), repoRoot, acc, facts, underDocs || PROSE_DIRS.has(lower));
21687
22001
  continue;
21688
22002
  }
21689
22003
  if (!entry.isFile())
21690
22004
  continue;
22005
+ noteFileName(facts, entry.name, underDocs);
22006
+ if (!underDocs && isFactManifest(entry.name)) {
22007
+ try {
22008
+ noteManifest(facts, entry.name, readFileSync4(join4(dir, entry.name), "utf-8"));
22009
+ } catch {
22010
+ continue;
22011
+ }
22012
+ }
21691
22013
  const kind = classifyFile(entry.name);
21692
22014
  if (!kind)
21693
22015
  continue;
@@ -21707,8 +22029,9 @@ function loadChangeSetContext(repoRoot) {
21707
22029
  if (cached2)
21708
22030
  return cached2;
21709
22031
  const declarationFiles = [];
21710
- walk(repoRoot, repoRoot, declarationFiles);
21711
- const context = { declarationFiles, repoRoot };
22032
+ const facts = newFactCollector();
22033
+ walk(repoRoot, repoRoot, declarationFiles, facts);
22034
+ const context = { declarationFiles, repoRoot, repoFacts: factsOf(facts) };
21712
22035
  cache.set(repoRoot, context);
21713
22036
  return context;
21714
22037
  }
@@ -21803,6 +22126,7 @@ function buildGuardEvalRun(opts) {
21803
22126
  ...opts.failureReason !== void 0 ? { failureReason: opts.failureReason } : {},
21804
22127
  phase: opts.phase,
21805
22128
  scope,
22129
+ ...opts.repoFacts !== void 0 ? { repoFacts: [...opts.repoFacts] } : {},
21806
22130
  // cost is optional (D6: live cost signal lands later; ingest works without it)
21807
22131
  // result present IFF the run was evaluated (schema-enforced refinement).
21808
22132
  ...opts.outcome === "evaluated" && opts.result !== void 0 ? { result: opts.result } : {}
@@ -21822,7 +22146,8 @@ async function emitRunRecord(opts) {
21822
22146
  outcome: opts.outcome,
21823
22147
  failureReason: opts.failureReason,
21824
22148
  changeSet: opts.changeSet,
21825
- diffBase: opts.diffBase
22149
+ diffBase: opts.diffBase,
22150
+ repoFacts: opts.repoFacts
21826
22151
  });
21827
22152
  await emitTelemetry(run, {
21828
22153
  controlTelemetryUrl: telemetryConfig.controlTelemetryUrl,
@@ -21888,7 +22213,8 @@ async function runCi() {
21888
22213
  runType: "ci",
21889
22214
  outcome: acquired.kind === "contract" ? "contract-error" : "infra-error",
21890
22215
  failureReason: `ci credential: ${acquired.kind}: ${acquired.message}`,
21891
- changeSet
22216
+ changeSet,
22217
+ repoFacts: context.repoFacts
21892
22218
  });
21893
22219
  process.stderr.write(credentialExchangeFailureMessage(acquired));
21894
22220
  return 1;
@@ -21903,7 +22229,7 @@ async function runCi() {
21903
22229
  }
21904
22230
  }
21905
22231
  if (!configResult.config) {
21906
- await emitRunRecord({ runType: "ci", outcome: "unconfigured", changeSet });
22232
+ await emitRunRecord({ runType: "ci", outcome: "unconfigured", changeSet, repoFacts: context.repoFacts });
21907
22233
  process.stderr.write(isHalfcycleOwnCi() ? ciNeverConfiguredMessage() : CI_NOT_SUPPORTED_MESSAGE);
21908
22234
  return 1;
21909
22235
  }
@@ -21920,6 +22246,7 @@ async function evaluateAndAct(args2) {
21920
22246
  outcome: outcomeForClientFailure(clientResult.kind),
21921
22247
  failureReason: failure.reason,
21922
22248
  changeSet,
22249
+ repoFacts: context.repoFacts,
21923
22250
  credentialRefused: failure.kind === "credential"
21924
22251
  });
21925
22252
  if (failure.kind === "credential") {
@@ -21946,6 +22273,7 @@ async function evaluateAndAct(args2) {
21946
22273
  phase: telemetryConfig.phase,
21947
22274
  outcome: "evaluated",
21948
22275
  changeSet,
22276
+ repoFacts: context.repoFacts,
21949
22277
  result: clientResult.envelope
21950
22278
  });
21951
22279
  await emitTelemetry(run, {
@@ -24311,19 +24639,19 @@ function settled(dir, file2, tokens) {
24311
24639
  writePhaseLimit(dir, next);
24312
24640
  return next;
24313
24641
  }
24314
- function turnEndLimitLine(file2, tokens, working) {
24642
+ function turnEndLimitLine(file2, tokens, working, weekCeilingTokens = null) {
24315
24643
  if (file2.awaiting) {
24316
24644
  if (file2.shown.includes("limit"))
24317
24645
  return { text: null, file: file2 };
24318
24646
  return {
24319
- text: limitReachedLineOf(tokens, file2.limit, working, estimateOf(file2)),
24647
+ text: limitReachedLineOf({ limit: file2.limit, working, weekCeilingTokens }),
24320
24648
  file: { ...file2, shown: [...LIMIT_PARTS] }
24321
24649
  };
24322
24650
  }
24323
24651
  const r = partsReached(tokens, file2.limit, file2.shown);
24324
24652
  if (r.show === null)
24325
24653
  return { text: null, file: file2 };
24326
- return { text: limitPartLineOf(r.show, tokens, file2.limit), file: { ...file2, shown: r.shown } };
24654
+ return { text: limitPartLineOf(r.show, tokens, weekCeilingTokens), file: { ...file2, shown: r.shown } };
24327
24655
  }
24328
24656
  function openPhaseLimit(dir, remembered) {
24329
24657
  if (remembered !== null && remembered.phase === null)
@@ -26562,7 +26890,7 @@ async function evaluateEdit(reply, readSeat, at, profile) {
26562
26890
  });
26563
26891
  const configResult = readConfig();
26564
26892
  if (!configResult.config) {
26565
- await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
26893
+ await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, repoFacts: context.repoFacts });
26566
26894
  if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
26567
26895
  return;
26568
26896
  emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()), reply);
@@ -26586,6 +26914,7 @@ async function evaluateEdit(reply, readSeat, at, profile) {
26586
26914
  outcome: outcomeForClientFailure(clientResult.kind),
26587
26915
  failureReason: failure.reason,
26588
26916
  changeSet,
26917
+ repoFacts: context.repoFacts,
26589
26918
  credential: evaluation.token,
26590
26919
  credentialRefused: failure.kind === "credential"
26591
26920
  });
@@ -26620,6 +26949,7 @@ async function evaluateEdit(reply, readSeat, at, profile) {
26620
26949
  phase: telemetryConfig.phase,
26621
26950
  outcome: "evaluated",
26622
26951
  changeSet,
26952
+ repoFacts: context.repoFacts,
26623
26953
  result: clientResult.envelope
26624
26954
  });
26625
26955
  await emitTelemetry(run, {
@@ -26867,29 +27197,171 @@ function runsBesideOtherWork(toolInput, record2) {
26867
27197
  }
26868
27198
 
26869
27199
  // dist/pace-hook.js
26870
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync20, renameSync as renameSync10, unlinkSync as unlinkSync8, writeFileSync as writeFileSync12 } from "node:fs";
26871
- import nodePath5 from "node:path";
27200
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync21, renameSync as renameSync11, unlinkSync as unlinkSync8, writeFileSync as writeFileSync13 } from "node:fs";
27201
+ import nodePath6 from "node:path";
26872
27202
 
26873
27203
  // dist/pace-verb.js
26874
- import { mkdirSync as mkdirSync7, readdirSync as readdirSync8, readFileSync as readFileSync19, renameSync as renameSync9, statSync as statSync6, writeFileSync as writeFileSync11 } from "node:fs";
27204
+ import { mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync20, renameSync as renameSync10, statSync as statSync6, writeFileSync as writeFileSync12 } from "node:fs";
26875
27205
  import { homedir as homedir9 } from "node:os";
27206
+ import nodePath5 from "node:path";
27207
+
27208
+ // dist/plan-report.js
27209
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync11 } from "node:fs";
26876
27210
  import nodePath4 from "node:path";
26877
- var PACE_DEFAULT_TASK_MINUTES = 30;
26878
- var PACE_DURATION_ROUND_MINUTES = 10;
27211
+ var PLAN_REPORT_MIN_INTERVAL_MS = 10 * 60 * 1e3;
27212
+ var PLAN_MIN_GAIN_POINTS = 2;
27213
+ var PLAN_FIRST_READ_FILENAME = "plan-first-read.json";
27214
+ var PLAN_DERIVED_FILENAME = "plan-derived-ceiling.json";
27215
+ var PLAN_REPORT_THROTTLE_FILENAME = "plan-report.json";
27216
+ function isNum2(v) {
27217
+ return typeof v === "number" && Number.isFinite(v);
27218
+ }
27219
+ function isTime(v) {
27220
+ return typeof v === "string" && !Number.isNaN(Date.parse(v));
27221
+ }
27222
+ function readAccounts(path) {
27223
+ try {
27224
+ const v = JSON.parse(readFileSync19(path, "utf-8"));
27225
+ const accounts = typeof v === "object" && v !== null ? v["accounts"] : void 0;
27226
+ return typeof accounts === "object" && accounts !== null && !Array.isArray(accounts) ? { ...accounts } : {};
27227
+ } catch {
27228
+ return {};
27229
+ }
27230
+ }
27231
+ function writeAccount(path, key, entry) {
27232
+ const accounts = readAccounts(path);
27233
+ accounts[key] = entry;
27234
+ mkdirSync7(nodePath4.dirname(path), { recursive: true, mode: 448 });
27235
+ const tmp = `${path}.${String(process.pid)}.${Math.random().toString(36).slice(2, 10)}.tmp`;
27236
+ writeFileSync11(tmp, JSON.stringify({ accounts }), { mode: 384 });
27237
+ renameSync9(tmp, path);
27238
+ }
27239
+ function firstReadOf(key, home) {
27240
+ const v = readAccounts(nodePath4.join(pacingDir(home), PLAN_FIRST_READ_FILENAME))[key];
27241
+ if (typeof v !== "object" || v === null)
27242
+ return null;
27243
+ const o = v;
27244
+ return isNum2(o["usedPercent"]) && isTime(o["resetsAt"]) && isTime(o["at"]) ? { usedPercent: o["usedPercent"], resetsAt: o["resetsAt"], at: o["at"] } : null;
27245
+ }
27246
+ function derivedOf(key, home) {
27247
+ const v = readAccounts(nodePath4.join(pacingDir(home), PLAN_DERIVED_FILENAME))[key];
27248
+ if (typeof v !== "object" || v === null)
27249
+ return null;
27250
+ const o = v;
27251
+ return isNum2(o["tokens"]) && Number.isInteger(o["tokens"]) && o["tokens"] > 0 && isTime(o["resetsAt"]) ? { tokens: o["tokens"], resetsAt: o["resetsAt"] } : null;
27252
+ }
27253
+ function weeklyCeilingOf(key, deps = {}) {
27254
+ const learned = readLearnedAccount(key, deps)?.ceilings["seven-day"];
27255
+ if (learned !== void 0)
27256
+ return learned.tokens;
27257
+ const week = readReportedAccount(key, deps)["seven-day"];
27258
+ const derived = derivedOf(key, deps.home);
27259
+ if (week === void 0 || derived === null)
27260
+ return null;
27261
+ return Date.parse(derived.resetsAt) === Date.parse(week.resetsAt) ? derived.tokens : null;
27262
+ }
27263
+ function derivedBetweenReads(key, week, deps, nowMs) {
27264
+ const first = firstReadOf(key, deps.home);
27265
+ if (first === null || Date.parse(first.resetsAt) !== Date.parse(week.resetsAt)) {
27266
+ const thisRead = { usedPercent: week.usedPercentage, resetsAt: week.resetsAt, at: week.savedAt };
27267
+ writeAccount(nodePath4.join(pacingDir(deps.home), PLAN_FIRST_READ_FILENAME), key, thisRead);
27268
+ return null;
27269
+ }
27270
+ if (deps.configFolder === "codex-home")
27271
+ return null;
27272
+ const gain = week.usedPercentage - first.usedPercent;
27273
+ if (gain < PLAN_MIN_GAIN_POINTS)
27274
+ return null;
27275
+ if (readLearnedAccount(key, { home: deps.home }) === null)
27276
+ return null;
27277
+ const scanned = scanUnderLock(key, { ...deps.home === void 0 ? {} : { home: deps.home }, now: () => new Date(nowMs) }, 0);
27278
+ if (scanned === null)
27279
+ return null;
27280
+ const fromSec = Math.floor(Date.parse(first.at) / 1e3);
27281
+ const toSec = Math.floor(Date.parse(week.savedAt) / 1e3);
27282
+ if (!(toSec > fromSec) || scanned.coveredFrom > fromSec)
27283
+ return null;
27284
+ const tokens = tokensBetween(scanned.minutes, fromSec, toSec);
27285
+ if (!(tokens > 0))
27286
+ return null;
27287
+ const ceiling = Math.round(tokens * 100 / gain);
27288
+ return ceiling > 0 ? ceiling : null;
27289
+ }
27290
+ async function planUsageReport(input, deps = {}) {
27291
+ try {
27292
+ const code = process.exitCode;
27293
+ if (code !== void 0 && code !== null && Number(code) !== 0)
27294
+ return;
27295
+ const env = deps.env ?? process.env;
27296
+ const configured = readConfig(env);
27297
+ if (configured.config === null)
27298
+ return;
27299
+ const { guardEngagementId: engagementId, guardServiceToken: token } = configured.config;
27300
+ const dir = enginePacingDir(engagementId, deps.home);
27301
+ if (dir === null)
27302
+ return;
27303
+ const nowMs = (deps.now ?? Date.now)();
27304
+ const said = typeof input === "object" && input !== null ? input["transcript_path"] : void 0;
27305
+ const key = configFolderKey({
27306
+ env,
27307
+ transcriptPath: typeof said === "string" && said !== "" ? said : null,
27308
+ home: deps.home,
27309
+ rule: deps.configFolder
27310
+ });
27311
+ const throttlePath = nodePath4.join(dir, PLAN_REPORT_THROTTLE_FILENAME);
27312
+ const last = readAccounts(throttlePath)[key];
27313
+ if (isNum2(last) && nowMs >= last && nowMs - last < PLAN_REPORT_MIN_INTERVAL_MS)
27314
+ return;
27315
+ writeAccount(throttlePath, key, nowMs);
27316
+ const now = new Date(nowMs);
27317
+ if (deps.configFolder === "codex-home")
27318
+ refreshCodexWindows(key, { home: deps.home, now: () => now });
27319
+ const reported = readReportedAccount(key, { home: deps.home });
27320
+ const five = reported["five-hour"];
27321
+ const week = reported["seven-day"];
27322
+ const freshWeek = reportedFresh(week, now) ? week : void 0;
27323
+ let ceiling = null;
27324
+ if (freshWeek !== void 0) {
27325
+ const learned = readLearnedAccount(key, { home: deps.home })?.ceilings["seven-day"];
27326
+ ceiling = weeklyCeilingOf(key, { home: deps.home });
27327
+ let derived = null;
27328
+ if (learned === void 0) {
27329
+ try {
27330
+ derived = derivedBetweenReads(key, freshWeek, deps, nowMs);
27331
+ } catch {
27332
+ derived = null;
27333
+ }
27334
+ }
27335
+ if (derived !== null) {
27336
+ try {
27337
+ writeAccount(nodePath4.join(pacingDir(deps.home), PLAN_DERIVED_FILENAME), key, { tokens: derived, resetsAt: freshWeek.resetsAt });
27338
+ } catch {
27339
+ }
27340
+ ceiling = derived;
27341
+ }
27342
+ }
27343
+ const report = {
27344
+ fiveHour: reportedFresh(five, now) ? { usedPercent: five.usedPercentage, resetsAt: five.resetsAt } : null,
27345
+ week: freshWeek === void 0 ? null : { usedPercent: freshWeek.usedPercentage, resetsAt: freshWeek.resetsAt, ceilingTokens: ceiling },
27346
+ readAt: now.toISOString()
27347
+ };
27348
+ if (!planUsageReportSchema.safeParse(report).success)
27349
+ return;
27350
+ await requestJson(deps.fetchFn ?? globalThis.fetch, `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/plan-usage`, {
27351
+ method: "PUT",
27352
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
27353
+ body: JSON.stringify(report)
27354
+ }, OBSERVE_REQUEST_DEADLINE_MS);
27355
+ } catch {
27356
+ }
27357
+ }
27358
+
27359
+ // dist/pace-verb.js
26879
27360
  var PACE_WINDOW_MINUTES = 300;
26880
27361
  var PACE_BASIS_DEADLINE_MS = 5e3;
26881
27362
  var PACE_FIRST_READ_WAIT_MS = 10 * 60 * 1e3;
26882
27363
  var PACE_LATER_READ_WAIT_MS = 5e3;
26883
27364
  var BUILD_NUMBER_FILENAME = "build.json";
26884
- function taskMinutesOf(task, medianTaskMinutes) {
26885
- if (task.human)
26886
- return 0;
26887
- if (task.budgetMinutes !== void 0)
26888
- return task.budgetMinutes;
26889
- if (medianTaskMinutes !== null && medianTaskMinutes > 0)
26890
- return medianTaskMinutes;
26891
- return PACE_DEFAULT_TASK_MINUTES;
26892
- }
26893
27365
  function layoutGraph(tasks, agents, medianTaskMinutes) {
26894
27366
  const index = /* @__PURE__ */ new Map();
26895
27367
  tasks.forEach((t2, i) => {
@@ -26973,7 +27445,7 @@ function suggestedNumberOf(input) {
26973
27445
  function suggestPace(input) {
26974
27446
  const tasks = input.tasks !== null && input.tasks.length > 0 ? input.tasks : null;
26975
27447
  const number4 = input.named !== null ? clampAgents(input.named, PACE_AGENTS_MAX) : suggestedNumberOf(input);
26976
- const minutes = tasks === null ? null : Math.ceil(layoutGraph(tasks, number4, input.medianTaskMinutes).minutes / PACE_DURATION_ROUND_MINUTES) * PACE_DURATION_ROUND_MINUTES;
27448
+ const minutes = tasks === null ? null : paceDurationRoundedOf(layoutGraph(tasks, number4, input.medianTaskMinutes).minutes);
26977
27449
  let pauses = 0;
26978
27450
  const ceiling = input.learned?.ceilings["five-hour"];
26979
27451
  if (tasks !== null && ceiling !== void 0 && input.medianTaskTokens !== null) {
@@ -26989,20 +27461,32 @@ function suggestPace(input) {
26989
27461
  minutes,
26990
27462
  pauses,
26991
27463
  keepFreePercent: input.keepFreePercent,
26992
- knownWindow
27464
+ knownWindow,
27465
+ workMinutes: agentWorkMinutesOf(tasks, input.medianTaskMinutes)
26993
27466
  };
26994
27467
  }
27468
+ function weekCeilingFor(key, home) {
27469
+ try {
27470
+ const tokens = weeklyCeilingOf(key, home === void 0 ? {} : { home });
27471
+ if (tokens === null || !Number.isFinite(tokens))
27472
+ return null;
27473
+ const whole = Math.round(tokens);
27474
+ return whole > 0 ? whole : null;
27475
+ } catch {
27476
+ return null;
27477
+ }
27478
+ }
26995
27479
  function enginePacingDir(engagementId, home) {
26996
27480
  if (!isEngagementId(engagementId))
26997
27481
  return null;
26998
- return nodePath4.join(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, PACING_DIR_NAME);
27482
+ return nodePath5.join(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, PACING_DIR_NAME);
26999
27483
  }
27000
27484
  function readBuildNumber(engagementId, home) {
27001
27485
  const dir = enginePacingDir(engagementId, home);
27002
27486
  if (dir === null)
27003
27487
  return null;
27004
27488
  try {
27005
- const v = JSON.parse(readFileSync19(nodePath4.join(dir, BUILD_NUMBER_FILENAME), "utf-8"));
27489
+ const v = JSON.parse(readFileSync20(nodePath5.join(dir, BUILD_NUMBER_FILENAME), "utf-8"));
27006
27490
  const n = v["number"];
27007
27491
  if (typeof v["phase"] === "string" && typeof n === "number" && Number.isInteger(n) && n >= 1 && n <= PACE_AGENTS_MAX && typeof v["namedByOwner"] === "boolean") {
27008
27492
  return { phase: v["phase"], number: n, namedByOwner: v["namedByOwner"] };
@@ -27019,11 +27503,11 @@ function writeBuildNumber(engagementId, value, home) {
27019
27503
  const dir = enginePacingDir(engagementId, home);
27020
27504
  if (dir === null)
27021
27505
  throw new Error("not an engagement id");
27022
- mkdirSync7(dir, { recursive: true, mode: 448 });
27023
- const path = nodePath4.join(dir, BUILD_NUMBER_FILENAME);
27506
+ mkdirSync8(dir, { recursive: true, mode: 448 });
27507
+ const path = nodePath5.join(dir, BUILD_NUMBER_FILENAME);
27024
27508
  const tmp = `${path}.${String(process.pid)}.tmp`;
27025
- writeFileSync11(tmp, JSON.stringify(value), { mode: 384 });
27026
- renameSync9(tmp, path);
27509
+ writeFileSync12(tmp, JSON.stringify(value), { mode: 384 });
27510
+ renameSync10(tmp, path);
27027
27511
  }
27028
27512
  function liveSubAgentsIn(cwd, deps = {}) {
27029
27513
  try {
@@ -27039,7 +27523,7 @@ function liveSubAgentsIn(cwd, deps = {}) {
27039
27523
  if (p === null || p.key !== key || p.agentId === null)
27040
27524
  continue;
27041
27525
  try {
27042
- if (now - statSync6(nodePath4.join(dir, name)).mtimeMs <= LIVE_WINDOW_MS)
27526
+ if (now - statSync6(nodePath5.join(dir, name)).mtimeMs <= LIVE_WINDOW_MS)
27043
27527
  n += 1;
27044
27528
  } catch {
27045
27529
  }
@@ -27190,6 +27674,8 @@ async function runPaceVerb(args2, deps = {}) {
27190
27674
  refreshCodexWindows(key, scanDeps);
27191
27675
  const learned = codex ? null : readThisAccount(key, scanDeps, stderr);
27192
27676
  const reported = readReportedAccount(key, scanDeps);
27677
+ const weekCeilingTokens = weekCeilingFor(key, deps.home);
27678
+ const ceiling = weekCeilingTokens === null ? {} : { weekCeilingTokens };
27193
27679
  let number4;
27194
27680
  let suggestion;
27195
27681
  let limitFile = null;
@@ -27212,7 +27698,7 @@ async function runPaceVerb(args2, deps = {}) {
27212
27698
  return { stdout: null, stderr: [...stderr, PACE_ONLY_PERSON_RAISES], code: 2 };
27213
27699
  }
27214
27700
  const named = parsed.agents ?? kept;
27215
- suggestion = named === null ? suggested : suggestFor(named);
27701
+ suggestion = { ...named === null ? suggested : suggestFor(named), ...ceiling };
27216
27702
  number4 = suggestion.number;
27217
27703
  writeBuildNumber(engagementId, { phase: parsed.phase, number: number4, namedByOwner: named !== null }, deps.home);
27218
27704
  const limitBasis = basis === null ? null : { tasks: sameGraph ? basis.tasks : [], medianTaskTokens: basis.medianTaskTokens };
@@ -27266,14 +27752,15 @@ async function runPaceVerb(args2, deps = {}) {
27266
27752
  ...decision.until !== null ? { until: decision.until.toISOString() } : {},
27267
27753
  keepFreePercent,
27268
27754
  ...suggestion !== void 0 ? { suggestion } : {},
27269
- ...limitFile !== null ? { usage: { used: readPhaseUsage(pacingDir2, limitFile.phase), limit: limitFile.limit } } : {},
27755
+ ...limitFile !== null ? { usage: { used: readPhaseUsage(pacingDir2, limitFile.phase), limit: limitFile.limit, ...ceiling } } : {},
27270
27756
  ...atLimit ? {
27271
27757
  limit: "reached",
27272
27758
  limitHeld: {
27273
27759
  used: readPhaseUsage(pacingDir2, limitFile.phase),
27274
27760
  limit: limitFile.limit,
27275
27761
  raiseBy: estimateOf(limitFile),
27276
- working: live
27762
+ working: live,
27763
+ ...ceiling
27277
27764
  }
27278
27765
  } : {}
27279
27766
  };
@@ -27298,17 +27785,17 @@ function objectOf2(v) {
27298
27785
  }
27299
27786
  function readJson(path) {
27300
27787
  try {
27301
- return objectOf2(JSON.parse(readFileSync20(path, "utf-8")));
27788
+ return objectOf2(JSON.parse(readFileSync21(path, "utf-8")));
27302
27789
  } catch {
27303
27790
  return null;
27304
27791
  }
27305
27792
  }
27306
27793
  function writeJsonWhole2(path, value) {
27307
- mkdirSync8(nodePath5.dirname(path), { recursive: true, mode: 448 });
27794
+ mkdirSync9(nodePath6.dirname(path), { recursive: true, mode: 448 });
27308
27795
  const tmp = `${path}.${String(process.pid)}.${Math.random().toString(36).slice(2, 10)}.tmp`;
27309
27796
  try {
27310
- writeFileSync12(tmp, JSON.stringify(value), { mode: 384 });
27311
- renameSync10(tmp, path);
27797
+ writeFileSync13(tmp, JSON.stringify(value), { mode: 384 });
27798
+ renameSync11(tmp, path);
27312
27799
  } catch (err) {
27313
27800
  try {
27314
27801
  unlinkSync8(tmp);
@@ -27421,7 +27908,7 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
27421
27908
  return;
27422
27909
  }
27423
27910
  const working = liveCount(readLiveness(field2(input, "session_id"), field2(input, "cwd") ?? process.cwd(), deps.liveness));
27424
- const limitText = limitAtTurnEnd(dir, basis.phase, read !== null && read.phase === basis.phase ? { tasks: read.tasks, medianTaskTokens: read.medianTaskTokens } : null, working);
27911
+ const limitText = limitAtTurnEnd(dir, basis.phase, read !== null && read.phase === basis.phase ? { tasks: read.tasks, medianTaskTokens: read.medianTaskTokens } : null, working, key, deps.home);
27425
27912
  let learned = null;
27426
27913
  if (deps.configFolder === "codex-home") {
27427
27914
  refreshCodexWindows(key, { home: deps.home, now: () => now });
@@ -27471,13 +27958,13 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
27471
27958
  } catch {
27472
27959
  }
27473
27960
  }
27474
- function limitAtTurnEnd(dir, phase, basis, working) {
27961
+ function limitAtTurnEnd(dir, phase, basis, working, key, home) {
27475
27962
  try {
27476
27963
  const tokens = readPhaseUsage(dir, phase);
27477
27964
  const file2 = limitInForce(dir, phase, basis, tokens);
27478
27965
  if (file2 === null)
27479
27966
  return null;
27480
- const step = turnEndLimitLine(file2, tokens, working);
27967
+ const step = turnEndLimitLine(file2, tokens, working, weekCeilingFor(key, home));
27481
27968
  if (step.text === null)
27482
27969
  return null;
27483
27970
  writePhaseLimit(dir, step.file);
@@ -27488,7 +27975,7 @@ function limitAtTurnEnd(dir, phase, basis, working) {
27488
27975
  }
27489
27976
  function readStarts(dir) {
27490
27977
  const out = {};
27491
- for (const [key, sessions] of Object.entries(objectOf2(readJson(nodePath5.join(dir, PACE_STARTS_FILENAME))?.["accounts"]) ?? {})) {
27978
+ for (const [key, sessions] of Object.entries(objectOf2(readJson(nodePath6.join(dir, PACE_STARTS_FILENAME))?.["accounts"]) ?? {})) {
27492
27979
  const s = {};
27493
27980
  for (const [session, list] of Object.entries(objectOf2(sessions) ?? {})) {
27494
27981
  if (Array.isArray(list))
@@ -27516,7 +28003,7 @@ function sleepSync3(ms) {
27516
28003
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
27517
28004
  }
27518
28005
  function underStartsLock(dir, giveUpMs, fn) {
27519
- const lock = nodePath5.join(dir, PACE_STARTS_LOCK_FILENAME);
28006
+ const lock = nodePath6.join(dir, PACE_STARTS_LOCK_FILENAME);
27520
28007
  const giveUpAt = Date.now() + giveUpMs;
27521
28008
  while (!tryTakeLockFile(lock, PACE_STARTS_LOCK_STALE_MS)) {
27522
28009
  if (Date.now() >= giveUpAt)
@@ -27554,7 +28041,7 @@ function retirePendingStart(input, deps = {}) {
27554
28041
  if (list === void 0 || list.length === 0)
27555
28042
  return false;
27556
28043
  list.shift();
27557
- writeJsonWhole2(nodePath5.join(where.dir, PACE_STARTS_FILENAME), { accounts: pruneStarts(file2, nowMs) });
28044
+ writeJsonWhole2(nodePath6.join(where.dir, PACE_STARTS_FILENAME), { accounts: pruneStarts(file2, nowMs) });
27558
28045
  return true;
27559
28046
  }) ?? false;
27560
28047
  } catch {
@@ -27573,7 +28060,7 @@ function paceHoldText(input, toolInput, deps = {}) {
27573
28060
  const beside = () => runsBesideOtherWork(toolInput, withoutAgent(record2, field2(input, "agent_id")));
27574
28061
  const atLimit = openPhaseLimit(where.dir, stateFile.basis);
27575
28062
  if (atLimit !== null && atLimit.awaiting && beside())
27576
- return limitHeldStartTextOf(atLimit.limit);
28063
+ return limitHeldStartTextOf(atLimit.limit, weekCeilingFor(where.key, deps.home));
27577
28064
  const entry = stateFile.accounts[where.key];
27578
28065
  if (entry === void 0)
27579
28066
  return null;
@@ -27606,7 +28093,7 @@ function paceHoldText(input, toolInput, deps = {}) {
27606
28093
  if (live + list.length >= allowance)
27607
28094
  return true;
27608
28095
  const sessions = { ...file2[where.key] ?? {}, [session]: [...list, nowMs] };
27609
- writeJsonWhole2(nodePath5.join(where.dir, PACE_STARTS_FILENAME), { accounts: { ...file2, [where.key]: sessions } });
28096
+ writeJsonWhole2(nodePath6.join(where.dir, PACE_STARTS_FILENAME), { accounts: { ...file2, [where.key]: sessions } });
27610
28097
  return false;
27611
28098
  });
27612
28099
  return pending === true ? held() : null;
@@ -27617,12 +28104,12 @@ function paceHoldText(input, toolInput, deps = {}) {
27617
28104
 
27618
28105
  // dist/risky/account-names.js
27619
28106
  import { randomBytes } from "node:crypto";
27620
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync22, renameSync as renameSync11, rmSync as rmSync3, statSync as statSync8, writeFileSync as writeFileSync13 } from "node:fs";
28107
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, renameSync as renameSync12, rmSync as rmSync3, statSync as statSync8, writeFileSync as writeFileSync14 } from "node:fs";
27621
28108
  import { homedir as homedir11 } from "node:os";
27622
28109
  import { isAbsolute as isAbsolute5, join as join16, posix as posix2, resolve as resolve7, sep as sep6 } from "node:path";
27623
28110
 
27624
28111
  // dist/risky/facts.js
27625
- import { existsSync as existsSync8, lstatSync as lstatSync3, readdirSync as readdirSync9, readFileSync as readFileSync21, realpathSync as realpathSync7, statSync as statSync7 } from "node:fs";
28112
+ import { existsSync as existsSync8, lstatSync as lstatSync3, readdirSync as readdirSync9, readFileSync as readFileSync22, realpathSync as realpathSync7, statSync as statSync7 } from "node:fs";
27626
28113
  import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
27627
28114
  import { basename as pathBasename, dirname as dirname6, isAbsolute as isAbsolute4, join as join15, relative as relative7, resolve as resolve6, sep as sep5 } from "node:path";
27628
28115
 
@@ -29860,7 +30347,7 @@ var Env = class {
29860
30347
  return this.description;
29861
30348
  this.description = null;
29862
30349
  try {
29863
- const raw = JSON.parse(readFileSync21(join15(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
30350
+ const raw = JSON.parse(readFileSync22(join15(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
29864
30351
  const checked = checkSystemDescription(raw);
29865
30352
  if (checked.ok)
29866
30353
  this.description = checked.description;
@@ -31046,7 +31533,7 @@ function publishFacts(m, env) {
31046
31533
  const start = startFolder(m, env);
31047
31534
  if (start !== null && PACKAGE_JSON_PROGRAMS.has(m.program)) {
31048
31535
  try {
31049
- const pkg = JSON.parse(readFileSync21(join15(start, "package.json"), "utf-8"));
31536
+ const pkg = JSON.parse(readFileSync22(join15(start, "package.json"), "utf-8"));
31050
31537
  if (typeof pkg["name"] === "string" && typeof pkg["version"] === "string" && pkg["name"] !== "" && pkg["version"] !== "") {
31051
31538
  name = pkg["name"];
31052
31539
  version2 = pkg["version"];
@@ -31096,7 +31583,7 @@ function shrinkRecordFacts(m, env) {
31096
31583
  try {
31097
31584
  if (!lstatSync3(file2.abs).isFile())
31098
31585
  continue;
31099
- before = readFileSync21(file2.abs, "utf-8");
31586
+ before = readFileSync22(file2.abs, "utf-8");
31100
31587
  } catch {
31101
31588
  continue;
31102
31589
  }
@@ -31331,7 +31818,7 @@ function canonical(stored) {
31331
31818
  function readSource(path, parse3) {
31332
31819
  let text;
31333
31820
  try {
31334
- text = readFileSync22(path, "utf-8");
31821
+ text = readFileSync23(path, "utf-8");
31335
31822
  } catch (e) {
31336
31823
  const code = e.code;
31337
31824
  return { names: [], broken: code !== "ENOENT" && code !== "ENOTDIR" };
@@ -31360,7 +31847,7 @@ function namesFromList(p) {
31360
31847
  }
31361
31848
  let lines2;
31362
31849
  try {
31363
- lines2 = readFileSync22(p.list, "utf-8").split("\n");
31850
+ lines2 = readFileSync23(p.list, "utf-8").split("\n");
31364
31851
  } catch {
31365
31852
  return null;
31366
31853
  }
@@ -31376,10 +31863,10 @@ function writeList(p, names) {
31376
31863
  const lines2 = [p.firstLine, ...comparable.length === names.length ? [] : [ACCOUNT_NAMES_ANY], ...comparable];
31377
31864
  const temp = `${p.list}.${String(process.pid)}.${randomBytes(4).toString("hex")}`;
31378
31865
  try {
31379
- mkdirSync9(join16(p.home, HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
31380
- writeFileSync13(temp, `${lines2.join("\n")}
31866
+ mkdirSync10(join16(p.home, HALFCYCLE_DIR_NAME), { recursive: true, mode: 448 });
31867
+ writeFileSync14(temp, `${lines2.join("\n")}
31381
31868
  `, { mode: 384, flag: "wx" });
31382
- renameSync11(temp, p.list);
31869
+ renameSync12(temp, p.list);
31383
31870
  } catch {
31384
31871
  try {
31385
31872
  rmSync3(temp, { force: true });
@@ -31438,7 +31925,7 @@ function sumOf(texts, name) {
31438
31925
  }
31439
31926
  function textOf(path) {
31440
31927
  try {
31441
- return readFileSync22(path, "utf-8");
31928
+ return readFileSync23(path, "utf-8");
31442
31929
  } catch {
31443
31930
  return "";
31444
31931
  }
@@ -32061,7 +32548,7 @@ function isOneCall(call) {
32061
32548
 
32062
32549
  // dist/risky/allow-once.js
32063
32550
  import { createHash as createHash5 } from "node:crypto";
32064
- import { appendFileSync as appendFileSync5, readFileSync as readFileSync23, realpathSync as realpathSync8, renameSync as renameSync12, statSync as statSync9, unlinkSync as unlinkSync9, writeFileSync as writeFileSync14 } from "node:fs";
32551
+ import { appendFileSync as appendFileSync5, readFileSync as readFileSync24, realpathSync as realpathSync8, renameSync as renameSync13, statSync as statSync9, unlinkSync as unlinkSync9, writeFileSync as writeFileSync15 } from "node:fs";
32065
32552
  import { isAbsolute as isAbsolute6, relative as relative8, resolve as resolve8, sep as sep7 } from "node:path";
32066
32553
  var ALLOW_ONCE_MS = 5 * 60 * 1e3;
32067
32554
  var HELD_STEP_MAX_AGE_MS = 60 * 60 * 1e3;
@@ -32222,7 +32709,7 @@ function numberKept(path, key) {
32222
32709
  function readNumbers(path) {
32223
32710
  let text;
32224
32711
  try {
32225
- text = readFileSync23(path, "utf8");
32712
+ text = readFileSync24(path, "utf8");
32226
32713
  } catch (err) {
32227
32714
  return err.code === "ENOENT" ? { next: 1, steps: /* @__PURE__ */ new Map() } : null;
32228
32715
  }
@@ -32334,13 +32821,13 @@ function distinctSteps(held, numbersPath) {
32334
32821
  function claimHeld(path, session, now) {
32335
32822
  const claimed = `${path}.${process.pid}.${now}.claim`;
32336
32823
  try {
32337
- renameSync12(path, claimed);
32824
+ renameSync13(path, claimed);
32338
32825
  } catch {
32339
32826
  return [];
32340
32827
  }
32341
32828
  let text = "";
32342
32829
  try {
32343
- text = readFileSync23(claimed, "utf8");
32830
+ text = readFileSync24(claimed, "utf8");
32344
32831
  } catch {
32345
32832
  text = "";
32346
32833
  }
@@ -32395,7 +32882,7 @@ function nowOf(deps) {
32395
32882
  }
32396
32883
  function readRecord2(path, session) {
32397
32884
  try {
32398
- return parseRecord(readFileSync23(path, "utf8"), session);
32885
+ return parseRecord(readFileSync24(path, "utf8"), session);
32399
32886
  } catch {
32400
32887
  return null;
32401
32888
  }
@@ -32433,8 +32920,8 @@ function writeRecord(path, record2) {
32433
32920
  function writeWhole2(path, body) {
32434
32921
  const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
32435
32922
  try {
32436
- writeFileSync14(tmp, body, { mode: 384 });
32437
- renameSync12(tmp, path);
32923
+ writeFileSync15(tmp, body, { mode: 384 });
32924
+ renameSync13(tmp, path);
32438
32925
  return true;
32439
32926
  } catch {
32440
32927
  removeQuietly(tmp);
@@ -32534,7 +33021,7 @@ function decideAutonomous(facts, mayPublishNow) {
32534
33021
  }
32535
33022
 
32536
33023
  // dist/risky/own-copy.js
32537
- import { readFileSync as readFileSync24 } from "node:fs";
33024
+ import { readFileSync as readFileSync25 } from "node:fs";
32538
33025
  import { join as join17, posix as posix3, win32 as win322 } from "node:path";
32539
33026
  var OWN_COPY_START_TOOL = "Agent";
32540
33027
  var OWN_COPY_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
@@ -32556,7 +33043,7 @@ function stringField2(o, key) {
32556
33043
  }
32557
33044
  function readOr(deps, path) {
32558
33045
  try {
32559
- return (deps.readFile ?? ((p) => readFileSync24(p, "utf-8")))(path);
33046
+ return (deps.readFile ?? ((p) => readFileSync25(p, "utf-8")))(path);
32560
33047
  } catch {
32561
33048
  return null;
32562
33049
  }
@@ -32702,7 +33189,7 @@ function judgeChange(input, call, deps) {
32702
33189
 
32703
33190
  // dist/risky/save.js
32704
33191
  import { randomBytes as randomBytes2 } from "node:crypto";
32705
- import { chmodSync as chmodSync2, constants as fsConstants, copyFileSync, lstatSync as lstatSync4, mkdirSync as mkdirSync10, readdirSync as readdirSync10, readFileSync as readFileSync25, readlinkSync, realpathSync as realpathSync9, renameSync as renameSync13, rmSync as rmSync4, statSync as statSync10, symlinkSync, writeFileSync as writeFileSync15 } from "node:fs";
33192
+ import { chmodSync as chmodSync2, constants as fsConstants, copyFileSync, lstatSync as lstatSync4, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync26, readlinkSync, realpathSync as realpathSync9, renameSync as renameSync14, rmSync as rmSync4, statSync as statSync10, symlinkSync, writeFileSync as writeFileSync16 } from "node:fs";
32706
33193
  import { homedir as homedir12 } from "node:os";
32707
33194
  import { dirname as dirname7, isAbsolute as isAbsolute7, join as join18, resolve as resolve9 } from "node:path";
32708
33195
  var SAVE_LIMIT_BYTES = 2e8;
@@ -32748,7 +33235,7 @@ function saveFirst(staked, ctx) {
32748
33235
  const repository = repositoryOf(g);
32749
33236
  if (repository === null)
32750
33237
  return failed(saveDir);
32751
- writeFileSync15(join18(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
33238
+ writeFileSync16(join18(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
32752
33239
  `, { mode: 384 });
32753
33240
  const privateIndex = join18(saveDir, PRIVATE_INDEX_NAME);
32754
33241
  const commit = saveCommit(id, headCommit2, found2.tracked, plan.lines, privateIndex, g, now());
@@ -32774,9 +33261,9 @@ function saveFirst(staked, ctx) {
32774
33261
  if (!description.success)
32775
33262
  return failed(saveDir);
32776
33263
  const file2 = join18(saveDir, SAVED_FILE_NAME);
32777
- writeFileSync15(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
33264
+ writeFileSync16(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
32778
33265
  `, { mode: 384 });
32779
- renameSync13(`${file2}.part`, file2);
33266
+ renameSync14(`${file2}.part`, file2);
32780
33267
  ctx.beforeRef?.();
32781
33268
  const ref = g.run(["update-ref", "--no-deref", savedRefOf(id), commit, ""]);
32782
33269
  if (!ref.ok)
@@ -32896,7 +33383,7 @@ function copyOne(src, dst) {
32896
33383
  const st = lstatOrNull(src);
32897
33384
  if (st === null)
32898
33385
  return false;
32899
- mkdirSync10(dirname7(dst), { recursive: true, mode: 448 });
33386
+ mkdirSync11(dirname7(dst), { recursive: true, mode: 448 });
32900
33387
  if (st.isSymbolicLink()) {
32901
33388
  symlinkSync(readlinkSync(src), dst);
32902
33389
  return true;
@@ -32972,13 +33459,13 @@ var SaveGit = class {
32972
33459
  }
32973
33460
  };
32974
33461
  function freshId(folder2, newId) {
32975
- mkdirSync10(folder2, { recursive: true, mode: 448 });
33462
+ mkdirSync11(folder2, { recursive: true, mode: 448 });
32976
33463
  for (let i = 0; i < 5; i++) {
32977
33464
  const id = newId?.() ?? defaultId();
32978
33465
  if (!savedIdSchema.safeParse(id).success)
32979
33466
  return null;
32980
33467
  try {
32981
- mkdirSync10(join18(folder2, id), { mode: 448 });
33468
+ mkdirSync11(join18(folder2, id), { mode: 448 });
32982
33469
  return id;
32983
33470
  } catch (e) {
32984
33471
  if (e.code !== "EEXIST")
@@ -33003,7 +33490,7 @@ function repositoryOf(g) {
33003
33490
  }
33004
33491
  function markedRepository(dir) {
33005
33492
  try {
33006
- const text = readFileSync25(join18(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
33493
+ const text = readFileSync26(join18(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
33007
33494
  return text === "" ? null : text;
33008
33495
  } catch {
33009
33496
  return null;
@@ -33076,7 +33563,7 @@ function realOrSelf3(p) {
33076
33563
 
33077
33564
  // dist/autonomy-state.js
33078
33565
  import { spawnSync as spawnSync2 } from "node:child_process";
33079
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync11, readFileSync as readFileSync26, renameSync as renameSync14, rmSync as rmSync5, writeFileSync as writeFileSync16 } from "node:fs";
33566
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync15, rmSync as rmSync5, writeFileSync as writeFileSync17 } from "node:fs";
33080
33567
  import { homedir as homedir13, platform as platform2 } from "node:os";
33081
33568
  import { dirname as dirname8, join as join19 } from "node:path";
33082
33569
  var AUTONOMY_STATE_FILENAME = "autonomy.json";
@@ -33088,7 +33575,7 @@ function readCachedAutonomy(engagementId, nowMs, home) {
33088
33575
  if (!isEngagementId(engagementId))
33089
33576
  return null;
33090
33577
  try {
33091
- const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync26(autonomyStateFilePath(engagementId, home), "utf-8")));
33578
+ const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync27(autonomyStateFilePath(engagementId, home), "utf-8")));
33092
33579
  if (!parsed.success || !parsed.data.on)
33093
33580
  return null;
33094
33581
  const age = nowMs - Date.parse(parsed.data.readAt);
@@ -33137,12 +33624,12 @@ function writeAutonomyStateFile(engagementId, file2, home) {
33137
33624
  const target = autonomyStateFilePath(engagementId, home);
33138
33625
  const temp = join19(dirname8(target), `.${AUTONOMY_STATE_FILENAME}.${process.pid}.tmp`);
33139
33626
  try {
33140
- mkdirSync11(dirname8(target), { recursive: true, mode: 448 });
33141
- writeFileSync16(temp, `${JSON.stringify(file2)}
33627
+ mkdirSync12(dirname8(target), { recursive: true, mode: 448 });
33628
+ writeFileSync17(temp, `${JSON.stringify(file2)}
33142
33629
  `, { mode: 384 });
33143
33630
  if (platform2() !== "win32")
33144
33631
  chmodSync3(temp, 384);
33145
- renameSync14(temp, target);
33632
+ renameSync15(temp, target);
33146
33633
  return true;
33147
33634
  } catch {
33148
33635
  try {
@@ -33618,7 +34105,7 @@ async function evaluateSession(input, kind, reply, readSeat, profile) {
33618
34105
  const changeSet = buildChangeSet(fileDiffEntries, { ...context, excerpts: readExcerptConfig() });
33619
34106
  const configResult = readConfig();
33620
34107
  if (!configResult.config) {
33621
- await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
34108
+ await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase, repoFacts: context.repoFacts });
33622
34109
  if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
33623
34110
  return;
33624
34111
  emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()), reply);
@@ -33642,6 +34129,7 @@ async function evaluateSession(input, kind, reply, readSeat, profile) {
33642
34129
  outcome: outcomeForClientFailure(clientResult.kind),
33643
34130
  failureReason: failure.reason,
33644
34131
  changeSet,
34132
+ repoFacts: context.repoFacts,
33645
34133
  diffBase,
33646
34134
  credential: evaluation.token,
33647
34135
  credentialRefused: failure.kind === "credential"
@@ -33675,6 +34163,7 @@ async function evaluateSession(input, kind, reply, readSeat, profile) {
33675
34163
  phase: telemetryConfig.phase,
33676
34164
  outcome: "evaluated",
33677
34165
  changeSet,
34166
+ repoFacts: context.repoFacts,
33678
34167
  result: clientResult.envelope,
33679
34168
  diffBase
33680
34169
  });
@@ -33793,18 +34282,19 @@ function subagentStopped(input, deps = {}) {
33793
34282
  }
33794
34283
 
33795
34284
  // dist/hooks/stop.js
33796
- async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}, profile = AGENT_PROFILES["claude-code"]) {
34285
+ async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}, profile = AGENT_PROFILES["claude-code"], planDeps = {}) {
33797
34286
  const input = hookInputForRule(await readHookStdin(), profile.configFolder);
33798
34287
  recordTimeEvent("turn-end", input);
33799
34288
  await runSessionDiff(input, "stop", readSeatConfiguration, async (reply) => {
33800
34289
  await observeChanges(input.session_id, reply, observeDeps);
33801
34290
  await sendTimeRecord(timeDeps, { agent: profile.id });
33802
34291
  await paceAtTurnEnd(input, reply, { configFolder: profile.configFolder, ...paceDeps });
34292
+ await planUsageReport(input, { configFolder: profile.configFolder, ...planDeps });
33803
34293
  }, profile);
33804
34294
  }
33805
34295
 
33806
34296
  // dist/relayed-choices.js
33807
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync15, rmSync as rmSync6, writeFileSync as writeFileSync17 } from "node:fs";
34297
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync28, renameSync as renameSync16, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "node:fs";
33808
34298
  import { homedir as homedir15 } from "node:os";
33809
34299
  import { dirname as dirname9, join as join21 } from "node:path";
33810
34300
  function relayedChoicesFilePath(engagementId, home) {
@@ -33817,7 +34307,7 @@ function readRelayedChoices(engagementId, phase, home) {
33817
34307
  if (path === null)
33818
34308
  return null;
33819
34309
  try {
33820
- const parsed = relayedChoicesFileSchema.safeParse(JSON.parse(readFileSync27(path, "utf-8")));
34310
+ const parsed = relayedChoicesFileSchema.safeParse(JSON.parse(readFileSync28(path, "utf-8")));
33821
34311
  return parsed.success && parsed.data.phase === phase ? parsed.data : null;
33822
34312
  } catch {
33823
34313
  return null;
@@ -33848,10 +34338,10 @@ function recordRelayedChoice(opts) {
33848
34338
  return false;
33849
34339
  const temp = join21(dirname9(target), `.${RELAYED_CHOICES_FILENAME}.${process.pid}.tmp`);
33850
34340
  try {
33851
- mkdirSync12(dirname9(target), { recursive: true, mode: 448 });
33852
- writeFileSync17(temp, `${JSON.stringify(file2.data)}
34341
+ mkdirSync13(dirname9(target), { recursive: true, mode: 448 });
34342
+ writeFileSync18(temp, `${JSON.stringify(file2.data)}
33853
34343
  `, { mode: 384 });
33854
- renameSync15(temp, target);
34344
+ renameSync16(temp, target);
33855
34345
  return true;
33856
34346
  } catch {
33857
34347
  try {
@@ -33915,6 +34405,7 @@ var KEEP_GOING = /^keep going$/i;
33915
34405
  var STOP = /^stop(?: here)?$/i;
33916
34406
  var TIMES = /^limit it to ([0-9]{1,9}(?:\.[0-9]{1,2})?) times$/i;
33917
34407
  var FIGURE = /^limit it to ([0-9]{1,9}(?:\.[0-9]{1,2})?) (million|billion)(?: tokens)?$/i;
34408
+ var SHARE = /^limit it to ([0-9]{1,3})% of the week$/i;
33918
34409
  function limitPhraseShape(prompt) {
33919
34410
  if (typeof prompt !== "string")
33920
34411
  return null;
@@ -33933,6 +34424,11 @@ function limitPhraseShape(prompt) {
33933
34424
  const unit = figure[2].toLowerCase() === "billion" ? 1e9 : 1e6;
33934
34425
  return { kind: "figure", tokens: Math.round(Number(figure[1]) * unit) };
33935
34426
  }
34427
+ const share = SHARE.exec(line2);
34428
+ if (share !== null) {
34429
+ const percent = Number(share[1]);
34430
+ return percent >= 1 && percent <= 100 ? { kind: "share", percent } : null;
34431
+ }
33936
34432
  return null;
33937
34433
  }
33938
34434
 
@@ -34074,7 +34570,7 @@ function matchReviewPhraseShape(shape, reviews) {
34074
34570
  }
34075
34571
 
34076
34572
  // dist/hooks/shown-record.js
34077
- import { chmodSync as chmodSync4, readFileSync as readFileSync28, renameSync as renameSync16, unlinkSync as unlinkSync11, writeFileSync as writeFileSync18 } from "node:fs";
34573
+ import { chmodSync as chmodSync4, readFileSync as readFileSync29, renameSync as renameSync17, unlinkSync as unlinkSync11, writeFileSync as writeFileSync19 } from "node:fs";
34078
34574
  import { homedir as homedir16, platform as platform3 } from "node:os";
34079
34575
  import { join as join22 } from "node:path";
34080
34576
  function recordDir(engagementId, home) {
@@ -34087,7 +34583,7 @@ function readShownRecord(engagementId, home) {
34087
34583
  if (dir === null)
34088
34584
  return { entries: [], reviews: [] };
34089
34585
  try {
34090
- return parseShownRecord(readFileSync28(join22(dir, SHOWN_PLANS_FILENAME), "utf-8"));
34586
+ return parseShownRecord(readFileSync29(join22(dir, SHOWN_PLANS_FILENAME), "utf-8"));
34091
34587
  } catch {
34092
34588
  return { entries: [], reviews: [] };
34093
34589
  }
@@ -34109,7 +34605,7 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
34109
34605
  if (dir === null)
34110
34606
  return false;
34111
34607
  const path = join22(dir, SHOWN_PLANS_FILENAME);
34112
- const current = parseShownRecord(readFileSync28(path, "utf-8"));
34608
+ const current = parseShownRecord(readFileSync29(path, "utf-8"));
34113
34609
  const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
34114
34610
  if (at === -1)
34115
34611
  return false;
@@ -34117,10 +34613,10 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
34117
34613
  const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
34118
34614
  const body = serialiseShownRecord({ entries: current.entries, reviews });
34119
34615
  temp = join22(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
34120
- writeFileSync18(temp, body, { mode: 384 });
34616
+ writeFileSync19(temp, body, { mode: 384 });
34121
34617
  if (platform3() !== "win32")
34122
34618
  chmodSync4(temp, 384);
34123
- renameSync16(temp, path);
34619
+ renameSync17(temp, path);
34124
34620
  return true;
34125
34621
  } catch {
34126
34622
  if (temp !== null) {
@@ -34639,20 +35135,17 @@ function paceNoOwnerText() {
34639
35135
  function paceCannotReachText() {
34640
35136
  return `${PACE_NOTHING_CHANGED_OPENING}: Halfcycle could not be reached just now. Type the line again in a minute.`;
34641
35137
  }
34642
- function limitAlreadyUsedSuffix(limit, used) {
34643
- return limit <= used ? ` This phase has already used ${tokenCountWordsOf(used)}, so no new work will start until you raise it.` : "";
34644
- }
34645
- function limitKeepGoingDoneText(limit, used) {
34646
- return `Done. The limit is now ${tokenCountWordsOf(limit)} tokens. This phase has used ${tokenCountWordsOf(used)}. New work can start again.`;
35138
+ function limitKeepGoingDoneText(limit, used, weekCeilingTokens = null) {
35139
+ return limitKeepGoingShareText(limit, used, weekCeilingTokens);
34647
35140
  }
34648
35141
  function limitStopDoneText() {
34649
35142
  return 'Done. This phase is paused at its usage limit. Nothing is lost, and the agents already working will finish. Say "keep going" whenever you want to carry on.';
34650
35143
  }
34651
- function limitTimesDoneText(limit, typed, used) {
34652
- return `Done. The limit for this phase is now ${tokenCountWordsOf(limit)} tokens. That is ${typed} times what it is expected to use.` + limitAlreadyUsedSuffix(limit, used);
35144
+ function limitTimesDoneText(limit, typed, used, weekCeilingTokens = null) {
35145
+ return limitTimesShareText(limit, typed, used, weekCeilingTokens);
34653
35146
  }
34654
- function limitFigureDoneText(limit, used) {
34655
- return `Done. The limit for this phase is now ${tokenCountWordsOf(limit)} tokens.${limitAlreadyUsedSuffix(limit, used)}`;
35147
+ function limitFigureDoneText(limit, used, weekCeilingTokens = null) {
35148
+ return limitFigureShareText(limit, used, weekCeilingTokens);
34656
35149
  }
34657
35150
  function limitNotReachedText() {
34658
35151
  return `${PACE_NOTHING_CHANGED_OPENING}: this phase has not reached its usage limit, so there is nothing to answer.`;
@@ -34660,11 +35153,14 @@ function limitNotReachedText() {
34660
35153
  function limitNoBuildText() {
34661
35154
  return `${PACE_NOTHING_CHANGED_OPENING}: no build is running in this project yet.`;
34662
35155
  }
34663
- function limitTimesRangeText() {
34664
- return `${PACE_NOTHING_CHANGED_OPENING}: a limit is between ${String(LIMIT_MULTIPLE_RANGE.min)} and ${String(LIMIT_MULTIPLE_RANGE.max)} times what is expected. Say a number in that range, like "limit it to 1.5 times".`;
35156
+ function limitTimesRangeText(weekCeilingTokens = null) {
35157
+ return limitTimesRangeShareText(LIMIT_MULTIPLE_RANGE, weekCeilingTokens);
35158
+ }
35159
+ function limitNoExpectedText(weekCeilingTokens = null) {
35160
+ return limitNoExpectedShareText(weekCeilingTokens);
34665
35161
  }
34666
- function limitNoExpectedText() {
34667
- return `${PACE_NOTHING_CHANGED_OPENING}: there is no expected usage yet, so say a figure, like "limit it to 300 million".`;
35162
+ function limitShareNotSetText() {
35163
+ return LIMIT_SHARE_NOT_SET_TEXT;
34668
35164
  }
34669
35165
  function limitFigureRangeText() {
34670
35166
  return `${PACE_NOTHING_CHANGED_OPENING}: a limit is between ${tokenCountWordsOf(LIMIT_FIGURE_RANGE.min)} and ${tokenCountWordsOf(LIMIT_FIGURE_RANGE.max)} tokens.`;
@@ -34941,8 +35437,11 @@ function decideLimit(shape, opts) {
34941
35437
  const file2 = dir === null ? null : openPhaseLimit(dir, readPaceStateFile(dir).basis);
34942
35438
  if (dir === null || file2 === null)
34943
35439
  return say(limitNoBuildText());
35440
+ const ceiling = weeklyCeilingHere(opts, agent);
35441
+ if (shape.kind === "share" && ceiling === null)
35442
+ return say(limitShareNotSetText());
34944
35443
  if (shape.kind === "times" && !multipleInRange(shape.multiple))
34945
- return say(limitTimesRangeText());
35444
+ return say(limitTimesRangeText(ceiling));
34946
35445
  if (shape.kind === "figure" && !figureInRange(shape.tokens))
34947
35446
  return say(limitFigureRangeText());
34948
35447
  const used = readPhaseUsage(dir, file2.phase);
@@ -34966,11 +35465,11 @@ function decideLimit(shape, opts) {
34966
35465
  return say(limitNotSavedText());
34967
35466
  if (typed === "cannot-tell")
34968
35467
  relay();
34969
- return say(limitKeepGoingDoneText(next2.limit, used));
35468
+ return say(limitKeepGoingDoneText(next2.limit, used, ceiling));
34970
35469
  }
34971
35470
  if (shape.kind === "times" && !(file2.expected > 0))
34972
- return say(limitNoExpectedText());
34973
- const limit = shape.kind === "times" ? limitOf(file2.expected, shape.multiple) : shape.tokens;
35471
+ return say(limitNoExpectedText(ceiling));
35472
+ const limit = shape.kind === "times" ? limitOf(file2.expected, shape.multiple) : shape.kind === "share" ? Math.max(1, Math.round(shape.percent / 100 * (ceiling ?? 0))) : shape.tokens;
34974
35473
  const raise = limit > file2.limit;
34975
35474
  if (raise && typed === "not-attended")
34976
35475
  return say(limitNotAttendedText());
@@ -34979,7 +35478,32 @@ function decideLimit(shape, opts) {
34979
35478
  return say(limitNotSavedText());
34980
35479
  if (raise && typed === "cannot-tell")
34981
35480
  relay();
34982
- return say(shape.kind === "times" ? limitTimesDoneText(limit, shape.typed, used) : limitFigureDoneText(limit, used));
35481
+ return say(shape.kind === "times" ? limitTimesDoneText(limit, shape.typed, used, ceiling) : limitFigureDoneText(limit, used, ceiling));
35482
+ }
35483
+ function weeklyCeilingHere(opts, agent) {
35484
+ try {
35485
+ const said = promptRecordPath(opts.input);
35486
+ const key = configFolderKey({
35487
+ env: opts.env ?? process.env,
35488
+ transcriptPath: said,
35489
+ home: opts.home,
35490
+ ...agent === "codex" ? { rule: "codex-home" } : {}
35491
+ });
35492
+ return weeklyCeilingOf(key, { home: opts.home });
35493
+ } catch {
35494
+ return null;
35495
+ }
35496
+ }
35497
+ function promptRecordPath(input) {
35498
+ try {
35499
+ const parsed = JSON.parse(input);
35500
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
35501
+ return null;
35502
+ const said = parsed["transcript_path"];
35503
+ return typeof said === "string" && said !== "" ? said : null;
35504
+ } catch {
35505
+ return null;
35506
+ }
34983
35507
  }
34984
35508
  function adjustPaceState(dir, opts, number4) {
34985
35509
  try {
@@ -35302,7 +35826,7 @@ function runUserPromptFindings(opts) {
35302
35826
  }
35303
35827
 
35304
35828
  // dist/resolve-engagement.js
35305
- import { existsSync as existsSync9, readFileSync as readFileSync29, realpathSync as realpathSync10 } from "node:fs";
35829
+ import { existsSync as existsSync9, readFileSync as readFileSync30, realpathSync as realpathSync10 } from "node:fs";
35306
35830
  import { join as join23, sep as sep8 } from "node:path";
35307
35831
  import { homedir as homedir17 } from "node:os";
35308
35832
  function defaultRegistryPath() {
@@ -35312,7 +35836,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
35312
35836
  if (!existsSync9(registryPath))
35313
35837
  return {};
35314
35838
  try {
35315
- const parsed = JSON.parse(readFileSync29(registryPath, "utf-8"));
35839
+ const parsed = JSON.parse(readFileSync30(registryPath, "utf-8"));
35316
35840
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
35317
35841
  return {};
35318
35842
  const out = {};
@@ -35360,12 +35884,12 @@ function runResolveEngagement(cwd, registryPath) {
35360
35884
  }
35361
35885
 
35362
35886
  // dist/refresh-credential.js
35363
- import { lstatSync as lstatSync5, readFileSync as readFileSync31, realpathSync as realpathSync11 } from "node:fs";
35887
+ import { lstatSync as lstatSync5, readFileSync as readFileSync32, realpathSync as realpathSync11 } from "node:fs";
35364
35888
  import { homedir as homedir19 } from "node:os";
35365
35889
  import { join as join24, resolve as resolve12 } from "node:path";
35366
35890
 
35367
35891
  // dist/mcp-headers.js
35368
- import { readFileSync as readFileSync30 } from "node:fs";
35892
+ import { readFileSync as readFileSync31 } from "node:fs";
35369
35893
  import { homedir as homedir18 } from "node:os";
35370
35894
  import { dirname as dirname10, resolve as resolve11 } from "node:path";
35371
35895
  var URL_FLAG = "--url";
@@ -35399,7 +35923,7 @@ function projectRootFromBinary(binaryPath) {
35399
35923
  function readPinnedVersion(root) {
35400
35924
  let raw;
35401
35925
  try {
35402
- raw = readFileSync30(resolve11(root, BUNDLE_PIN_REL), "utf-8");
35926
+ raw = readFileSync31(resolve11(root, BUNDLE_PIN_REL), "utf-8");
35403
35927
  } catch {
35404
35928
  return void 0;
35405
35929
  }
@@ -35661,7 +36185,7 @@ var SESSION_START_PROBE_ATTEMPTS = 2;
35661
36185
  function registeredMcpUrl(projectRoot) {
35662
36186
  let parsed;
35663
36187
  try {
35664
- parsed = JSON.parse(readFileSync31(resolve12(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
36188
+ parsed = JSON.parse(readFileSync32(resolve12(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
35665
36189
  } catch {
35666
36190
  return void 0;
35667
36191
  }
@@ -35850,7 +36374,7 @@ function headlessInUntrustedFolder(input, profile, opts) {
35850
36374
  const cwd = input["cwd"];
35851
36375
  const folder2 = typeof cwd === "string" && cwd !== "" ? cwd : projectRootFromBinary(opts.binaryPath);
35852
36376
  const stateFile = join24(readClaudeConfigFolder() ?? opts.home ?? homedir19(), CLAUDE_STATE_FILENAME);
35853
- return trustOfWorkingFolder(folder2, stateFile, process.platform, { lstatSync: lstatSync5, readFileSync: readFileSync31, realpathSync: realpathSync11 }) === "not-trusted";
36377
+ return trustOfWorkingFolder(folder2, stateFile, process.platform, { lstatSync: lstatSync5, readFileSync: readFileSync32, realpathSync: realpathSync11 }) === "not-trusted";
35854
36378
  }
35855
36379
  async function runRefreshCredential(opts) {
35856
36380
  const writeErr = opts.writeErr ?? defaultWriteErr2;
@@ -35943,7 +36467,7 @@ async function runRefreshCredential(opts) {
35943
36467
  import { fileURLToPath } from "node:url";
35944
36468
 
35945
36469
  // dist/capture.js
35946
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync19, mkdirSync as mkdirSync13, existsSync as existsSync10 } from "node:fs";
36470
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync20, mkdirSync as mkdirSync14, existsSync as existsSync10 } from "node:fs";
35947
36471
  import { dirname as dirname11 } from "node:path";
35948
36472
  function runCapture(provider, endpoint, opts = {}) {
35949
36473
  const repoRoot = opts.repoRoot ?? process.cwd();
@@ -35979,8 +36503,8 @@ function runCapture(provider, endpoint, opts = {}) {
35979
36503
  const fixture = fixturePath(repoRoot, provider, slug);
35980
36504
  const manifestFile = manifestPath(repoRoot);
35981
36505
  try {
35982
- mkdirSync13(dirname11(fixture), { recursive: true });
35983
- writeFileSync19(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
36506
+ mkdirSync14(dirname11(fixture), { recursive: true });
36507
+ writeFileSync20(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
35984
36508
  } catch (err) {
35985
36509
  const msg = err instanceof Error ? err.message : String(err);
35986
36510
  return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
@@ -36011,7 +36535,7 @@ function readInputFile(fromFile) {
36011
36535
  if (!fromFile)
36012
36536
  return void 0;
36013
36537
  try {
36014
- return readFileSync32(fromFile, "utf-8");
36538
+ return readFileSync33(fromFile, "utf-8");
36015
36539
  } catch {
36016
36540
  return void 0;
36017
36541
  }
@@ -36024,7 +36548,7 @@ function relativeFixturePath(repoRoot, fixture) {
36024
36548
  // dist/prove.js
36025
36549
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
36026
36550
  import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
36027
- import { createWriteStream, mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as readFileSync33, renameSync as renameSync17, unlinkSync as unlinkSync12, writeFileSync as writeFileSync20 } from "node:fs";
36551
+ import { createWriteStream, mkdirSync as mkdirSync15, readdirSync as readdirSync11, readFileSync as readFileSync34, renameSync as renameSync18, unlinkSync as unlinkSync12, writeFileSync as writeFileSync21 } from "node:fs";
36028
36552
  import { homedir as homedir20 } from "node:os";
36029
36553
  import { dirname as dirname12, join as join25 } from "node:path";
36030
36554
 
@@ -36221,10 +36745,10 @@ function parseProveArgs(args2) {
36221
36745
  };
36222
36746
  }
36223
36747
  function writeJsonFile2(path, value) {
36224
- mkdirSync14(dirname12(path), { recursive: true, mode: 448 });
36748
+ mkdirSync15(dirname12(path), { recursive: true, mode: 448 });
36225
36749
  const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
36226
- writeFileSync20(tmp, JSON.stringify(value), { mode: 384 });
36227
- renameSync17(tmp, path);
36750
+ writeFileSync21(tmp, JSON.stringify(value), { mode: 384 });
36751
+ renameSync18(tmp, path);
36228
36752
  }
36229
36753
  function subjectKey(subject) {
36230
36754
  return subject.kind === "task" ? ["task", subject.phase, subject.task] : ["change", subject.change];
@@ -36235,7 +36759,7 @@ function keptCheckPath(dir, subject, label) {
36235
36759
  }
36236
36760
  function readKeptCheck(dir, subject, label) {
36237
36761
  try {
36238
- const raw = JSON.parse(readFileSync33(keptCheckPath(dir, subject, label), "utf-8"));
36762
+ const raw = JSON.parse(readFileSync34(keptCheckPath(dir, subject, label), "utf-8"));
36239
36763
  if (typeof raw.command !== "string" || raw.command === "" || typeof raw.commandId !== "string")
36240
36764
  return null;
36241
36765
  const b = raw.before;
@@ -36260,7 +36784,7 @@ function readQueue(dir) {
36260
36784
  const path = join25(dir, QUEUE_DIR_NAME, name);
36261
36785
  let parsed = null;
36262
36786
  try {
36263
- parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync33(path, "utf-8")));
36787
+ parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync34(path, "utf-8")));
36264
36788
  } catch {
36265
36789
  parsed = null;
36266
36790
  }
@@ -36337,7 +36861,7 @@ function runCommand(command, repoRoot, logPath2, env, deps) {
36337
36861
  const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
36338
36862
  const childEnv = { ...env };
36339
36863
  delete childEnv[SERVICE_TOKEN_VAR];
36340
- mkdirSync14(dirname12(logPath2), { recursive: true, mode: 448 });
36864
+ mkdirSync15(dirname12(logPath2), { recursive: true, mode: 448 });
36341
36865
  const log = createWriteStream(logPath2, { mode: 384 });
36342
36866
  const began = process.hrtime.bigint();
36343
36867
  return new Promise((resolve13) => {
@@ -36503,7 +37027,7 @@ ${PROVE_USAGE}`);
36503
37027
  const end = await runCommand(command, repoRoot, logPath2, env, deps);
36504
37028
  let output = "";
36505
37029
  try {
36506
- output = readFileSync33(logPath2, "utf-8");
37030
+ output = readFileSync34(logPath2, "utf-8");
36507
37031
  } catch {
36508
37032
  output = "";
36509
37033
  }
@@ -36553,8 +37077,8 @@ ${PROVE_USAGE}`);
36553
37077
 
36554
37078
  // dist/statusline.js
36555
37079
  import { spawn as spawn3 } from "node:child_process";
36556
- import { readFileSync as readFileSync34 } from "node:fs";
36557
- import nodePath6 from "node:path";
37080
+ import { readFileSync as readFileSync35 } from "node:fs";
37081
+ import nodePath7 from "node:path";
36558
37082
  var OWN_STATUS_LINE_DEADLINE_MS = 5e3;
36559
37083
  var PERSON_SETTINGS_FILENAME = "settings.json";
36560
37084
  var OWN_COMMAND_MARK = /guard-runner\.sh["']?\s+statusline\b/;
@@ -36591,7 +37115,7 @@ function halfcycleStatusLineOf(figures) {
36591
37115
  }
36592
37116
  function personStatusLineCommand(configFolder) {
36593
37117
  try {
36594
- const v = JSON.parse(readFileSync34(nodePath6.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
37118
+ const v = JSON.parse(readFileSync35(nodePath7.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
36595
37119
  const line2 = v["statusLine"];
36596
37120
  if (typeof line2 !== "object" || line2 === null)
36597
37121
  return null;