halfcycle 0.4.14 → 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +2549 -552
- package/bin/pre-tool-use-filter.sh +41 -5
- package/commands/agent-teams-orchestration.md +1 -1
- package/commands/consistency-check.md +1 -1
- package/commands/context-update.md +1 -1
- package/commands/halfcycle-design.md +1 -1
- package/commands/halfcycle-setup.md +1 -1
- package/commands/stack-assembly-worker.md +1 -1
- package/commands/task-execution.md +1 -1
- package/commands/task-graph.md +1 -1
- package/commands/write-spec.md +1 -1
- package/dist/bin.js +0 -0
- package/dist/build-record/render-v2.d.ts.map +1 -1
- package/dist/claude-plan.d.ts +24 -5
- package/dist/claude-plan.d.ts.map +1 -1
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +5236 -3540
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts +10 -1
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/doctor.d.ts +34 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/folder-refusals.d.ts +27 -0
- package/dist/folder-refusals.d.ts.map +1 -0
- package/dist/identity.d.ts +31 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +1332 -404
- package/dist/index.js.map +3 -3
- package/dist/install-plan.d.ts +112 -0
- package/dist/install-plan.d.ts.map +1 -0
- package/dist/install.d.ts +535 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/plan.d.ts +16 -3
- package/dist/plan.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts +28 -0
- package/dist/pre-create-checks.d.ts.map +1 -1
- package/dist/preview.d.ts +40 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/restore.d.ts +58 -0
- package/dist/restore.d.ts.map +1 -0
- package/dist/review.d.ts +9 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/scan.d.ts +11 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/setup/discovery.d.ts.map +1 -1
- package/dist/setup/rows.d.ts +10 -6
- package/dist/setup/rows.d.ts.map +1 -1
- package/package.json +12 -12
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14663,9 +14663,10 @@ var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
|
|
|
14663
14663
|
var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
|
|
14664
14664
|
var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
14665
14665
|
var USER_PROMPT_SCOPE_REL = ".claude/hooks/user-prompt-scope.sh";
|
|
14666
|
+
var USER_PROMPT_FINDINGS_REL = ".claude/hooks/user-prompt-findings.sh";
|
|
14666
14667
|
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
14667
14668
|
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
14668
|
-
var PRE_TOOL_USE_MATCHER = "^(Bash|Read|Grep|Write|Edit|MultiEdit|NotebookEdit|Agent)$";
|
|
14669
|
+
var PRE_TOOL_USE_MATCHER = "^(Bash|Read|Grep|Write|Edit|MultiEdit|NotebookEdit|Agent|PowerShell)$";
|
|
14669
14670
|
function hookCommand(script, verb) {
|
|
14670
14671
|
const invocation = `bash "$CLAUDE_PROJECT_DIR"/${script}`;
|
|
14671
14672
|
return verb === void 0 ? invocation : `${invocation} ${verb}`;
|
|
@@ -14699,6 +14700,13 @@ var HALFCYCLE_HOOK_WIRING = [
|
|
|
14699
14700
|
command: hookCommand(USER_PROMPT_SCOPE_REL),
|
|
14700
14701
|
script: USER_PROMPT_SCOPE_REL
|
|
14701
14702
|
},
|
|
14703
|
+
{
|
|
14704
|
+
event: "UserPromptSubmit",
|
|
14705
|
+
matcher: null,
|
|
14706
|
+
timeout: 10,
|
|
14707
|
+
command: hookCommand(USER_PROMPT_FINDINGS_REL),
|
|
14708
|
+
script: USER_PROMPT_FINDINGS_REL
|
|
14709
|
+
},
|
|
14702
14710
|
{
|
|
14703
14711
|
event: "PreToolUse",
|
|
14704
14712
|
matcher: PRE_TOOL_USE_MATCHER,
|
|
@@ -16393,7 +16401,7 @@ var SCOPE_PART_STATUSES = ["approved", "waiting"];
|
|
|
16393
16401
|
var scopePartStatusSchema = external_exports.enum(SCOPE_PART_STATUSES);
|
|
16394
16402
|
var SCOPE_ACTS = ["approve", "send-back"];
|
|
16395
16403
|
var scopeActSchema = external_exports.enum(SCOPE_ACTS);
|
|
16396
|
-
var SCOPE_GIVEN_IN = ["console", "chat"];
|
|
16404
|
+
var SCOPE_GIVEN_IN = ["console", "chat", "autonomy"];
|
|
16397
16405
|
var scopeGivenInSchema = external_exports.enum(SCOPE_GIVEN_IN);
|
|
16398
16406
|
var scopeNoteSchema = descriptionText(SCOPE_NOTE_MAX);
|
|
16399
16407
|
var scopeNoteWireSchema = external_exports.string().max(SCOPE_NOTE_MAX);
|
|
@@ -16425,7 +16433,8 @@ var phasePlanRecordedSchema = external_exports.object({
|
|
|
16425
16433
|
typedApprovalOffered: external_exports.boolean(),
|
|
16426
16434
|
reportedAt: utcIso8601,
|
|
16427
16435
|
carriedFrom: external_exports.array(carriedFromEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX).optional(),
|
|
16428
|
-
movedUp: planMovedUpSchema.optional()
|
|
16436
|
+
movedUp: planMovedUpSchema.optional(),
|
|
16437
|
+
givenIn: scopeGivenInSchema.optional()
|
|
16429
16438
|
}).strict();
|
|
16430
16439
|
var phasePlanRefusedSchema = external_exports.object({
|
|
16431
16440
|
outcome: external_exports.literal("refused"),
|
|
@@ -16823,6 +16832,12 @@ var PROJECT_RECORD_PATTERNS = [
|
|
|
16823
16832
|
{ pattern: "docs/build-records/build-tasks.json", name: () => "the build's progress record (build-tasks.json)" },
|
|
16824
16833
|
{ pattern: "docs/build-records/task-plan.json", name: () => "the build's plan (task-plan.json)", shrinkAsked: false },
|
|
16825
16834
|
{ pattern: "docs/build-records", name: (file2) => `a closed phase's Build Record (${file2})` },
|
|
16835
|
+
{
|
|
16836
|
+
pattern: ".claude/context/tasks/*",
|
|
16837
|
+
name: (file2) => `a task's notes for this phase (${file2})`,
|
|
16838
|
+
shrinkAsked: false,
|
|
16839
|
+
deleteAsked: false
|
|
16840
|
+
},
|
|
16826
16841
|
{ pattern: ".claude/context", name: (file2) => `the project's working notes (${file2})` }
|
|
16827
16842
|
];
|
|
16828
16843
|
var observedCountSchema = external_exports.number({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).int({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).min(0, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).max(OBSERVED_COUNT_MAX, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` });
|
|
@@ -17645,6 +17660,14 @@ var REVIEW_PART_CHANGES = Object.freeze([...PHASE_PLAN_CHANGE_KINDS, "not-in-pla
|
|
|
17645
17660
|
var reviewPartChangeSchema = external_exports.enum(REVIEW_PART_CHANGES);
|
|
17646
17661
|
var REVIEW_DECISIONS = Object.freeze(["accepted", "carried", "dropped"]);
|
|
17647
17662
|
var reviewDecisionSchema = external_exports.enum(REVIEW_DECISIONS);
|
|
17663
|
+
var reviewGivenInSchema = scopeGivenInSchema.extract(["autonomy"]);
|
|
17664
|
+
function checkReviewGivenIn(v, ctx) {
|
|
17665
|
+
if (v.givenIn === void 0)
|
|
17666
|
+
return;
|
|
17667
|
+
if (v.decision !== "accepted" && v.decision !== "carried") {
|
|
17668
|
+
ctx.addIssue({ code: "custom", path: ["givenIn"], message: "is present only on a part accepted or carried" });
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17648
17671
|
function actOf(decision) {
|
|
17649
17672
|
if (decision === "accepted")
|
|
17650
17673
|
return "accept";
|
|
@@ -17712,6 +17735,25 @@ var reviewRefusedSchema = external_exports.object({
|
|
|
17712
17735
|
function bareReviewOutcome(outcome) {
|
|
17713
17736
|
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
17714
17737
|
}
|
|
17738
|
+
var reviewClosedByAutonomySchema = external_exports.object({
|
|
17739
|
+
outcome: external_exports.literal("closed-by-autonomy"),
|
|
17740
|
+
accepted: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX),
|
|
17741
|
+
carried: external_exports.number().int().min(0).max(REVIEW_PARTS_MAX)
|
|
17742
|
+
}).strict().refine((v) => v.accepted + v.carried <= REVIEW_PARTS_MAX, {
|
|
17743
|
+
message: "cannot exceed the parts a round holds",
|
|
17744
|
+
path: ["carried"]
|
|
17745
|
+
});
|
|
17746
|
+
var AUTONOMY_WAIT_REASONS = ["unchecked-claims", "built-without-approval"];
|
|
17747
|
+
var autonomyWaitReasonSchema = external_exports.enum(AUTONOMY_WAIT_REASONS);
|
|
17748
|
+
var reviewAutonomyWaitsSchema = external_exports.object({
|
|
17749
|
+
outcome: external_exports.literal("autonomy-waits"),
|
|
17750
|
+
reason: autonomyWaitReasonSchema,
|
|
17751
|
+
count: external_exports.number().int().min(1),
|
|
17752
|
+
parts: external_exports.array(external_exports.object({ part: systemPartIdSchema, name: systemPartSchema.shape.name }).strict()).min(1).max(REVIEW_PARTS_MAX)
|
|
17753
|
+
}).strict().refine((v) => v.reason !== "built-without-approval" || v.count === v.parts.length, {
|
|
17754
|
+
message: "counts the parts it names",
|
|
17755
|
+
path: ["count"]
|
|
17756
|
+
});
|
|
17715
17757
|
var openReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
17716
17758
|
reviewOpenedSchema,
|
|
17717
17759
|
reviewTasksOpenSchema,
|
|
@@ -17720,7 +17762,9 @@ var openReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
17720
17762
|
reviewOutstandingSchema,
|
|
17721
17763
|
reviewCleanOverFindingsSchema,
|
|
17722
17764
|
reviewRefusedSchema,
|
|
17723
|
-
bareReviewOutcome("unavailable")
|
|
17765
|
+
bareReviewOutcome("unavailable"),
|
|
17766
|
+
reviewClosedByAutonomySchema,
|
|
17767
|
+
reviewAutonomyWaitsSchema
|
|
17724
17768
|
]);
|
|
17725
17769
|
var reviewPhaseNotOpenSchema = external_exports.object({
|
|
17726
17770
|
statusCode: external_exports.literal(409),
|
|
@@ -17834,6 +17878,7 @@ var reviewViewPartSchema = external_exports.object({
|
|
|
17834
17878
|
decidable: external_exports.boolean(),
|
|
17835
17879
|
waiting: external_exports.boolean(),
|
|
17836
17880
|
decision: reviewDecisionSchema.optional(),
|
|
17881
|
+
givenIn: reviewGivenInSchema.optional(),
|
|
17837
17882
|
reason: reviewNoteSchema.optional(),
|
|
17838
17883
|
carriedTo: reviewCarriedToSchema.optional(),
|
|
17839
17884
|
sentBack: external_exports.array(reviewSentBackSchema),
|
|
@@ -17861,6 +17906,7 @@ var reviewViewPartSchema = external_exports.object({
|
|
|
17861
17906
|
if (v.change === "not-in-plan" && !v.built) {
|
|
17862
17907
|
ctx.addIssue({ code: "custom", path: ["built"], message: "is always true for a part outside the plan" });
|
|
17863
17908
|
}
|
|
17909
|
+
checkReviewGivenIn(v, ctx);
|
|
17864
17910
|
});
|
|
17865
17911
|
var reviewPhaseNumberSchema = external_exports.number().int().min(1).nullable();
|
|
17866
17912
|
var phaseReviewNoneSchema = external_exports.object({
|
|
@@ -18286,6 +18332,87 @@ var reviewAnswerResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
18286
18332
|
bareOutcome2("unavailable")
|
|
18287
18333
|
]);
|
|
18288
18334
|
|
|
18335
|
+
// ../events/dist/owner-waits.js
|
|
18336
|
+
var OWNER_WAIT_ITEMS = Object.freeze([
|
|
18337
|
+
Object.freeze({ id: "delete-users-data", words: "Deleting or replacing data people already have." }),
|
|
18338
|
+
Object.freeze({
|
|
18339
|
+
id: "change-promises",
|
|
18340
|
+
words: "Changing what the product promises its users, such as pricing, terms or privacy."
|
|
18341
|
+
}),
|
|
18342
|
+
Object.freeze({ id: "spend-or-sign-up", words: "Anything that costs money or signs up for a service." }),
|
|
18343
|
+
Object.freeze({ id: "publish-or-release", words: "Publishing or releasing to users." }),
|
|
18344
|
+
Object.freeze({ id: "reshape-shared-data", words: "Dropping or reshaping stored data that other parts read." }),
|
|
18345
|
+
Object.freeze({ id: "change-standing-rule", words: "Changing a standing rule." })
|
|
18346
|
+
]);
|
|
18347
|
+
var OWNER_WAIT_ITEM_IDS = Object.freeze(OWNER_WAIT_ITEMS.map((item) => item.id));
|
|
18348
|
+
var ownerWaitItemIdSchema = external_exports.enum(OWNER_WAIT_ITEM_IDS);
|
|
18349
|
+
var OWNER_WAIT_FIXED_COUNT = OWNER_WAIT_ITEMS.length;
|
|
18350
|
+
var OWNER_WAIT_ADDITIONS_MAX = 20;
|
|
18351
|
+
var OWNER_WAIT_TEXT_MAX = 240;
|
|
18352
|
+
var ownerWaitItemRefSchema = external_exports.union([
|
|
18353
|
+
external_exports.number().int().min(1),
|
|
18354
|
+
external_exports.string().uuid({ message: "must be a number from 1 to 6, or the id shown beside an added item" })
|
|
18355
|
+
]);
|
|
18356
|
+
var ownerWaitText = descriptionText(OWNER_WAIT_TEXT_MAX);
|
|
18357
|
+
var ownerWaitAddRequestSchema = external_exports.object({
|
|
18358
|
+
engagementId: external_exports.string().min(1),
|
|
18359
|
+
text: ownerWaitText,
|
|
18360
|
+
reason: ownerWaitText
|
|
18361
|
+
}).strict();
|
|
18362
|
+
var ownerWaitAddResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
18363
|
+
external_exports.object({ outcome: external_exports.literal("added"), id: external_exports.string().uuid(), addedAt: utcIso8601 }).strict(),
|
|
18364
|
+
external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.literal("too-many") }).strict()
|
|
18365
|
+
]);
|
|
18366
|
+
var ownerWaitUndoableSchema = external_exports.enum(["yes", "no"]);
|
|
18367
|
+
var ownerWaitDeclareRequestSchema = external_exports.object({
|
|
18368
|
+
engagementId: external_exports.string().min(1),
|
|
18369
|
+
item: ownerWaitItemRefSchema,
|
|
18370
|
+
question: ownerWaitText,
|
|
18371
|
+
recommendation: ownerWaitText,
|
|
18372
|
+
task: buildTaskIdSchema.optional(),
|
|
18373
|
+
by: agentNameSchema.optional(),
|
|
18374
|
+
undoable: ownerWaitUndoableSchema.optional(),
|
|
18375
|
+
how: statementText(STATEMENT_MAX).optional()
|
|
18376
|
+
}).strict().refine((r) => r.undoable === "yes" === (r.how !== void 0), {
|
|
18377
|
+
message: "says how to undo it exactly when it can be undone",
|
|
18378
|
+
path: ["how"]
|
|
18379
|
+
});
|
|
18380
|
+
var OWNER_WAIT_DECLARE_REFUSALS = ["no-open-phase", "unknown-item", "say-if-undoable"];
|
|
18381
|
+
var ownerWaitDeclareResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
18382
|
+
external_exports.object({ outcome: external_exports.literal("recorded"), id: external_exports.string().uuid(), say: external_exports.string().min(1) }).strict(),
|
|
18383
|
+
external_exports.object({
|
|
18384
|
+
outcome: external_exports.literal("go-ahead"),
|
|
18385
|
+
id: external_exports.string().uuid(),
|
|
18386
|
+
callId: external_exports.number().int().positive(),
|
|
18387
|
+
say: external_exports.string().min(1)
|
|
18388
|
+
}).strict(),
|
|
18389
|
+
external_exports.object({ outcome: external_exports.literal("refused"), reason: external_exports.enum(OWNER_WAIT_DECLARE_REFUSALS) }).strict()
|
|
18390
|
+
]);
|
|
18391
|
+
var ownerWaitAddedItemSchema = external_exports.object({
|
|
18392
|
+
id: external_exports.string().uuid(),
|
|
18393
|
+
words: external_exports.string().min(1),
|
|
18394
|
+
reason: external_exports.string().min(1),
|
|
18395
|
+
addedAt: utcIso8601
|
|
18396
|
+
}).strict();
|
|
18397
|
+
var ownerWaitListSchema = external_exports.object({
|
|
18398
|
+
fixed: external_exports.array(external_exports.object({ id: ownerWaitItemIdSchema, words: external_exports.string().min(1) }).strict()).length(OWNER_WAIT_FIXED_COUNT),
|
|
18399
|
+
added: external_exports.array(ownerWaitAddedItemSchema).max(OWNER_WAIT_ADDITIONS_MAX)
|
|
18400
|
+
}).strict();
|
|
18401
|
+
var ownerWaitDeclarationSchema = external_exports.object({
|
|
18402
|
+
id: external_exports.string().uuid(),
|
|
18403
|
+
fixedItem: ownerWaitItemIdSchema.nullable(),
|
|
18404
|
+
addition: external_exports.object({ id: external_exports.string().uuid(), words: external_exports.string().min(1) }).strict().nullable(),
|
|
18405
|
+
question: external_exports.string().min(1),
|
|
18406
|
+
recommendation: external_exports.string().min(1),
|
|
18407
|
+
task: buildTaskIdSchema.nullable(),
|
|
18408
|
+
declaredAt: utcIso8601
|
|
18409
|
+
}).strict().refine((d) => d.fixedItem === null !== (d.addition === null), {
|
|
18410
|
+
message: "names exactly one item: a fixed one or an added one",
|
|
18411
|
+
path: ["fixedItem"]
|
|
18412
|
+
});
|
|
18413
|
+
var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
|
|
18414
|
+
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 in the console first.`;
|
|
18415
|
+
|
|
18289
18416
|
// ../events/dist/pacing.js
|
|
18290
18417
|
var CLAUDE_PLANS = Object.freeze([
|
|
18291
18418
|
Object.freeze({ id: "pro", words: "Pro", startingAgents: 2 }),
|
|
@@ -18417,10 +18544,212 @@ function paceStatusLineOf(percent, window) {
|
|
|
18417
18544
|
return `Halfcycle \xB7 ${String(Math.round(percent))}% of this ${PACE_WINDOW_WORDS[window]} used`;
|
|
18418
18545
|
}
|
|
18419
18546
|
|
|
18547
|
+
// ../events/dist/autonomy.js
|
|
18548
|
+
var AUTONOMY_CHANGED_IN = ["console", "chat"];
|
|
18549
|
+
var autonomyChangedInSchema = external_exports.enum(AUTONOMY_CHANGED_IN);
|
|
18550
|
+
var autonomySettingsSchema = external_exports.object({
|
|
18551
|
+
on: external_exports.boolean(),
|
|
18552
|
+
// whether builds run on their own
|
|
18553
|
+
mayPublish: external_exports.boolean(),
|
|
18554
|
+
// whether builds may publish or release without waiting
|
|
18555
|
+
maySpend: external_exports.boolean(),
|
|
18556
|
+
// whether builds may spend money or sign up without waiting
|
|
18557
|
+
changedAt: utcIso8601.nullable(),
|
|
18558
|
+
// when the setting last changed; null if never
|
|
18559
|
+
changedIn: autonomyChangedInSchema.nullable()
|
|
18560
|
+
// where it last changed; null if never
|
|
18561
|
+
}).strict().refine((v) => v.changedAt === null === (v.changedIn === null), {
|
|
18562
|
+
message: "says when and where it changed, or neither",
|
|
18563
|
+
path: ["changedIn"]
|
|
18564
|
+
});
|
|
18565
|
+
var autonomyChangeRequestSchema = external_exports.object({
|
|
18566
|
+
on: external_exports.boolean(),
|
|
18567
|
+
// the switch
|
|
18568
|
+
mayPublish: external_exports.boolean(),
|
|
18569
|
+
// the publish box
|
|
18570
|
+
maySpend: external_exports.boolean()
|
|
18571
|
+
// the spend box
|
|
18572
|
+
}).strict();
|
|
18573
|
+
var AUTONOMY_CHAT_ACTS = ["on", "off"];
|
|
18574
|
+
var autonomyChatActSchema = external_exports.enum(AUTONOMY_CHAT_ACTS);
|
|
18575
|
+
var autonomyChatRequestSchema = external_exports.object({
|
|
18576
|
+
act: autonomyChatActSchema
|
|
18577
|
+
// what the owner typed
|
|
18578
|
+
}).strict();
|
|
18579
|
+
var autonomyChatResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
18580
|
+
external_exports.object({
|
|
18581
|
+
outcome: external_exports.literal("recorded"),
|
|
18582
|
+
on: external_exports.boolean()
|
|
18583
|
+
// whether builds now run on their own
|
|
18584
|
+
}).strict(),
|
|
18585
|
+
external_exports.object({ outcome: external_exports.literal("not-owner") }).strict(),
|
|
18586
|
+
external_exports.object({ outcome: external_exports.literal("no-owner") }).strict(),
|
|
18587
|
+
external_exports.object({ outcome: external_exports.literal("not-offered") }).strict(),
|
|
18588
|
+
external_exports.object({ outcome: external_exports.literal("unavailable") }).strict()
|
|
18589
|
+
]);
|
|
18590
|
+
var autonomyStateSchema = external_exports.object({
|
|
18591
|
+
on: external_exports.boolean(),
|
|
18592
|
+
// whether builds run on their own
|
|
18593
|
+
mayPublish: external_exports.boolean(),
|
|
18594
|
+
// whether builds may publish or release without waiting
|
|
18595
|
+
maySpend: external_exports.boolean()
|
|
18596
|
+
// whether builds may spend money or sign up without waiting
|
|
18597
|
+
}).strict();
|
|
18598
|
+
var autonomyStateFileSchema = autonomyStateSchema.extend({
|
|
18599
|
+
readAt: utcIso8601
|
|
18600
|
+
// when this copy was read from Halfcycle
|
|
18601
|
+
}).strict();
|
|
18602
|
+
var AUTONOMY_STATE_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
18603
|
+
var AUTONOMY_ACTIVITY_KINDS = ["went-ahead", "waiting", "approved", "closed", "waiting-on-owner"];
|
|
18604
|
+
var autonomyActivityKindSchema = external_exports.enum(AUTONOMY_ACTIVITY_KINDS);
|
|
18605
|
+
var autonomyActivityRowSchema = external_exports.object({
|
|
18606
|
+
kind: autonomyActivityKindSchema,
|
|
18607
|
+
// what happened
|
|
18608
|
+
phase: scopePhaseSchema,
|
|
18609
|
+
// the phase it happened in
|
|
18610
|
+
question: ownerWaitText.optional(),
|
|
18611
|
+
// the agent's question, for a decision
|
|
18612
|
+
chose: ownerWaitText.optional(),
|
|
18613
|
+
// what the agent chose or recommends, for a decision
|
|
18614
|
+
undoable: ownerWaitUndoableSchema.optional(),
|
|
18615
|
+
// whether the agent says it can be undone
|
|
18616
|
+
how: statementText(STATEMENT_MAX).optional(),
|
|
18617
|
+
// how to undo it, when it can be
|
|
18618
|
+
at: utcIso8601
|
|
18619
|
+
// when it happened
|
|
18620
|
+
}).strict().superRefine((row, ctx) => {
|
|
18621
|
+
const decision = row.kind === "went-ahead" || row.kind === "waiting";
|
|
18622
|
+
if (decision && row.question === void 0) {
|
|
18623
|
+
ctx.addIssue({ code: "custom", message: "a decision says its question", path: ["question"] });
|
|
18624
|
+
}
|
|
18625
|
+
if (!decision) {
|
|
18626
|
+
for (const key of ["question", "chose", "undoable", "how"]) {
|
|
18627
|
+
if (row[key] !== void 0) {
|
|
18628
|
+
ctx.addIssue({ code: "custom", message: "only a decision carries this", path: [key] });
|
|
18629
|
+
}
|
|
18630
|
+
}
|
|
18631
|
+
}
|
|
18632
|
+
if (row.kind === "went-ahead") {
|
|
18633
|
+
if (row.chose === void 0) {
|
|
18634
|
+
ctx.addIssue({ code: "custom", message: "a decision that went ahead says what was chosen", path: ["chose"] });
|
|
18635
|
+
}
|
|
18636
|
+
if (row.undoable === void 0) {
|
|
18637
|
+
ctx.addIssue({
|
|
18638
|
+
code: "custom",
|
|
18639
|
+
message: "a decision that went ahead says whether it can be undone",
|
|
18640
|
+
path: ["undoable"]
|
|
18641
|
+
});
|
|
18642
|
+
}
|
|
18643
|
+
}
|
|
18644
|
+
if (row.kind === "waiting" && (row.undoable !== void 0 || row.how !== void 0)) {
|
|
18645
|
+
ctx.addIssue({ code: "custom", message: "a waiting decision has not been made yet", path: ["undoable"] });
|
|
18646
|
+
}
|
|
18647
|
+
if (row.kind === "went-ahead" && row.undoable === "yes" !== (row.how !== void 0)) {
|
|
18648
|
+
ctx.addIssue({ code: "custom", message: "says how to undo it exactly when it can be undone", path: ["how"] });
|
|
18649
|
+
}
|
|
18650
|
+
});
|
|
18651
|
+
var autonomyActivitySchema = external_exports.object({
|
|
18652
|
+
rows: external_exports.array(autonomyActivityRowSchema)
|
|
18653
|
+
// newest first
|
|
18654
|
+
}).strict();
|
|
18655
|
+
var savedIdSchema = external_exports.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lower-case letters, digits and single hyphens");
|
|
18656
|
+
var SAVED_REF_PREFIX = "refs/halfcycle/saved/";
|
|
18657
|
+
function savedRefOf(id) {
|
|
18658
|
+
return `${SAVED_REF_PREFIX}${id}`;
|
|
18659
|
+
}
|
|
18660
|
+
var SAVED_KEEP_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
18661
|
+
var SAVED_DIR_NAME = "saved";
|
|
18662
|
+
var SAVED_FILE_NAME = "saved.json";
|
|
18663
|
+
var SAVED_COPIES_DIR_NAME = "files";
|
|
18664
|
+
var SAVED_REPOSITORY_FILE_NAME = "repository";
|
|
18665
|
+
var SAVED_HALF_WRITTEN_GRACE_MS = 6e4;
|
|
18666
|
+
function savedPruneAnswer(save, here) {
|
|
18667
|
+
const old = (t) => !Number.isFinite(t) || here.now - t > SAVED_KEEP_MS;
|
|
18668
|
+
if (save.refMadeAt !== null) {
|
|
18669
|
+
if (!old(save.refMadeAt))
|
|
18670
|
+
return "keep";
|
|
18671
|
+
return save.folderChangedAt === null ? "remove-ref" : "remove-ref-then-folder";
|
|
18672
|
+
}
|
|
18673
|
+
if (save.folderChangedAt === null || !Number.isFinite(save.folderChangedAt))
|
|
18674
|
+
return "keep";
|
|
18675
|
+
const ours = save.folderRepository === null || here.repository !== null && save.folderRepository === here.repository;
|
|
18676
|
+
const wait = ours ? SAVED_HALF_WRITTEN_GRACE_MS : SAVED_KEEP_MS;
|
|
18677
|
+
return here.now - save.folderChangedAt > wait ? "remove-folder" : "keep";
|
|
18678
|
+
}
|
|
18679
|
+
function isProjectRelativePath(p) {
|
|
18680
|
+
if (p.length === 0 || hasControlCharacter(p))
|
|
18681
|
+
return false;
|
|
18682
|
+
if (p.startsWith("/") || p.startsWith("\\") || /^[A-Za-z]:/.test(p))
|
|
18683
|
+
return false;
|
|
18684
|
+
return p.split(/[\\/]/).every((segment) => segment !== "" && windowsNameOf(segment) !== "" && !isGitFolderSegment(segment));
|
|
18685
|
+
}
|
|
18686
|
+
function windowsNameOf(segment) {
|
|
18687
|
+
return segment.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u2064\u206a-\u206f\ufeff]/g, "").split(":")[0].replace(/[. ]+$/, "").toLowerCase();
|
|
18688
|
+
}
|
|
18689
|
+
function isGitFolderSegment(segment) {
|
|
18690
|
+
const t = windowsNameOf(segment);
|
|
18691
|
+
return t === ".git" || /^git~\d+$/.test(t);
|
|
18692
|
+
}
|
|
18693
|
+
var SAVED_PATH_KEPT_IN = ["commit", "copy"];
|
|
18694
|
+
var savedPathSchema = external_exports.object({
|
|
18695
|
+
path: external_exports.string().max(4096).refine(isProjectRelativePath, { message: "must be a path inside the project" }),
|
|
18696
|
+
// the file, relative to the project
|
|
18697
|
+
keptIn: external_exports.enum(SAVED_PATH_KEPT_IN)
|
|
18698
|
+
// in the save's commit, or copied beside this file
|
|
18699
|
+
}).strict();
|
|
18700
|
+
var savedCommitSchema = external_exports.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash");
|
|
18701
|
+
var savedBranchSchema = external_exports.object({
|
|
18702
|
+
name: external_exports.string().min(1).max(255).refine((n) => !hasControlCharacter(n) && !n.startsWith("-") && !n.includes("..") && n !== "HEAD", {
|
|
18703
|
+
message: "must be a branch name"
|
|
18704
|
+
}),
|
|
18705
|
+
// the branch's name
|
|
18706
|
+
commit: savedCommitSchema
|
|
18707
|
+
// the commit it pointed at
|
|
18708
|
+
}).strict();
|
|
18709
|
+
var savedSchema = external_exports.object({
|
|
18710
|
+
id: savedIdSchema,
|
|
18711
|
+
// what restore is typed with
|
|
18712
|
+
savedAt: utcIso8601,
|
|
18713
|
+
// when the copy was made
|
|
18714
|
+
ref: external_exports.string().min(1),
|
|
18715
|
+
// where the saved commit is kept
|
|
18716
|
+
what: external_exports.string().min(1).max(2e3),
|
|
18717
|
+
// what the action would have changed, in words
|
|
18718
|
+
paths: external_exports.array(savedPathSchema),
|
|
18719
|
+
// every saved file
|
|
18720
|
+
branches: external_exports.array(savedBranchSchema)
|
|
18721
|
+
// every saved line of work
|
|
18722
|
+
}).strict().refine((s) => s.ref === savedRefOf(s.id), { message: "must be the ref of this save", path: ["ref"] });
|
|
18723
|
+
var AUTONOMY_A4 = 'Builds in this project now run on their own. Turn it off with "autonomy off", or in the console.';
|
|
18724
|
+
var AUTONOMY_A5 = "Builds in this project wait for you again.";
|
|
18725
|
+
function withoutFinalStop(text) {
|
|
18726
|
+
return text.endsWith(".") ? text.slice(0, -1) : text;
|
|
18727
|
+
}
|
|
18728
|
+
function autonomyA9(what, id) {
|
|
18729
|
+
return `Halfcycle saved a copy before this: ${withoutFinalStop(what)}. To bring it back, run: ${HALFCYCLE_COMMAND} restore ${id} (kept for 7 days).`;
|
|
18730
|
+
}
|
|
18731
|
+
function autonomyA10(typeSentence, item) {
|
|
18732
|
+
return `${typeSentence} Builds run on their own here, but this still waits for the owner. Declare it with halfcycle_declare_owner_wait, item ${String(item)}, and carry on with other work.`;
|
|
18733
|
+
}
|
|
18734
|
+
function autonomyA11(typeSentence) {
|
|
18735
|
+
return `${typeSentence} This is never done while builds run on their own. Find a way that does not need the secrets.`;
|
|
18736
|
+
}
|
|
18737
|
+
function autonomyA12(typeSentence, reason) {
|
|
18738
|
+
return `${typeSentence} Halfcycle could not save a copy first (${reason}), so it was stopped. Nothing was changed.`;
|
|
18739
|
+
}
|
|
18740
|
+
var AUTONOMY_COULD_NOT_TELL_SENTENCE = "Halfcycle: This step can delete, overwrite, publish or show something that cannot be taken back, and Halfcycle could not work out what.";
|
|
18741
|
+
var AUTONOMY_COULD_NOT_TELL_REASON = "it could not tell what this would change";
|
|
18742
|
+
var AUTONOMY_SAVE_TOO_LARGE_REASON = "more than 200 MB of files would have to be copied";
|
|
18743
|
+
var AUTONOMY_SAVE_NO_HISTORY_REASON = "the project has no saved history yet";
|
|
18744
|
+
var AUTONOMY_SAVE_FAILED_REASON = "the copy could not be made";
|
|
18745
|
+
var AUTONOMY_SAVE_NOT_COVERED_REASON = "this kind of change cannot be saved yet";
|
|
18746
|
+
var AUTONOMY_A18 = "Halfcycle cannot check PowerShell commands while builds run on their own. Use Bash for this; nothing was changed.";
|
|
18747
|
+
|
|
18420
18748
|
// dist/hook-protocol.js
|
|
18421
18749
|
import { writeSync } from "node:fs";
|
|
18422
|
-
function emitBlock(reason) {
|
|
18423
|
-
|
|
18750
|
+
function emitBlock(reason, systemMessage) {
|
|
18751
|
+
const body = systemMessage === void 0 ? { decision: "block", reason } : { decision: "block", reason, systemMessage };
|
|
18752
|
+
process.stdout.write(JSON.stringify(body) + "\n");
|
|
18424
18753
|
process.exit(0);
|
|
18425
18754
|
}
|
|
18426
18755
|
function preToolUseReplyText(answer, text = "", agentLine) {
|
|
@@ -18441,40 +18770,65 @@ ${agentLine}`;
|
|
|
18441
18770
|
function preToolUseNoticeText(text) {
|
|
18442
18771
|
return JSON.stringify({ systemMessage: text });
|
|
18443
18772
|
}
|
|
18773
|
+
function preToolUseSavedText(text) {
|
|
18774
|
+
return JSON.stringify({ systemMessage: text, hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: text } });
|
|
18775
|
+
}
|
|
18444
18776
|
function nonBlockingReply(event, text) {
|
|
18445
|
-
if (event === "
|
|
18777
|
+
if (event === "SessionStart") {
|
|
18446
18778
|
return JSON.stringify({
|
|
18447
18779
|
systemMessage: text,
|
|
18448
18780
|
hookSpecificOutput: { hookEventName: event, additionalContext: text }
|
|
18449
18781
|
});
|
|
18450
18782
|
}
|
|
18783
|
+
if (event === "PostToolUse") {
|
|
18784
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } });
|
|
18785
|
+
}
|
|
18451
18786
|
return JSON.stringify({ systemMessage: text });
|
|
18452
18787
|
}
|
|
18453
18788
|
function hookReply(event) {
|
|
18454
18789
|
const queued = [];
|
|
18790
|
+
const forAgent = [];
|
|
18455
18791
|
return {
|
|
18456
18792
|
notice(text) {
|
|
18457
18793
|
queued.push(text);
|
|
18458
18794
|
},
|
|
18459
|
-
|
|
18795
|
+
agentContext(text) {
|
|
18796
|
+
forAgent.push(text);
|
|
18797
|
+
},
|
|
18798
|
+
block(reason, systemMessage) {
|
|
18460
18799
|
const carried = queued.splice(0);
|
|
18461
|
-
return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"));
|
|
18800
|
+
return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"), systemMessage);
|
|
18462
18801
|
},
|
|
18463
18802
|
flush() {
|
|
18464
|
-
if (queued.length === 0)
|
|
18465
|
-
return;
|
|
18466
18803
|
const text = queued.splice(0).join("\n");
|
|
18804
|
+
const context = event === "PostToolUse" ? forAgent.splice(0).join("\n") : "";
|
|
18467
18805
|
const code = process.exitCode;
|
|
18468
18806
|
if (code !== void 0 && code !== null && Number(code) !== 0) {
|
|
18469
|
-
|
|
18807
|
+
if (text !== "")
|
|
18808
|
+
writeSync(2, `${text}
|
|
18809
|
+
`);
|
|
18810
|
+
return;
|
|
18811
|
+
}
|
|
18812
|
+
if (context !== "") {
|
|
18813
|
+
process.stdout.write(`${postToolUseReply(text, context)}
|
|
18470
18814
|
`);
|
|
18471
18815
|
return;
|
|
18472
18816
|
}
|
|
18817
|
+
if (text === "")
|
|
18818
|
+
return;
|
|
18473
18819
|
process.stdout.write(`${nonBlockingReply(event, text)}
|
|
18474
18820
|
`);
|
|
18475
18821
|
}
|
|
18476
18822
|
};
|
|
18477
18823
|
}
|
|
18824
|
+
function postToolUseReply(notice, context) {
|
|
18825
|
+
const additionalContext = notice === "" ? context : `${notice}
|
|
18826
|
+
${context}`;
|
|
18827
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext } });
|
|
18828
|
+
}
|
|
18829
|
+
function userPromptContextReply(context) {
|
|
18830
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } });
|
|
18831
|
+
}
|
|
18478
18832
|
function infraWarningText() {
|
|
18479
18833
|
return "[Halfcycle] WARNING: Halfcycle's check service could not be reached, so nothing was checked this time. The session continues without checks.";
|
|
18480
18834
|
}
|
|
@@ -18827,7 +19181,8 @@ function spawnGit(args2, cwd, timeoutMs, extra) {
|
|
|
18827
19181
|
encoding: "utf-8",
|
|
18828
19182
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs },
|
|
18829
19183
|
...extra?.input === void 0 ? {} : { input: extra.input },
|
|
18830
|
-
...extra?.maxBuffer === void 0 ? {} : { maxBuffer: extra.maxBuffer }
|
|
19184
|
+
...extra?.maxBuffer === void 0 ? {} : { maxBuffer: extra.maxBuffer },
|
|
19185
|
+
...extra?.env === void 0 ? {} : { env: { ...process.env, ...extra.env } }
|
|
18831
19186
|
});
|
|
18832
19187
|
}
|
|
18833
19188
|
function git(args2, cwd, timeoutMs, extra) {
|
|
@@ -18903,6 +19258,9 @@ function remoteHeadLine(remote, repoRoot, timeoutMs, globalArgs = []) {
|
|
|
18903
19258
|
function readGitForFacts(args2, cwd, timeoutMs, input) {
|
|
18904
19259
|
return git(args2, cwd, timeoutMs, { maxBuffer: 64 * 1024 * 1024, ...input === void 0 ? {} : { input } });
|
|
18905
19260
|
}
|
|
19261
|
+
function gitForSave(args2, cwd, timeoutMs, extra = {}) {
|
|
19262
|
+
return git(args2, cwd, timeoutMs, { maxBuffer: 64 * 1024 * 1024, ...extra });
|
|
19263
|
+
}
|
|
18906
19264
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
18907
19265
|
function resolveDiffBase(repoRoot, explicit) {
|
|
18908
19266
|
const named = (explicit ?? "").trim();
|
|
@@ -18964,6 +19322,15 @@ function getRefToWorktreeChangedFiles(ref, repoRoot) {
|
|
|
18964
19322
|
const files = result.value.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
18965
19323
|
return { ok: true, value: files };
|
|
18966
19324
|
}
|
|
19325
|
+
function getUntrackedFiles(repoRoot, paths) {
|
|
19326
|
+
if (paths.length === 0)
|
|
19327
|
+
return { ok: true, value: [] };
|
|
19328
|
+
const pathspecs = paths.map((p) => `:(literal)${p}`);
|
|
19329
|
+
const result = git(["ls-files", "--others", "--exclude-standard", "-z", "--", ...pathspecs], repoRoot);
|
|
19330
|
+
if (!result.ok)
|
|
19331
|
+
return result;
|
|
19332
|
+
return { ok: true, value: result.value.split("\0").filter(Boolean) };
|
|
19333
|
+
}
|
|
18967
19334
|
var PROVE_GIT_TIMEOUT_MS = 3e4;
|
|
18968
19335
|
function pathsDifferingFromHead(repoRoot, timeoutMs = PROVE_GIT_TIMEOUT_MS) {
|
|
18969
19336
|
const result = git(["--no-optional-locks", "status", "--porcelain=v1", "-z", "--no-renames", "--untracked-files=all"], repoRoot, timeoutMs);
|
|
@@ -20767,6 +21134,17 @@ var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
|
20767
21134
|
var MARKER_PREFIX = "halfcycle-session-";
|
|
20768
21135
|
var MARKER_SUFFIX = ".ref";
|
|
20769
21136
|
var DEDUPE_SUFFIX = ".eval";
|
|
21137
|
+
var OWN_WORK_PREFIX = "halfcycle-own-";
|
|
21138
|
+
var OWN_WORK_SUFFIX = ".jsonl";
|
|
21139
|
+
var FINDINGS_PREFIX = "halfcycle-findings-";
|
|
21140
|
+
var FINDINGS_SUFFIX = ".json";
|
|
21141
|
+
var FINDINGS_ID_SOURCE = "^[A-Za-z0-9-]{1,128}$";
|
|
21142
|
+
var FINDINGS_ID = new RegExp(FINDINGS_ID_SOURCE);
|
|
21143
|
+
function findingsPath(sessionId) {
|
|
21144
|
+
if (typeof sessionId !== "string" || !FINDINGS_ID.test(sessionId))
|
|
21145
|
+
return null;
|
|
21146
|
+
return join6(markerDir(), `${FINDINGS_PREFIX}${sessionId}${FINDINGS_SUFFIX}`);
|
|
21147
|
+
}
|
|
20770
21148
|
function markerDir() {
|
|
20771
21149
|
const raw = process.env["TMPDIR"] || tmpdir();
|
|
20772
21150
|
return raw.replace(/\/+$/, "");
|
|
@@ -20873,6 +21251,7 @@ function digestChangeState(base, entries) {
|
|
|
20873
21251
|
function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now(), livenessCommon) {
|
|
20874
21252
|
if (livenessCommon !== void 0)
|
|
20875
21253
|
sweepLiveness(livenessCommon, now);
|
|
21254
|
+
sweepSessionFiles(keepSessionId, now);
|
|
20876
21255
|
const dir = markerDir();
|
|
20877
21256
|
const hash2 = repoHash(repoRoot);
|
|
20878
21257
|
const keepName = keepSessionId ? markerFileName(repoRoot, keepSessionId) : markerFileName(repoRoot);
|
|
@@ -20912,6 +21291,33 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now(), livene
|
|
|
20912
21291
|
}
|
|
20913
21292
|
}
|
|
20914
21293
|
}
|
|
21294
|
+
function sweepSessionFiles(keepSessionId, now) {
|
|
21295
|
+
const dir = markerDir();
|
|
21296
|
+
let names;
|
|
21297
|
+
try {
|
|
21298
|
+
names = readdirSync3(dir);
|
|
21299
|
+
} catch {
|
|
21300
|
+
return;
|
|
21301
|
+
}
|
|
21302
|
+
const kinds = [
|
|
21303
|
+
[FINDINGS_PREFIX, FINDINGS_SUFFIX],
|
|
21304
|
+
[OWN_WORK_PREFIX, OWN_WORK_SUFFIX]
|
|
21305
|
+
];
|
|
21306
|
+
for (const name of names) {
|
|
21307
|
+
for (const [prefix, suffix] of kinds) {
|
|
21308
|
+
if (!name.startsWith(prefix) || !name.endsWith(suffix))
|
|
21309
|
+
continue;
|
|
21310
|
+
if (keepSessionId !== void 0 && name === `${prefix}${keepSessionId}${suffix}`)
|
|
21311
|
+
continue;
|
|
21312
|
+
const full = join6(dir, name);
|
|
21313
|
+
try {
|
|
21314
|
+
if (now - statSync(full).mtimeMs > MARKER_TTL_MS)
|
|
21315
|
+
unlinkSync3(full);
|
|
21316
|
+
} catch {
|
|
21317
|
+
}
|
|
21318
|
+
}
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
20915
21321
|
function observeThrottlePath(folder) {
|
|
20916
21322
|
return join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}.observe-throttle`);
|
|
20917
21323
|
}
|
|
@@ -21788,6 +22194,21 @@ function parseTimeLog(text) {
|
|
|
21788
22194
|
}
|
|
21789
22195
|
return out;
|
|
21790
22196
|
}
|
|
22197
|
+
function readSessionTimeLines(sessionId, deps = {}) {
|
|
22198
|
+
try {
|
|
22199
|
+
const read = readConfig(deps.env ?? process.env);
|
|
22200
|
+
if (read.config === null)
|
|
22201
|
+
return [];
|
|
22202
|
+
const path = timeLogPath(read.config.guardEngagementId, deps.home);
|
|
22203
|
+
if (path === null)
|
|
22204
|
+
return [];
|
|
22205
|
+
const needle = JSON.stringify(sessionId);
|
|
22206
|
+
const kept = readFileSync11(path, "utf-8").split("\n").filter((raw) => raw.includes(needle));
|
|
22207
|
+
return parseTimeLog(kept.join("\n")).filter((line2) => line2.session === sessionId);
|
|
22208
|
+
} catch {
|
|
22209
|
+
return [];
|
|
22210
|
+
}
|
|
22211
|
+
}
|
|
21791
22212
|
function shortHash(text) {
|
|
21792
22213
|
return createHash2("sha256").update(text, "utf8").digest("hex").slice(0, 16);
|
|
21793
22214
|
}
|
|
@@ -21809,8 +22230,8 @@ function separatorOf(path) {
|
|
|
21809
22230
|
function subagentsFolderOf(transcript) {
|
|
21810
22231
|
if (!transcript.endsWith(".jsonl") || transcript.length === ".jsonl".length)
|
|
21811
22232
|
return null;
|
|
21812
|
-
const
|
|
21813
|
-
return `${transcript.slice(0, -".jsonl".length)}${
|
|
22233
|
+
const sep5 = separatorOf(transcript);
|
|
22234
|
+
return `${transcript.slice(0, -".jsonl".length)}${sep5}subagents`;
|
|
21814
22235
|
}
|
|
21815
22236
|
function subagentRecordName(agentId) {
|
|
21816
22237
|
return /^[A-Za-z0-9_-]+$/.test(agentId) ? `agent-${agentId}.jsonl` : null;
|
|
@@ -22350,7 +22771,7 @@ async function pruneTimeLog(path, openedAt) {
|
|
|
22350
22771
|
written = true;
|
|
22351
22772
|
let seen = Buffer.byteLength(text, "utf-8");
|
|
22352
22773
|
for (; ; ) {
|
|
22353
|
-
await new Promise((
|
|
22774
|
+
await new Promise((resolve8) => setTimeout(resolve8, PRUNE_SETTLE_MS));
|
|
22354
22775
|
const after = readFileSync11(aside);
|
|
22355
22776
|
if (after.length <= seen)
|
|
22356
22777
|
break;
|
|
@@ -22468,101 +22889,634 @@ async function sendTimeRecord(deps = {}) {
|
|
|
22468
22889
|
}
|
|
22469
22890
|
}
|
|
22470
22891
|
|
|
22471
|
-
// dist/
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22892
|
+
// dist/own-work.js
|
|
22893
|
+
import { appendFileSync as appendFileSync3, lstatSync, readFileSync as readFileSync12, realpathSync as realpathSync4 } from "node:fs";
|
|
22894
|
+
import { basename, dirname as dirname5, isAbsolute, join as join10, relative as relative4, resolve as resolve3, sep as sep2 } from "node:path";
|
|
22895
|
+
|
|
22896
|
+
// dist/risky/types.js
|
|
22897
|
+
var RISKY_ACTION_TYPES = [
|
|
22898
|
+
"delete-files",
|
|
22899
|
+
"discard-unsaved",
|
|
22900
|
+
"rewrite-shared-history",
|
|
22901
|
+
"delete-unmerged-work",
|
|
22902
|
+
"wipe-data",
|
|
22903
|
+
"read-credentials",
|
|
22904
|
+
"publish",
|
|
22905
|
+
"deploy",
|
|
22906
|
+
"shrink-record",
|
|
22907
|
+
"outside-own-copy"
|
|
22908
|
+
];
|
|
22909
|
+
function riskyTypeOrder(type) {
|
|
22910
|
+
return RISKY_ACTION_TYPES.indexOf(type);
|
|
22477
22911
|
}
|
|
22478
|
-
|
|
22479
|
-
|
|
22912
|
+
var CHECKED_TOOLS = ["Bash", "Read", "Grep", "Write", "Edit", "MultiEdit", "NotebookEdit", "Agent"];
|
|
22913
|
+
var RECORDED_ONLY_TOOLS = ["PowerShell"];
|
|
22914
|
+
|
|
22915
|
+
// dist/own-work.js
|
|
22916
|
+
var OWN_WORK_SHELL_TOOLS = ["Bash", "PowerShell"];
|
|
22917
|
+
var SHELL = new Set(OWN_WORK_SHELL_TOOLS);
|
|
22918
|
+
var MATCHED = /* @__PURE__ */ new Set([...CHECKED_TOOLS, ...RECORDED_ONLY_TOOLS]);
|
|
22919
|
+
function isOwnWorkShellTool(toolName) {
|
|
22920
|
+
return typeof toolName === "string" && SHELL.has(toolName) && MATCHED.has(toolName);
|
|
22921
|
+
}
|
|
22922
|
+
function ownWorkFileName(sessionId) {
|
|
22923
|
+
return `${OWN_WORK_PREFIX}${sessionId}${OWN_WORK_SUFFIX}`;
|
|
22924
|
+
}
|
|
22925
|
+
function ownWorkPath(sessionId, dir) {
|
|
22926
|
+
if (typeof sessionId !== "string" || !livenessIdOk(sessionId))
|
|
22927
|
+
return null;
|
|
22928
|
+
return join10(dir ?? markerDir(), ownWorkFileName(sessionId));
|
|
22929
|
+
}
|
|
22930
|
+
function inputString2(input, key) {
|
|
22931
|
+
if (typeof input !== "object" || input === null)
|
|
22932
|
+
return null;
|
|
22933
|
+
const value = input[key];
|
|
22934
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
22935
|
+
}
|
|
22936
|
+
function ownShellLine(input, at) {
|
|
22937
|
+
return { at: at.toISOString(), kind: "shell", agent: inputString2(input, "agent_id"), toolUse: inputString2(input, "tool_use_id") };
|
|
22938
|
+
}
|
|
22939
|
+
function ownEditLine(path, at) {
|
|
22940
|
+
return { at: at.toISOString(), kind: "edit", path };
|
|
22941
|
+
}
|
|
22942
|
+
function append(sessionId, line2, dir) {
|
|
22943
|
+
const path = ownWorkPath(sessionId, dir);
|
|
22944
|
+
if (path === null)
|
|
22945
|
+
return;
|
|
22946
|
+
appendFileSync3(path, `${JSON.stringify(line2)}
|
|
22947
|
+
`, { mode: 384 });
|
|
22948
|
+
}
|
|
22949
|
+
function recordOwnShell(input, at, deps = {}) {
|
|
22480
22950
|
try {
|
|
22481
|
-
|
|
22482
|
-
|
|
22951
|
+
if (!isOwnWorkShellTool(inputString2(input, "tool_name")))
|
|
22952
|
+
return;
|
|
22953
|
+
append(inputString2(input, "session_id"), ownShellLine(input, at), deps.dir);
|
|
22483
22954
|
} catch {
|
|
22484
|
-
recordTimeEvent("edit", {});
|
|
22485
|
-
return;
|
|
22486
22955
|
}
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
22495
|
-
|
|
22496
|
-
|
|
22497
|
-
|
|
22498
|
-
|
|
22956
|
+
}
|
|
22957
|
+
function recordOwnEdit(input, filePath, deps = {}) {
|
|
22958
|
+
try {
|
|
22959
|
+
if (filePath === "")
|
|
22960
|
+
return;
|
|
22961
|
+
const base = inputString2(input, "cwd") ?? process.cwd();
|
|
22962
|
+
const path = isAbsolute(filePath) ? filePath : resolve3(base, filePath);
|
|
22963
|
+
append(inputString2(input, "session_id"), ownEditLine(path, (deps.now ?? (() => /* @__PURE__ */ new Date()))()), deps.dir);
|
|
22964
|
+
} catch {
|
|
22965
|
+
}
|
|
22966
|
+
}
|
|
22967
|
+
function parseOwnWork(text) {
|
|
22968
|
+
const out = [];
|
|
22969
|
+
for (const raw of text.split("\n")) {
|
|
22970
|
+
if (raw === "")
|
|
22971
|
+
continue;
|
|
22972
|
+
try {
|
|
22973
|
+
const o = JSON.parse(raw);
|
|
22974
|
+
if (typeof o["at"] !== "string")
|
|
22975
|
+
continue;
|
|
22976
|
+
const nullable2 = (v) => v === null || typeof v === "string";
|
|
22977
|
+
if (o["kind"] === "edit" && typeof o["path"] === "string")
|
|
22978
|
+
out.push({ at: o["at"], kind: "edit", path: o["path"] });
|
|
22979
|
+
else if (o["kind"] === "shell" && nullable2(o["agent"]) && nullable2(o["toolUse"])) {
|
|
22980
|
+
out.push({ at: o["at"], kind: "shell", agent: o["agent"], toolUse: o["toolUse"] });
|
|
22981
|
+
}
|
|
22982
|
+
} catch {
|
|
22499
22983
|
}
|
|
22500
|
-
return;
|
|
22501
22984
|
}
|
|
22502
|
-
|
|
22503
|
-
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
|
|
22508
|
-
|
|
22509
|
-
|
|
22510
|
-
|
|
22511
|
-
|
|
22512
|
-
});
|
|
22513
|
-
const configResult = readConfig();
|
|
22514
|
-
if (!configResult.config) {
|
|
22515
|
-
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
22516
|
-
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
22517
|
-
return;
|
|
22518
|
-
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
22519
|
-
return;
|
|
22985
|
+
return out;
|
|
22986
|
+
}
|
|
22987
|
+
function readOwnWork(sessionId, dir) {
|
|
22988
|
+
try {
|
|
22989
|
+
const path = ownWorkPath(sessionId, dir);
|
|
22990
|
+
if (path === null)
|
|
22991
|
+
return [];
|
|
22992
|
+
return parseOwnWork(readFileSync12(path, "utf-8"));
|
|
22993
|
+
} catch {
|
|
22994
|
+
return [];
|
|
22520
22995
|
}
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
const
|
|
22524
|
-
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22533
|
-
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22541
|
-
|
|
22542
|
-
if (failure.kind === "credential") {
|
|
22543
|
-
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
22544
|
-
return;
|
|
22545
|
-
emitCredentialRejected(
|
|
22546
|
-
failure.statusCode,
|
|
22547
|
-
failure.message,
|
|
22548
|
-
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
22549
|
-
// The remedy the renewal attempt earned: the sign-in when the account
|
|
22550
|
-
// credential is the thing that was refused, and otherwise the plane's own
|
|
22551
|
-
// sentence about why it would not mint.
|
|
22552
|
-
evaluation.remedy
|
|
22553
|
-
);
|
|
22554
|
-
return;
|
|
22996
|
+
}
|
|
22997
|
+
function shellWindows(own, timeLines, sessionId, passAt) {
|
|
22998
|
+
const out = [];
|
|
22999
|
+
for (const line2 of own) {
|
|
23000
|
+
if (line2.kind !== "shell")
|
|
23001
|
+
continue;
|
|
23002
|
+
const from = Date.parse(line2.at);
|
|
23003
|
+
if (Number.isNaN(from))
|
|
23004
|
+
continue;
|
|
23005
|
+
let to = passAt;
|
|
23006
|
+
for (const t of timeLines) {
|
|
23007
|
+
if (t.session !== sessionId || t.agent !== line2.agent)
|
|
23008
|
+
continue;
|
|
23009
|
+
const isOwnLine = line2.toolUse !== null ? t.toolUse === line2.toolUse : t.at === line2.at;
|
|
23010
|
+
if (isOwnLine)
|
|
23011
|
+
continue;
|
|
23012
|
+
const at = Date.parse(t.at);
|
|
23013
|
+
if (Number.isNaN(at) || at < from)
|
|
23014
|
+
continue;
|
|
23015
|
+
if (at < to)
|
|
23016
|
+
to = at;
|
|
22555
23017
|
}
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
23018
|
+
out.push({ from, to: Math.max(from, to) });
|
|
23019
|
+
}
|
|
23020
|
+
return out;
|
|
23021
|
+
}
|
|
23022
|
+
function realPathOf(path) {
|
|
23023
|
+
const missing = [];
|
|
23024
|
+
let at = resolve3(path);
|
|
23025
|
+
for (; ; ) {
|
|
23026
|
+
try {
|
|
23027
|
+
return join10(realpathSync4.native(at), ...missing.reverse());
|
|
23028
|
+
} catch {
|
|
23029
|
+
const parent = dirname5(at);
|
|
23030
|
+
if (parent === at)
|
|
23031
|
+
return resolve3(path);
|
|
23032
|
+
missing.push(basename(at));
|
|
23033
|
+
at = parent;
|
|
22559
23034
|
}
|
|
22560
|
-
reply.notice(infraWarningText());
|
|
22561
|
-
return;
|
|
22562
23035
|
}
|
|
22563
|
-
|
|
22564
|
-
|
|
22565
|
-
|
|
23036
|
+
}
|
|
23037
|
+
function modifiedAt(path) {
|
|
23038
|
+
try {
|
|
23039
|
+
return lstatSync(path).mtimeMs;
|
|
23040
|
+
} catch {
|
|
23041
|
+
return null;
|
|
23042
|
+
}
|
|
23043
|
+
}
|
|
23044
|
+
function ownEditedFiles(repoRoot, sessionId, deps = {}) {
|
|
23045
|
+
const root = realPathOf(repoRoot);
|
|
23046
|
+
const out = [];
|
|
23047
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23048
|
+
for (const line2 of readOwnWork(sessionId, deps.dir)) {
|
|
23049
|
+
if (line2.kind !== "edit")
|
|
23050
|
+
continue;
|
|
23051
|
+
const abs = realPathOf(line2.path);
|
|
23052
|
+
const rel = relative4(root, abs);
|
|
23053
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute(rel))
|
|
23054
|
+
continue;
|
|
23055
|
+
if (modifiedAt(abs) === null)
|
|
23056
|
+
continue;
|
|
23057
|
+
const posix3 = rel.split(sep2).join("/");
|
|
23058
|
+
if (seen.has(posix3))
|
|
23059
|
+
continue;
|
|
23060
|
+
seen.add(posix3);
|
|
23061
|
+
out.push(posix3);
|
|
23062
|
+
}
|
|
23063
|
+
return out;
|
|
23064
|
+
}
|
|
23065
|
+
function ownChangedFiles(repoRoot, files, sessionId, deps = {}) {
|
|
23066
|
+
if (typeof sessionId !== "string" || ownWorkPath(sessionId, deps.dir) === null)
|
|
23067
|
+
return [...files];
|
|
23068
|
+
const own = readOwnWork(sessionId, deps.dir);
|
|
23069
|
+
if (own.length === 0)
|
|
23070
|
+
return [];
|
|
23071
|
+
const edited = /* @__PURE__ */ new Set();
|
|
23072
|
+
for (const line2 of own)
|
|
23073
|
+
if (line2.kind === "edit")
|
|
23074
|
+
edited.add(realPathOf(line2.path));
|
|
23075
|
+
const hasShell = own.some((line2) => line2.kind === "shell");
|
|
23076
|
+
const passAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
23077
|
+
const timeLines = hasShell ? readSessionTimeLines(sessionId, { env: deps.env, home: deps.home }) : [];
|
|
23078
|
+
const windows = hasShell ? shellWindows(own, timeLines, sessionId, passAt) : [];
|
|
23079
|
+
const inWindow = (mtimeMs) => {
|
|
23080
|
+
if (mtimeMs === null)
|
|
23081
|
+
return false;
|
|
23082
|
+
const ms = Math.floor(mtimeMs);
|
|
23083
|
+
return windows.some((w) => ms >= w.from && ms <= w.to);
|
|
23084
|
+
};
|
|
23085
|
+
const root = resolve3(repoRoot);
|
|
23086
|
+
return files.filter((rel) => {
|
|
23087
|
+
const abs = join10(root, rel);
|
|
23088
|
+
if (edited.has(realPathOf(abs)))
|
|
23089
|
+
return true;
|
|
23090
|
+
if (windows.length === 0)
|
|
23091
|
+
return false;
|
|
23092
|
+
const mtime = modifiedAt(abs);
|
|
23093
|
+
if (mtime !== null)
|
|
23094
|
+
return inWindow(mtime);
|
|
23095
|
+
return inWindow(modifiedAt(dirname5(abs)));
|
|
23096
|
+
});
|
|
23097
|
+
}
|
|
23098
|
+
|
|
23099
|
+
// dist/hooks/findings.js
|
|
23100
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
23101
|
+
import { closeSync, existsSync as existsSync6, openSync, readFileSync as readFileSync13, renameSync as renameSync4, statSync as statSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
23102
|
+
function findingKey(firing) {
|
|
23103
|
+
return createHash3("sha256").update(`${firing.guardId}
|
|
23104
|
+
${firing.path ?? ""}
|
|
23105
|
+
${firing.explanation}`).digest("hex");
|
|
23106
|
+
}
|
|
23107
|
+
function asFinding(raw) {
|
|
23108
|
+
if (typeof raw !== "object" || raw === null)
|
|
23109
|
+
return null;
|
|
23110
|
+
const o = raw;
|
|
23111
|
+
const str = (k) => typeof o[k] === "string";
|
|
23112
|
+
const bool = (k) => typeof o[k] === "boolean";
|
|
23113
|
+
if (!str("key") || !str("folder") || !str("guardId") || !str("patternRef") || !str("explanation") || !str("foundAt"))
|
|
23114
|
+
return null;
|
|
23115
|
+
if (o["severity"] !== "info" && o["severity"] !== "warn")
|
|
23116
|
+
return null;
|
|
23117
|
+
if (!bool("announced") || !bool("delivered") || !bool("escalated"))
|
|
23118
|
+
return null;
|
|
23119
|
+
if (o["path"] !== void 0 && typeof o["path"] !== "string")
|
|
23120
|
+
return null;
|
|
23121
|
+
return {
|
|
23122
|
+
key: o["key"],
|
|
23123
|
+
folder: o["folder"],
|
|
23124
|
+
guardId: o["guardId"],
|
|
23125
|
+
patternRef: o["patternRef"],
|
|
23126
|
+
severity: o["severity"],
|
|
23127
|
+
explanation: o["explanation"],
|
|
23128
|
+
...typeof o["path"] === "string" ? { path: o["path"] } : {},
|
|
23129
|
+
foundAt: o["foundAt"],
|
|
23130
|
+
announced: o["announced"],
|
|
23131
|
+
delivered: o["delivered"],
|
|
23132
|
+
escalated: o["escalated"]
|
|
23133
|
+
};
|
|
23134
|
+
}
|
|
23135
|
+
function emptyFindings(session) {
|
|
23136
|
+
return { version: 1, session, findings: [] };
|
|
23137
|
+
}
|
|
23138
|
+
function readFindings(sessionId) {
|
|
23139
|
+
const path = findingsPath(sessionId);
|
|
23140
|
+
if (path === null)
|
|
23141
|
+
return null;
|
|
23142
|
+
const session = sessionId;
|
|
23143
|
+
try {
|
|
23144
|
+
const parsed = JSON.parse(readFileSync13(path, "utf-8"));
|
|
23145
|
+
if (parsed["version"] !== 1 || !Array.isArray(parsed["findings"]))
|
|
23146
|
+
return emptyFindings(session);
|
|
23147
|
+
const findings = [];
|
|
23148
|
+
for (const raw of parsed["findings"]) {
|
|
23149
|
+
const f = asFinding(raw);
|
|
23150
|
+
if (f !== null)
|
|
23151
|
+
findings.push(f);
|
|
23152
|
+
}
|
|
23153
|
+
return { version: 1, session, findings };
|
|
23154
|
+
} catch {
|
|
23155
|
+
return emptyFindings(session);
|
|
23156
|
+
}
|
|
23157
|
+
}
|
|
23158
|
+
function writeFindings(file2) {
|
|
23159
|
+
const path = findingsPath(file2.session);
|
|
23160
|
+
if (path === null)
|
|
23161
|
+
return;
|
|
23162
|
+
const temp = `${path.slice(0, -".json".length)}.${process.pid}.tmp.json`;
|
|
23163
|
+
try {
|
|
23164
|
+
writeFileSync6(temp, `${JSON.stringify(file2)}
|
|
23165
|
+
`, { mode: 384 });
|
|
23166
|
+
renameSync4(temp, path);
|
|
23167
|
+
} catch {
|
|
23168
|
+
try {
|
|
23169
|
+
unlinkSync5(temp);
|
|
23170
|
+
} catch {
|
|
23171
|
+
}
|
|
23172
|
+
}
|
|
23173
|
+
}
|
|
23174
|
+
var FINDINGS_LOCK_WAIT_MS = 1e3;
|
|
23175
|
+
var FINDINGS_LOCK_STALE_MS = 5e3;
|
|
23176
|
+
function lockPathFor(path) {
|
|
23177
|
+
return `${path.slice(0, -".json".length)}.lock.json`;
|
|
23178
|
+
}
|
|
23179
|
+
function pause(ms) {
|
|
23180
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23181
|
+
}
|
|
23182
|
+
function takeOverStaleLock(lock) {
|
|
23183
|
+
const aside = `${lock}.${process.pid}.${randomUUID2()}.stale`;
|
|
23184
|
+
try {
|
|
23185
|
+
renameSync4(lock, aside);
|
|
23186
|
+
} catch {
|
|
23187
|
+
return false;
|
|
23188
|
+
}
|
|
23189
|
+
let staleIndeed = false;
|
|
23190
|
+
try {
|
|
23191
|
+
staleIndeed = Date.now() - statSync3(aside).mtimeMs > FINDINGS_LOCK_STALE_MS;
|
|
23192
|
+
} catch {
|
|
23193
|
+
staleIndeed = false;
|
|
23194
|
+
}
|
|
23195
|
+
if (!staleIndeed) {
|
|
23196
|
+
try {
|
|
23197
|
+
renameSync4(aside, lock);
|
|
23198
|
+
} catch {
|
|
23199
|
+
try {
|
|
23200
|
+
unlinkSync5(aside);
|
|
23201
|
+
} catch {
|
|
23202
|
+
}
|
|
23203
|
+
}
|
|
23204
|
+
return false;
|
|
23205
|
+
}
|
|
23206
|
+
try {
|
|
23207
|
+
unlinkSync5(aside);
|
|
23208
|
+
} catch {
|
|
23209
|
+
}
|
|
23210
|
+
return true;
|
|
23211
|
+
}
|
|
23212
|
+
function withFindingsLock(sessionId, body, waitMs = FINDINGS_LOCK_WAIT_MS) {
|
|
23213
|
+
const path = findingsPath(sessionId);
|
|
23214
|
+
if (path === null)
|
|
23215
|
+
return void 0;
|
|
23216
|
+
const lock = lockPathFor(path);
|
|
23217
|
+
const deadline = Date.now() + waitMs;
|
|
23218
|
+
for (; ; ) {
|
|
23219
|
+
try {
|
|
23220
|
+
closeSync(openSync(lock, "wx", 384));
|
|
23221
|
+
break;
|
|
23222
|
+
} catch (err) {
|
|
23223
|
+
if (err.code !== "EEXIST")
|
|
23224
|
+
return void 0;
|
|
23225
|
+
let stale = false;
|
|
23226
|
+
try {
|
|
23227
|
+
stale = Date.now() - statSync3(lock).mtimeMs > FINDINGS_LOCK_STALE_MS;
|
|
23228
|
+
} catch {
|
|
23229
|
+
continue;
|
|
23230
|
+
}
|
|
23231
|
+
if (stale && takeOverStaleLock(lock))
|
|
23232
|
+
continue;
|
|
23233
|
+
if (Date.now() >= deadline)
|
|
23234
|
+
return void 0;
|
|
23235
|
+
pause(10);
|
|
23236
|
+
}
|
|
23237
|
+
}
|
|
23238
|
+
try {
|
|
23239
|
+
return body();
|
|
23240
|
+
} finally {
|
|
23241
|
+
try {
|
|
23242
|
+
unlinkSync5(lock);
|
|
23243
|
+
} catch {
|
|
23244
|
+
}
|
|
23245
|
+
}
|
|
23246
|
+
}
|
|
23247
|
+
function takeUndelivered(file2, folder) {
|
|
23248
|
+
const taken = [];
|
|
23249
|
+
const findings = file2.findings.map((f) => {
|
|
23250
|
+
if (f.delivered || folder !== null && f.folder !== folder)
|
|
23251
|
+
return f;
|
|
23252
|
+
taken.push(f);
|
|
23253
|
+
return { ...f, delivered: true };
|
|
23254
|
+
});
|
|
23255
|
+
return { file: { ...file2, findings }, taken };
|
|
23256
|
+
}
|
|
23257
|
+
function deliverFindings(sessionId, folder) {
|
|
23258
|
+
const path = findingsPath(sessionId);
|
|
23259
|
+
if (path === null || !existsSync6(path))
|
|
23260
|
+
return [];
|
|
23261
|
+
try {
|
|
23262
|
+
const taken = withFindingsLock(sessionId, () => {
|
|
23263
|
+
const before = readFindings(sessionId);
|
|
23264
|
+
if (before === null)
|
|
23265
|
+
return [];
|
|
23266
|
+
const { file: file2, taken: given } = takeUndelivered(before, folder);
|
|
23267
|
+
if (given.length > 0)
|
|
23268
|
+
writeFindings(file2);
|
|
23269
|
+
return given;
|
|
23270
|
+
});
|
|
23271
|
+
return taken ?? [];
|
|
23272
|
+
} catch {
|
|
23273
|
+
return [];
|
|
23274
|
+
}
|
|
23275
|
+
}
|
|
23276
|
+
function turnEndBookkeeping(file2, folder, outcome, topLevel, now) {
|
|
23277
|
+
const recorded = [...file2.findings];
|
|
23278
|
+
const firing = /* @__PURE__ */ new Map();
|
|
23279
|
+
const notRun = new Set(outcome.kind === "evaluated" ? outcome.notRun : []);
|
|
23280
|
+
const observed = (f) => f.folder === folder && !notRun.has(f.patternRef);
|
|
23281
|
+
if (outcome.kind === "evaluated") {
|
|
23282
|
+
for (const g of outcome.fired) {
|
|
23283
|
+
if (g.severity === "block")
|
|
23284
|
+
continue;
|
|
23285
|
+
const key = findingKey(g);
|
|
23286
|
+
if (!firing.has(key))
|
|
23287
|
+
firing.set(key, g);
|
|
23288
|
+
}
|
|
23289
|
+
}
|
|
23290
|
+
const removed = [];
|
|
23291
|
+
const kept = [];
|
|
23292
|
+
for (const f of recorded) {
|
|
23293
|
+
const closes = observed(f) && (outcome.kind === "no-own-changes" || outcome.kind === "evaluated" && !firing.has(f.key));
|
|
23294
|
+
(closes ? removed : kept).push(f);
|
|
23295
|
+
}
|
|
23296
|
+
const known = new Set(kept.filter((f) => f.folder === folder).map((f) => f.key));
|
|
23297
|
+
for (const [key, g] of firing) {
|
|
23298
|
+
if (known.has(key))
|
|
23299
|
+
continue;
|
|
23300
|
+
kept.push({
|
|
23301
|
+
key,
|
|
23302
|
+
folder,
|
|
23303
|
+
guardId: g.guardId,
|
|
23304
|
+
patternRef: g.patternRef,
|
|
23305
|
+
severity: g.severity,
|
|
23306
|
+
explanation: g.explanation,
|
|
23307
|
+
...g.path !== void 0 ? { path: g.path } : {},
|
|
23308
|
+
foundAt: now.toISOString(),
|
|
23309
|
+
announced: false,
|
|
23310
|
+
delivered: false,
|
|
23311
|
+
escalated: false
|
|
23312
|
+
});
|
|
23313
|
+
}
|
|
23314
|
+
if (!topLevel) {
|
|
23315
|
+
return { file: { ...file2, findings: kept }, news: { fixed: 0, allFixed: false, found: 0, stillOpen: [] } };
|
|
23316
|
+
}
|
|
23317
|
+
const fixed = removed.filter((f) => f.announced).length;
|
|
23318
|
+
const allFixed = !kept.some((f) => f.announced);
|
|
23319
|
+
let found2 = 0;
|
|
23320
|
+
const stillOpen = [];
|
|
23321
|
+
const next = kept.map((f) => {
|
|
23322
|
+
if (!f.announced) {
|
|
23323
|
+
found2 += 1;
|
|
23324
|
+
return { ...f, announced: true };
|
|
23325
|
+
}
|
|
23326
|
+
if (f.delivered && !f.escalated && observed(f)) {
|
|
23327
|
+
stillOpen.push(f.explanation);
|
|
23328
|
+
return { ...f, escalated: true };
|
|
23329
|
+
}
|
|
23330
|
+
return f;
|
|
23331
|
+
});
|
|
23332
|
+
return { file: { ...file2, findings: next }, news: { fixed, allFixed, found: found2, stillOpen } };
|
|
23333
|
+
}
|
|
23334
|
+
function blockingExplanations(fired) {
|
|
23335
|
+
const blocks = fired.filter((g) => g.severity === "block");
|
|
23336
|
+
return (blocks.length > 0 ? blocks : fired).map((g) => g.explanation);
|
|
23337
|
+
}
|
|
23338
|
+
|
|
23339
|
+
// dist/hooks/finding-lines.js
|
|
23340
|
+
function bullets(explanations) {
|
|
23341
|
+
return explanations.map((e) => `- ${e}`).join("\n");
|
|
23342
|
+
}
|
|
23343
|
+
function distinctBullets(explanations) {
|
|
23344
|
+
return bullets([...new Set(explanations)]);
|
|
23345
|
+
}
|
|
23346
|
+
function foundLine(count) {
|
|
23347
|
+
return count === 1 ? "Halfcycle found 1 small thing to fix in this turn's work. The agent gets the details with your next message and will fix it; there is nothing for you to do." : `Halfcycle found ${count} small things to fix in this turn's work. The agent gets the details with your next message and will fix them; there is nothing for you to do.`;
|
|
23348
|
+
}
|
|
23349
|
+
function fixedLine(count, all) {
|
|
23350
|
+
if (all)
|
|
23351
|
+
return "Halfcycle: everything it found earlier is now fixed.";
|
|
23352
|
+
return count === 1 ? "Halfcycle: 1 of the things it found earlier is now fixed." : `Halfcycle: ${count} of the things it found earlier are now fixed.`;
|
|
23353
|
+
}
|
|
23354
|
+
function stillOpenMessage(explanations) {
|
|
23355
|
+
const n = explanations.length;
|
|
23356
|
+
const head = n === 1 ? "Halfcycle: 1 thing it found is still not fixed after the agent's last turn." : `Halfcycle: ${n} things it found are still not fixed after the agent's last turn.`;
|
|
23357
|
+
const tail = n === 1 ? "Ask the agent to fix it, or tell it to leave it as it is if that is what you want." : "Ask the agent to fix them, or tell it to leave them as they are if that is what you want.";
|
|
23358
|
+
return `${head}
|
|
23359
|
+
${bullets(explanations)}
|
|
23360
|
+
${tail}`;
|
|
23361
|
+
}
|
|
23362
|
+
function stoppedMessage(explanations) {
|
|
23363
|
+
return [
|
|
23364
|
+
"Halfcycle stopped this step because something in the agent's work has to be fixed first:",
|
|
23365
|
+
distinctBullets(explanations),
|
|
23366
|
+
"The agent has the details and is fixing it now. If it is still stopped after the agent's next try, ask the agent what is in the way."
|
|
23367
|
+
].join("\n");
|
|
23368
|
+
}
|
|
23369
|
+
function couldNotFixMessage(explanations) {
|
|
23370
|
+
return [
|
|
23371
|
+
"Halfcycle: the agent could not fix what stopped its work:",
|
|
23372
|
+
distinctBullets(explanations),
|
|
23373
|
+
"Ask the agent what is in the way, or tell it how you want this handled."
|
|
23374
|
+
].join("\n");
|
|
23375
|
+
}
|
|
23376
|
+
function notRunMessage(reasons) {
|
|
23377
|
+
if (reasons.length === 0)
|
|
23378
|
+
return "";
|
|
23379
|
+
const head = reasons.length === 1 ? "Halfcycle: 1 check could not run on this change." : `Halfcycle: ${reasons.length} checks could not run on this change.`;
|
|
23380
|
+
return `${head}
|
|
23381
|
+
${distinctBullets(reasons)}`;
|
|
23382
|
+
}
|
|
23383
|
+
function blockReason(explanations) {
|
|
23384
|
+
return `Halfcycle stopped this step. Fix each of these before you carry on:
|
|
23385
|
+
${distinctBullets(explanations)}`;
|
|
23386
|
+
}
|
|
23387
|
+
function detailsBlock(findings, projectFolder2) {
|
|
23388
|
+
const lines2 = findings.map((f) => {
|
|
23389
|
+
const where = f.folder === projectFolder2 ? f.path : f.path === void 0 ? f.folder : `${f.folder}/${f.path}`;
|
|
23390
|
+
const at = where === void 0 ? "" : ` in ${where}`;
|
|
23391
|
+
return ` [${f.severity.toUpperCase()}] ${f.patternRef}${at}: ${f.explanation}`;
|
|
23392
|
+
});
|
|
23393
|
+
return [
|
|
23394
|
+
"Halfcycle's checks found these in this session's own changes at the end of an earlier turn. Fix each one now, as part of this turn, before anything else; then carry on with what the person asks. Do not ask the person whether to fix them, and do not end your turn with a question before they are fixed. If the person later tells you to keep one, that is their call. When you mention them to the person, use plain words, not the labels in brackets.",
|
|
23395
|
+
...lines2
|
|
23396
|
+
].join("\n");
|
|
23397
|
+
}
|
|
23398
|
+
function turnEndMessage(news) {
|
|
23399
|
+
const parts = [];
|
|
23400
|
+
if (news.fixed > 0)
|
|
23401
|
+
parts.push(fixedLine(news.fixed, news.allFixed));
|
|
23402
|
+
if (news.found > 0)
|
|
23403
|
+
parts.push(foundLine(news.found));
|
|
23404
|
+
if (news.stillOpen.length > 0)
|
|
23405
|
+
parts.push(stillOpenMessage(news.stillOpen));
|
|
23406
|
+
return parts.length === 0 ? null : parts.join("\n");
|
|
23407
|
+
}
|
|
23408
|
+
|
|
23409
|
+
// dist/hooks/post-tool-use.js
|
|
23410
|
+
var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
23411
|
+
async function runPostToolUse(readSeat = readSeatConfiguration) {
|
|
23412
|
+
const reply = hookReply("PostToolUse");
|
|
23413
|
+
const at = { session: void 0, folder: null };
|
|
23414
|
+
await evaluateEdit(reply, readSeat, at);
|
|
23415
|
+
handOverAtEdit(reply, at);
|
|
23416
|
+
reply.flush();
|
|
23417
|
+
}
|
|
23418
|
+
function handOverAtEdit(reply, at) {
|
|
23419
|
+
if (at.folder === null)
|
|
23420
|
+
return;
|
|
23421
|
+
const code = process.exitCode;
|
|
23422
|
+
if (code !== void 0 && code !== null && Number(code) !== 0)
|
|
23423
|
+
return;
|
|
23424
|
+
const given = deliverFindings(at.session, at.folder);
|
|
23425
|
+
if (given.length === 0)
|
|
23426
|
+
return;
|
|
23427
|
+
reply.agentContext(detailsBlock(given, realPathOf(projectFolder())));
|
|
23428
|
+
}
|
|
23429
|
+
async function evaluateEdit(reply, readSeat, at) {
|
|
23430
|
+
let input;
|
|
23431
|
+
try {
|
|
23432
|
+
const raw = await readStdin();
|
|
23433
|
+
input = JSON.parse(raw);
|
|
23434
|
+
} catch {
|
|
23435
|
+
recordTimeEvent("edit", {});
|
|
23436
|
+
return;
|
|
23437
|
+
}
|
|
23438
|
+
recordTimeEvent("edit", input);
|
|
23439
|
+
const toolName = input.tool_name ?? "";
|
|
23440
|
+
if (!HANDLED_EDIT_TOOLS.has(toolName))
|
|
23441
|
+
return;
|
|
23442
|
+
const isNotebook = toolName === "NotebookEdit";
|
|
23443
|
+
const absFilePath = isNotebook ? input.tool_input?.notebook_path : input.tool_input?.file_path;
|
|
23444
|
+
if (!absFilePath || typeof absFilePath !== "string")
|
|
23445
|
+
return;
|
|
23446
|
+
recordOwnEdit(input, absFilePath);
|
|
23447
|
+
const repoRootResult = getRepoRoot(process.cwd());
|
|
23448
|
+
if (!repoRootResult.ok) {
|
|
23449
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
23450
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
23451
|
+
}
|
|
23452
|
+
return;
|
|
23453
|
+
}
|
|
23454
|
+
const repoRoot = repoRootResult.value;
|
|
23455
|
+
at.session = input.session_id;
|
|
23456
|
+
at.folder = realPathOf(repoRoot);
|
|
23457
|
+
const diffContent = isNotebook ? "" : getSingleFileDiff(absFilePath, repoRoot);
|
|
23458
|
+
const { relative: pathRelative } = await import("node:path");
|
|
23459
|
+
const relPath = pathRelative(repoRoot, absFilePath);
|
|
23460
|
+
if (isInstallerOutput(relPath))
|
|
23461
|
+
return;
|
|
23462
|
+
const context = loadChangeSetContext(repoRoot);
|
|
23463
|
+
const changeSet = buildChangeSet([{ path: relPath, diffContent }], {
|
|
23464
|
+
...context,
|
|
23465
|
+
excerpts: readExcerptConfig()
|
|
23466
|
+
});
|
|
23467
|
+
const configResult = readConfig();
|
|
23468
|
+
if (!configResult.config) {
|
|
23469
|
+
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
23470
|
+
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
23471
|
+
return;
|
|
23472
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
23473
|
+
return;
|
|
23474
|
+
}
|
|
23475
|
+
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
23476
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
23477
|
+
const evaluation = await evaluateWithRefresh({
|
|
23478
|
+
guardServiceUrl,
|
|
23479
|
+
token: guardServiceToken,
|
|
23480
|
+
engagementId: guardEngagementId,
|
|
23481
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration),
|
|
23482
|
+
warn: warnOnceThisSession("credential-renewed-for-this-run", repoRoot, input.session_id)
|
|
23483
|
+
});
|
|
23484
|
+
const clientResult = evaluation.result;
|
|
23485
|
+
if (!clientResult.ok) {
|
|
23486
|
+
const failure = classifyFailure(clientResult);
|
|
23487
|
+
await emitRunRecord({
|
|
23488
|
+
engagementId: guardEngagementId,
|
|
23489
|
+
runType: "hook",
|
|
23490
|
+
outcome: outcomeForClientFailure(clientResult.kind),
|
|
23491
|
+
failureReason: failure.reason,
|
|
23492
|
+
changeSet,
|
|
23493
|
+
credential: evaluation.token,
|
|
23494
|
+
credentialRefused: failure.kind === "credential"
|
|
23495
|
+
});
|
|
23496
|
+
if (failure.kind === "credential") {
|
|
23497
|
+
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
23498
|
+
return;
|
|
23499
|
+
emitCredentialRejected(
|
|
23500
|
+
failure.statusCode,
|
|
23501
|
+
failure.message,
|
|
23502
|
+
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
23503
|
+
// The remedy the renewal attempt earned: the sign-in when the account
|
|
23504
|
+
// credential is the thing that was refused, and otherwise the plane's own
|
|
23505
|
+
// sentence about why it would not mint.
|
|
23506
|
+
evaluation.remedy
|
|
23507
|
+
);
|
|
23508
|
+
return;
|
|
23509
|
+
}
|
|
23510
|
+
if (failure.kind === "contract") {
|
|
23511
|
+
reply.notice(contractFaultText(failure.message));
|
|
23512
|
+
return;
|
|
23513
|
+
}
|
|
23514
|
+
reply.notice(infraWarningText());
|
|
23515
|
+
return;
|
|
23516
|
+
}
|
|
23517
|
+
const telemetryConfig = readTelemetryConfig();
|
|
23518
|
+
const run = buildGuardEvalRun({
|
|
23519
|
+
engagementId: guardEngagementId,
|
|
22566
23520
|
runType: "hook",
|
|
22567
23521
|
// The phase this run belongs to, as the client named it — it names the
|
|
22568
23522
|
// guard-eval log file the run is appended to. Absent → the run is unphased.
|
|
@@ -22597,10 +23551,10 @@ ${notRun}` : "";
|
|
|
22597
23551
|
return;
|
|
22598
23552
|
}
|
|
22599
23553
|
if (envelope.blocking) {
|
|
22600
|
-
const
|
|
22601
|
-
const reason =
|
|
22602
|
-
${
|
|
22603
|
-
reply.block(reason);
|
|
23554
|
+
const personNotRun = notRunMessage((envelope.notRun ?? []).map((n) => n.reason));
|
|
23555
|
+
const reason = `${blockReason(envelope.guardsFired.map((g) => g.explanation))}${personNotRun ? `
|
|
23556
|
+
${personNotRun}` : ""}`;
|
|
23557
|
+
reply.block(reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
22604
23558
|
} else {
|
|
22605
23559
|
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
22606
23560
|
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
@@ -22608,17 +23562,17 @@ ${guardLines}${notRunBlock}`);
|
|
|
22608
23562
|
}
|
|
22609
23563
|
}
|
|
22610
23564
|
function readStdin() {
|
|
22611
|
-
return new Promise((
|
|
23565
|
+
return new Promise((resolve8, reject) => {
|
|
22612
23566
|
const chunks = [];
|
|
22613
23567
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
22614
|
-
process.stdin.on("end", () =>
|
|
23568
|
+
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
22615
23569
|
process.stdin.on("error", reject);
|
|
22616
23570
|
});
|
|
22617
23571
|
}
|
|
22618
23572
|
|
|
22619
23573
|
// dist/liveness.js
|
|
22620
|
-
import { closeSync, mkdtempSync, openSync, readdirSync as readdirSync5, readFileSync as
|
|
22621
|
-
import { join as
|
|
23574
|
+
import { closeSync as closeSync2, mkdtempSync, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync14, renameSync as renameSync5, rmSync, statSync as statSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
23575
|
+
import { join as join11 } from "node:path";
|
|
22622
23576
|
var LIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
22623
23577
|
function field(input, key) {
|
|
22624
23578
|
if (typeof input !== "object" || input === null)
|
|
@@ -22627,10 +23581,10 @@ function field(input, key) {
|
|
|
22627
23581
|
return typeof v === "string" && v !== "" ? v : null;
|
|
22628
23582
|
}
|
|
22629
23583
|
function indexEntry(dir, sessionId, name) {
|
|
22630
|
-
const folder =
|
|
23584
|
+
const folder = join11(dir, livenessIndexDirName(sessionId));
|
|
22631
23585
|
for (let attempt2 = 0; attempt2 < 3; attempt2++) {
|
|
22632
23586
|
try {
|
|
22633
|
-
|
|
23587
|
+
closeSync2(openSync2(join11(folder, name), "a", 384));
|
|
22634
23588
|
return true;
|
|
22635
23589
|
} catch (err) {
|
|
22636
23590
|
if (err.code !== "ENOENT")
|
|
@@ -22641,10 +23595,10 @@ function indexEntry(dir, sessionId, name) {
|
|
|
22641
23595
|
tmp = mkdtempSync(`${folder}.`);
|
|
22642
23596
|
for (const existing of readdirSync5(dir)) {
|
|
22643
23597
|
if (parseLivenessFileName(existing)?.sessionId === sessionId)
|
|
22644
|
-
|
|
23598
|
+
closeSync2(openSync2(join11(tmp, existing), "a", 384));
|
|
22645
23599
|
}
|
|
22646
|
-
|
|
22647
|
-
|
|
23600
|
+
closeSync2(openSync2(join11(tmp, name), "a", 384));
|
|
23601
|
+
renameSync5(tmp, folder);
|
|
22648
23602
|
return true;
|
|
22649
23603
|
} catch {
|
|
22650
23604
|
if (tmp !== null)
|
|
@@ -22669,18 +23623,18 @@ function recordLiveness(input, deps = {}) {
|
|
|
22669
23623
|
const name = livenessFileName(livenessKey(copy?.common ?? null), sessionId, agentId);
|
|
22670
23624
|
if (!indexEntry(dir, sessionId, name))
|
|
22671
23625
|
return null;
|
|
22672
|
-
const path =
|
|
23626
|
+
const path = join11(dir, name);
|
|
22673
23627
|
const text = `${cwd}
|
|
22674
23628
|
${copy?.top ?? ""}
|
|
22675
23629
|
`;
|
|
22676
23630
|
try {
|
|
22677
|
-
|
|
23631
|
+
writeFileSync7(path, text, { encoding: "utf-8", flag: "wx", mode: 384 });
|
|
22678
23632
|
return { firstSeen: true };
|
|
22679
23633
|
} catch (err) {
|
|
22680
23634
|
if (err.code !== "EEXIST")
|
|
22681
23635
|
return null;
|
|
22682
23636
|
}
|
|
22683
|
-
|
|
23637
|
+
writeFileSync7(path, text, { encoding: "utf-8", mode: 384 });
|
|
22684
23638
|
return { firstSeen: false };
|
|
22685
23639
|
} catch {
|
|
22686
23640
|
return null;
|
|
@@ -22699,7 +23653,7 @@ function clearLiveness(input, deps = {}) {
|
|
|
22699
23653
|
if (p === null || p.sessionId !== sessionId || p.agentId !== agentId)
|
|
22700
23654
|
continue;
|
|
22701
23655
|
try {
|
|
22702
|
-
|
|
23656
|
+
unlinkSync6(join11(dir, name));
|
|
22703
23657
|
removed += 1;
|
|
22704
23658
|
} catch {
|
|
22705
23659
|
}
|
|
@@ -22712,7 +23666,7 @@ function clearLiveness(input, deps = {}) {
|
|
|
22712
23666
|
}
|
|
22713
23667
|
function topOf(path) {
|
|
22714
23668
|
try {
|
|
22715
|
-
const top =
|
|
23669
|
+
const top = readFileSync14(path, "utf-8").split("\n")[1] ?? "";
|
|
22716
23670
|
return top === "" ? null : top;
|
|
22717
23671
|
} catch {
|
|
22718
23672
|
return null;
|
|
@@ -22734,10 +23688,10 @@ function readLiveness(sessionId, cwd, deps = {}) {
|
|
|
22734
23688
|
const p = parseLivenessFileName(name);
|
|
22735
23689
|
if (p === null || p.key !== key || p.sessionId !== sessionId)
|
|
22736
23690
|
continue;
|
|
22737
|
-
const full =
|
|
23691
|
+
const full = join11(dir, name);
|
|
22738
23692
|
let seenAt;
|
|
22739
23693
|
try {
|
|
22740
|
-
seenAt =
|
|
23694
|
+
seenAt = statSync4(full).mtimeMs;
|
|
22741
23695
|
} catch {
|
|
22742
23696
|
continue;
|
|
22743
23697
|
}
|
|
@@ -22775,17 +23729,17 @@ function runsBesideOtherWork(toolInput, record2) {
|
|
|
22775
23729
|
}
|
|
22776
23730
|
|
|
22777
23731
|
// dist/pace-hook.js
|
|
22778
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
23732
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
22779
23733
|
import nodePath4 from "node:path";
|
|
22780
23734
|
|
|
22781
23735
|
// dist/pace-verb.js
|
|
22782
|
-
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as
|
|
23736
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
22783
23737
|
import { homedir as homedir6 } from "node:os";
|
|
22784
23738
|
import nodePath3 from "node:path";
|
|
22785
23739
|
|
|
22786
23740
|
// dist/pacing.js
|
|
22787
|
-
import { createHash as
|
|
22788
|
-
import { closeSync as
|
|
23741
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
23742
|
+
import { closeSync as closeSync3, mkdirSync as mkdirSync4, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync15, readSync, realpathSync as realpathSync5, renameSync as renameSync6, statSync as statSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
22789
23743
|
import { homedir as homedir5 } from "node:os";
|
|
22790
23744
|
import nodePath from "node:path";
|
|
22791
23745
|
var DEFAULT_CONFIG_FOLDER_NAME = ".claude";
|
|
@@ -22834,7 +23788,7 @@ function pacingDir(home) {
|
|
|
22834
23788
|
}
|
|
22835
23789
|
function readJsonObject(path) {
|
|
22836
23790
|
try {
|
|
22837
|
-
const v = JSON.parse(
|
|
23791
|
+
const v = JSON.parse(readFileSync15(path, "utf-8"));
|
|
22838
23792
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
22839
23793
|
} catch {
|
|
22840
23794
|
return null;
|
|
@@ -22842,9 +23796,9 @@ function readJsonObject(path) {
|
|
|
22842
23796
|
}
|
|
22843
23797
|
function writeJsonWhole(path, value) {
|
|
22844
23798
|
mkdirSync4(nodePath.dirname(path), { recursive: true, mode: 448 });
|
|
22845
|
-
const tmp = `${path}.${String(process.pid)}.${
|
|
22846
|
-
|
|
22847
|
-
|
|
23799
|
+
const tmp = `${path}.${String(process.pid)}.${createHash4("sha1").update(String(Math.random())).digest("hex").slice(0, 8)}.tmp`;
|
|
23800
|
+
writeFileSync8(tmp, JSON.stringify(value), { mode: 384 });
|
|
23801
|
+
renameSync6(tmp, path);
|
|
22848
23802
|
}
|
|
22849
23803
|
function writeAccountEntry(path, key, entry) {
|
|
22850
23804
|
const current = readJsonObject(path);
|
|
@@ -22864,6 +23818,62 @@ function accountKeysOf(path) {
|
|
|
22864
23818
|
const accounts = current?.["accounts"];
|
|
22865
23819
|
return typeof accounts === "object" && accounts !== null && !Array.isArray(accounts) ? Object.keys(accounts) : [];
|
|
22866
23820
|
}
|
|
23821
|
+
var PACE_STATE_FILENAME = "state.json";
|
|
23822
|
+
function paceStateEntryOf(v) {
|
|
23823
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
23824
|
+
return null;
|
|
23825
|
+
const o = v;
|
|
23826
|
+
const pace = paceStateSchema.safeParse(o["pace"]);
|
|
23827
|
+
const kind = o["windowKind"] === null ? null : paceWindowKindSchema.safeParse(o["windowKind"]);
|
|
23828
|
+
const allowance = o["allowance"];
|
|
23829
|
+
const number4 = o["number"];
|
|
23830
|
+
const until = o["until"];
|
|
23831
|
+
const at = o["at"];
|
|
23832
|
+
if (!pace.success || kind !== null && !kind.success)
|
|
23833
|
+
return null;
|
|
23834
|
+
if (typeof allowance !== "number" || !Number.isInteger(allowance) || allowance < 0)
|
|
23835
|
+
return null;
|
|
23836
|
+
if (typeof number4 !== "number" || !Number.isInteger(number4) || number4 < 1 || number4 > PACE_AGENTS_MAX)
|
|
23837
|
+
return null;
|
|
23838
|
+
if (until !== null && (typeof until !== "string" || Number.isNaN(Date.parse(until))))
|
|
23839
|
+
return null;
|
|
23840
|
+
if (typeof at !== "string" || Number.isNaN(Date.parse(at)))
|
|
23841
|
+
return null;
|
|
23842
|
+
return { pace: pace.data, allowance, number: number4, windowKind: kind === null ? null : kind.data, until, at };
|
|
23843
|
+
}
|
|
23844
|
+
function rememberedBasisOf(v) {
|
|
23845
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
23846
|
+
return null;
|
|
23847
|
+
const o = v;
|
|
23848
|
+
const phase = o["phase"] === null ? null : phaseNameSchema.safeParse(o["phase"]);
|
|
23849
|
+
const plan = o["claudePlan"] === null ? null : claudePlanSchema.safeParse(o["claudePlan"]);
|
|
23850
|
+
const keep = keepFreePercentSchema.safeParse(o["keepFreePercent"]);
|
|
23851
|
+
if (phase !== null && !phase.success || plan !== null && !plan.success || !keep.success)
|
|
23852
|
+
return null;
|
|
23853
|
+
return { phase: phase === null ? null : phase.data, claudePlan: plan === null ? null : plan.data, keepFreePercent: keep.data };
|
|
23854
|
+
}
|
|
23855
|
+
function readPaceStateFile(dir) {
|
|
23856
|
+
const o = readJsonObject(nodePath.join(dir, PACE_STATE_FILENAME));
|
|
23857
|
+
const accounts = {};
|
|
23858
|
+
const rawAccounts = o?.["accounts"];
|
|
23859
|
+
if (typeof rawAccounts === "object" && rawAccounts !== null && !Array.isArray(rawAccounts)) {
|
|
23860
|
+
for (const [key, v] of Object.entries(rawAccounts)) {
|
|
23861
|
+
const e = paceStateEntryOf(v);
|
|
23862
|
+
if (e !== null)
|
|
23863
|
+
accounts[key] = e;
|
|
23864
|
+
}
|
|
23865
|
+
}
|
|
23866
|
+
return { accounts, basis: rememberedBasisOf(o?.["basis"]) };
|
|
23867
|
+
}
|
|
23868
|
+
function writePaceState(dir, key, entry, basis) {
|
|
23869
|
+
const current = readPaceStateFile(dir);
|
|
23870
|
+
const accounts = { ...current.accounts };
|
|
23871
|
+
if (entry === null)
|
|
23872
|
+
delete accounts[key];
|
|
23873
|
+
else
|
|
23874
|
+
accounts[key] = entry;
|
|
23875
|
+
writeJsonWhole(nodePath.join(dir, PACE_STATE_FILENAME), { accounts, basis: basis ?? current.basis });
|
|
23876
|
+
}
|
|
22867
23877
|
function recordFactsOf(o) {
|
|
22868
23878
|
if (o["type"] !== "assistant")
|
|
22869
23879
|
return null;
|
|
@@ -22979,7 +23989,7 @@ var NEWLINE = 10;
|
|
|
22979
23989
|
var ASSISTANT = Buffer.from('"assistant"');
|
|
22980
23990
|
var READ_CHUNK = 8 * 1024 * 1024;
|
|
22981
23991
|
function readWholeLines(path, from, size, onLine) {
|
|
22982
|
-
const fd =
|
|
23992
|
+
const fd = openSync3(path, "r");
|
|
22983
23993
|
try {
|
|
22984
23994
|
let pos = from;
|
|
22985
23995
|
let carry = Buffer.alloc(0);
|
|
@@ -23007,11 +24017,11 @@ function readWholeLines(path, from, size, onLine) {
|
|
|
23007
24017
|
}
|
|
23008
24018
|
return consumed;
|
|
23009
24019
|
} finally {
|
|
23010
|
-
|
|
24020
|
+
closeSync3(fd);
|
|
23011
24021
|
}
|
|
23012
24022
|
}
|
|
23013
24023
|
function hashId(id) {
|
|
23014
|
-
return
|
|
24024
|
+
return createHash4("sha1").update(id).digest("base64url").slice(0, 16);
|
|
23015
24025
|
}
|
|
23016
24026
|
function scanRecords(key, deps = {}) {
|
|
23017
24027
|
const nowMs = (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime();
|
|
@@ -23036,7 +24046,7 @@ function scanRecords(key, deps = {}) {
|
|
|
23036
24046
|
let size;
|
|
23037
24047
|
let mtimeSec;
|
|
23038
24048
|
try {
|
|
23039
|
-
const st =
|
|
24049
|
+
const st = statSync5(file2.path);
|
|
23040
24050
|
size = st.size;
|
|
23041
24051
|
mtimeSec = Math.floor(st.mtimeMs / 1e3);
|
|
23042
24052
|
} catch {
|
|
@@ -23159,7 +24169,7 @@ function tokensBetween(minutes, fromSec, toSec) {
|
|
|
23159
24169
|
}
|
|
23160
24170
|
function resolvedRecords(key, path) {
|
|
23161
24171
|
try {
|
|
23162
|
-
return
|
|
24172
|
+
return realpathSync5(recordsFolderOf(key, path));
|
|
23163
24173
|
} catch {
|
|
23164
24174
|
return null;
|
|
23165
24175
|
}
|
|
@@ -23172,7 +24182,7 @@ function timeLogTranscripts(home) {
|
|
|
23172
24182
|
continue;
|
|
23173
24183
|
let text;
|
|
23174
24184
|
try {
|
|
23175
|
-
text =
|
|
24185
|
+
text = readFileSync15(nodePath.join(engagements, e.name, TIME_LOG_DIR_NAME, TIME_LOG_FILENAME), "utf-8");
|
|
23176
24186
|
} catch {
|
|
23177
24187
|
continue;
|
|
23178
24188
|
}
|
|
@@ -23332,7 +24342,7 @@ function knownWindowOf(learned, reported, now) {
|
|
|
23332
24342
|
|
|
23333
24343
|
// dist/scan-lock.js
|
|
23334
24344
|
import { spawn } from "node:child_process";
|
|
23335
|
-
import { linkSync, mkdirSync as mkdirSync5, readFileSync as
|
|
24345
|
+
import { linkSync, mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
23336
24346
|
import nodePath2 from "node:path";
|
|
23337
24347
|
var SCAN_LOCK_FILENAME = "scan.lock";
|
|
23338
24348
|
var SCAN_LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
@@ -23343,7 +24353,7 @@ function scanLockPath(home) {
|
|
|
23343
24353
|
function readLockBody(path) {
|
|
23344
24354
|
let text;
|
|
23345
24355
|
try {
|
|
23346
|
-
text =
|
|
24356
|
+
text = readFileSync16(path, "utf-8");
|
|
23347
24357
|
} catch (err) {
|
|
23348
24358
|
return err.code === "ENOENT" ? "missing" : null;
|
|
23349
24359
|
}
|
|
@@ -23373,7 +24383,7 @@ function isStale(body, staleMs, deps) {
|
|
|
23373
24383
|
return !(deps.alive ?? processAlive)(body.pid);
|
|
23374
24384
|
}
|
|
23375
24385
|
function writeLock(path, pid, deps, flag) {
|
|
23376
|
-
|
|
24386
|
+
writeFileSync9(path, JSON.stringify({ pid, at: (deps.clock ?? Date.now)() }), { flag, mode: 384 });
|
|
23377
24387
|
}
|
|
23378
24388
|
function tryTakeScanLock(deps = {}) {
|
|
23379
24389
|
return tryTakeLockFile(scanLockPath(deps.home), SCAN_LOCK_STALE_MS, deps);
|
|
@@ -23400,7 +24410,7 @@ function tryTakeLockFile(path, staleMs, deps = {}) {
|
|
|
23400
24410
|
return false;
|
|
23401
24411
|
const aside = `${path}.${String(pid)}.${String((deps.clock ?? Date.now)())}.stale`;
|
|
23402
24412
|
try {
|
|
23403
|
-
|
|
24413
|
+
renameSync7(path, aside);
|
|
23404
24414
|
} catch {
|
|
23405
24415
|
continue;
|
|
23406
24416
|
}
|
|
@@ -23412,13 +24422,13 @@ function tryTakeLockFile(path, staleMs, deps = {}) {
|
|
|
23412
24422
|
} catch {
|
|
23413
24423
|
}
|
|
23414
24424
|
try {
|
|
23415
|
-
|
|
24425
|
+
unlinkSync7(aside);
|
|
23416
24426
|
} catch {
|
|
23417
24427
|
}
|
|
23418
24428
|
return false;
|
|
23419
24429
|
}
|
|
23420
24430
|
try {
|
|
23421
|
-
|
|
24431
|
+
unlinkSync7(aside);
|
|
23422
24432
|
} catch {
|
|
23423
24433
|
}
|
|
23424
24434
|
}
|
|
@@ -23429,7 +24439,7 @@ function releaseLockFile(path, pid = process.pid) {
|
|
|
23429
24439
|
if (body === "missing" || body === null || body.pid !== pid)
|
|
23430
24440
|
return;
|
|
23431
24441
|
try {
|
|
23432
|
-
|
|
24442
|
+
unlinkSync7(path);
|
|
23433
24443
|
} catch {
|
|
23434
24444
|
}
|
|
23435
24445
|
}
|
|
@@ -23620,7 +24630,7 @@ function readBuildNumber(engagementId, home) {
|
|
|
23620
24630
|
if (dir === null)
|
|
23621
24631
|
return null;
|
|
23622
24632
|
try {
|
|
23623
|
-
const v = JSON.parse(
|
|
24633
|
+
const v = JSON.parse(readFileSync17(nodePath3.join(dir, BUILD_NUMBER_FILENAME), "utf-8"));
|
|
23624
24634
|
const n = v["number"];
|
|
23625
24635
|
if (typeof v["phase"] === "string" && typeof n === "number" && Number.isInteger(n) && n >= 1 && n <= PACE_AGENTS_MAX && typeof v["namedByOwner"] === "boolean") {
|
|
23626
24636
|
return { phase: v["phase"], number: n, namedByOwner: v["namedByOwner"] };
|
|
@@ -23640,12 +24650,12 @@ function writeBuildNumber(engagementId, value, home) {
|
|
|
23640
24650
|
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
23641
24651
|
const path = nodePath3.join(dir, BUILD_NUMBER_FILENAME);
|
|
23642
24652
|
const tmp = `${path}.${String(process.pid)}.tmp`;
|
|
23643
|
-
|
|
23644
|
-
|
|
24653
|
+
writeFileSync10(tmp, JSON.stringify(value), { mode: 384 });
|
|
24654
|
+
renameSync8(tmp, path);
|
|
23645
24655
|
}
|
|
23646
24656
|
function readRememberedClaudePlan(home) {
|
|
23647
24657
|
try {
|
|
23648
|
-
const parsed = claudePlanRequestSchema.safeParse(JSON.parse(
|
|
24658
|
+
const parsed = claudePlanRequestSchema.safeParse(JSON.parse(readFileSync17(nodePath3.join(home ?? homedir6(), HALFCYCLE_DIR_NAME, REMEMBERED_CLAUDE_PLAN_FILENAME), "utf-8")));
|
|
23649
24659
|
return parsed.success ? parsed.data.claudePlan : null;
|
|
23650
24660
|
} catch {
|
|
23651
24661
|
return null;
|
|
@@ -23665,7 +24675,7 @@ function liveSubAgentsIn(cwd, deps = {}) {
|
|
|
23665
24675
|
if (p === null || p.key !== key || p.agentId === null)
|
|
23666
24676
|
continue;
|
|
23667
24677
|
try {
|
|
23668
|
-
if (now -
|
|
24678
|
+
if (now - statSync6(nodePath3.join(dir, name)).mtimeMs <= LIVE_WINDOW_MS)
|
|
23669
24679
|
n += 1;
|
|
23670
24680
|
} catch {
|
|
23671
24681
|
}
|
|
@@ -23824,6 +24834,23 @@ async function runPaceVerb(args2, deps = {}) {
|
|
|
23824
24834
|
number4 = stored?.number ?? Math.min(claudePlanOf(claudePlan).startingAgents, PACE_AGENTS_MAX);
|
|
23825
24835
|
}
|
|
23826
24836
|
const decision = paceOf({ number: number4, keepFreePercent, learned, reported, now });
|
|
24837
|
+
if (basis !== null) {
|
|
24838
|
+
const dir = enginePacingDir(engagementId, deps.home);
|
|
24839
|
+
if (dir !== null) {
|
|
24840
|
+
const entry = {
|
|
24841
|
+
pace: decision.pace,
|
|
24842
|
+
allowance: decision.allowance,
|
|
24843
|
+
number: number4,
|
|
24844
|
+
windowKind: decision.windowKind,
|
|
24845
|
+
until: decision.until === null ? null : decision.until.toISOString(),
|
|
24846
|
+
at: now.toISOString()
|
|
24847
|
+
};
|
|
24848
|
+
try {
|
|
24849
|
+
writePaceState(dir, key, entry, { phase: basis.phase, claudePlan: basis.claudePlan, keepFreePercent });
|
|
24850
|
+
} catch {
|
|
24851
|
+
}
|
|
24852
|
+
}
|
|
24853
|
+
}
|
|
23827
24854
|
const live = liveSubAgentsIn(deps.cwd ?? process.cwd(), deps.live);
|
|
23828
24855
|
const result = {
|
|
23829
24856
|
outcome: "paced",
|
|
@@ -23844,11 +24871,10 @@ async function runPaceVerb(args2, deps = {}) {
|
|
|
23844
24871
|
var PACE_HELD_TOOL = "Agent";
|
|
23845
24872
|
var PACE_STEP_MIN_INTERVAL_MS = OBSERVE_MIN_INTERVAL_MS;
|
|
23846
24873
|
var PACE_STATE_FRESH_MS = 10 * 60 * 1e3;
|
|
23847
|
-
var PACE_PENDING_START_MS =
|
|
24874
|
+
var PACE_PENDING_START_MS = 15 * 1e3;
|
|
23848
24875
|
var PACE_STARTS_LOCK_STALE_MS = 5 * 1e3;
|
|
23849
24876
|
var PACE_STARTS_LOCK_GIVE_UP_MS = 50;
|
|
23850
24877
|
var STARTS_LOCK_POLL_MS = 2;
|
|
23851
|
-
var PACE_STATE_FILENAME = "state.json";
|
|
23852
24878
|
var PACE_STARTS_FILENAME = "starts.json";
|
|
23853
24879
|
var PACE_STARTS_LOCK_FILENAME = "starts.lock";
|
|
23854
24880
|
function objectOf(v) {
|
|
@@ -23856,7 +24882,7 @@ function objectOf(v) {
|
|
|
23856
24882
|
}
|
|
23857
24883
|
function readJson(path) {
|
|
23858
24884
|
try {
|
|
23859
|
-
return objectOf(JSON.parse(
|
|
24885
|
+
return objectOf(JSON.parse(readFileSync18(path, "utf-8")));
|
|
23860
24886
|
} catch {
|
|
23861
24887
|
return null;
|
|
23862
24888
|
}
|
|
@@ -23865,68 +24891,16 @@ function writeJsonWhole2(path, value) {
|
|
|
23865
24891
|
mkdirSync7(nodePath4.dirname(path), { recursive: true, mode: 448 });
|
|
23866
24892
|
const tmp = `${path}.${String(process.pid)}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
23867
24893
|
try {
|
|
23868
|
-
|
|
23869
|
-
|
|
24894
|
+
writeFileSync11(tmp, JSON.stringify(value), { mode: 384 });
|
|
24895
|
+
renameSync9(tmp, path);
|
|
23870
24896
|
} catch (err) {
|
|
23871
24897
|
try {
|
|
23872
|
-
|
|
24898
|
+
unlinkSync8(tmp);
|
|
23873
24899
|
} catch {
|
|
23874
24900
|
}
|
|
23875
24901
|
throw err;
|
|
23876
24902
|
}
|
|
23877
24903
|
}
|
|
23878
|
-
function entryOf(v) {
|
|
23879
|
-
const o = objectOf(v);
|
|
23880
|
-
if (o === null)
|
|
23881
|
-
return null;
|
|
23882
|
-
const pace = paceStateSchema.safeParse(o["pace"]);
|
|
23883
|
-
const kind = o["windowKind"] === null ? null : paceWindowKindSchema.safeParse(o["windowKind"]);
|
|
23884
|
-
const allowance = o["allowance"];
|
|
23885
|
-
const number4 = o["number"];
|
|
23886
|
-
const until = o["until"];
|
|
23887
|
-
const at = o["at"];
|
|
23888
|
-
if (!pace.success || kind !== null && !kind.success)
|
|
23889
|
-
return null;
|
|
23890
|
-
if (typeof allowance !== "number" || !Number.isInteger(allowance) || allowance < 0)
|
|
23891
|
-
return null;
|
|
23892
|
-
if (typeof number4 !== "number" || !Number.isInteger(number4) || number4 < 1 || number4 > PACE_AGENTS_MAX)
|
|
23893
|
-
return null;
|
|
23894
|
-
if (until !== null && (typeof until !== "string" || Number.isNaN(Date.parse(until))))
|
|
23895
|
-
return null;
|
|
23896
|
-
if (typeof at !== "string" || Number.isNaN(Date.parse(at)))
|
|
23897
|
-
return null;
|
|
23898
|
-
return { pace: pace.data, allowance, number: number4, windowKind: kind === null ? null : kind.data, until, at };
|
|
23899
|
-
}
|
|
23900
|
-
function rememberedBasisOf(v) {
|
|
23901
|
-
const o = objectOf(v);
|
|
23902
|
-
if (o === null)
|
|
23903
|
-
return null;
|
|
23904
|
-
const phase = o["phase"] === null ? null : phaseNameSchema.safeParse(o["phase"]);
|
|
23905
|
-
const plan = o["claudePlan"] === null ? null : claudePlanSchema.safeParse(o["claudePlan"]);
|
|
23906
|
-
const keep = keepFreePercentSchema.safeParse(o["keepFreePercent"]);
|
|
23907
|
-
if (phase !== null && !phase.success || plan !== null && !plan.success || !keep.success)
|
|
23908
|
-
return null;
|
|
23909
|
-
return { phase: phase === null ? null : phase.data, claudePlan: plan === null ? null : plan.data, keepFreePercent: keep.data };
|
|
23910
|
-
}
|
|
23911
|
-
function readPaceStateFile(dir) {
|
|
23912
|
-
const o = readJson(nodePath4.join(dir, PACE_STATE_FILENAME));
|
|
23913
|
-
const accounts = {};
|
|
23914
|
-
for (const [key, v] of Object.entries(objectOf(o?.["accounts"]) ?? {})) {
|
|
23915
|
-
const e = entryOf(v);
|
|
23916
|
-
if (e !== null)
|
|
23917
|
-
accounts[key] = e;
|
|
23918
|
-
}
|
|
23919
|
-
return { accounts, basis: rememberedBasisOf(o?.["basis"]) };
|
|
23920
|
-
}
|
|
23921
|
-
function writePaceState(dir, key, entry, basis) {
|
|
23922
|
-
const current = readPaceStateFile(dir);
|
|
23923
|
-
const accounts = { ...current.accounts };
|
|
23924
|
-
if (entry === null)
|
|
23925
|
-
delete accounts[key];
|
|
23926
|
-
else
|
|
23927
|
-
accounts[key] = entry;
|
|
23928
|
-
writeJsonWhole2(nodePath4.join(dir, PACE_STATE_FILENAME), { accounts, basis: basis ?? current.basis });
|
|
23929
|
-
}
|
|
23930
24904
|
function field2(input, key) {
|
|
23931
24905
|
const v = objectOf(input)?.[key];
|
|
23932
24906
|
return typeof v === "string" && v !== "" ? v : null;
|
|
@@ -23934,12 +24908,14 @@ function field2(input, key) {
|
|
|
23934
24908
|
function shownAs(pace, windowKind, allowance) {
|
|
23935
24909
|
return pace === "full" ? "full" : `${pace}:${windowKind ?? ""}:${pace === "slowing" ? String(allowance) : ""}`;
|
|
23936
24910
|
}
|
|
23937
|
-
function paceChangeText(before, after, working, now) {
|
|
24911
|
+
function paceChangeText(before, after, working, now, keepFreePercent) {
|
|
23938
24912
|
const was = before === null ? "full" : shownAs(before.pace, before.windowKind, before.allowance);
|
|
23939
24913
|
const is = shownAs(after.pace, after.windowKind, after.allowance);
|
|
23940
24914
|
if (was === is)
|
|
23941
24915
|
return null;
|
|
23942
24916
|
if (after.pace === "full") {
|
|
24917
|
+
if (keepFreePercent === 0)
|
|
24918
|
+
return null;
|
|
23943
24919
|
return before !== null && before.windowKind !== null ? paceS3Of(before.windowKind, after.number) : null;
|
|
23944
24920
|
}
|
|
23945
24921
|
if (after.windowKind === null || after.until === null)
|
|
@@ -24009,7 +24985,7 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
|
|
|
24009
24985
|
};
|
|
24010
24986
|
write(dir, key, after, read !== null ? basis : null);
|
|
24011
24987
|
const working = liveCount(readLiveness(field2(input, "session_id"), field2(input, "cwd") ?? process.cwd(), deps.liveness));
|
|
24012
|
-
const text = paceChangeText(before, after, working, now);
|
|
24988
|
+
const text = paceChangeText(before, after, working, now, basis.keepFreePercent);
|
|
24013
24989
|
if (text !== null)
|
|
24014
24990
|
reply.notice(text);
|
|
24015
24991
|
} catch {
|
|
@@ -24138,9 +25114,9 @@ function paceHoldText(input, toolInput, deps = {}) {
|
|
|
24138
25114
|
}
|
|
24139
25115
|
|
|
24140
25116
|
// dist/risky/facts.js
|
|
24141
|
-
import { existsSync as
|
|
25117
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, statSync as statSync7 } from "node:fs";
|
|
24142
25118
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
|
|
24143
|
-
import { basename as pathBasename, dirname as
|
|
25119
|
+
import { basename as pathBasename, dirname as dirname6, isAbsolute as isAbsolute2, join as join12, relative as relative5, resolve as resolve4, sep as sep3 } from "node:path";
|
|
24144
25120
|
|
|
24145
25121
|
// dist/risky/shell.js
|
|
24146
25122
|
var MAX_DEPTH = 8;
|
|
@@ -24515,7 +25491,7 @@ var Splitter = class _Splitter {
|
|
|
24515
25491
|
const script = innerScript(words);
|
|
24516
25492
|
if (script !== void 0) {
|
|
24517
25493
|
if (this.depth < MAX_DEPTH) {
|
|
24518
|
-
this.child(script,
|
|
25494
|
+
this.child(script, basename2(words[0].text) === "eval").parseList(0, null);
|
|
24519
25495
|
}
|
|
24520
25496
|
return;
|
|
24521
25497
|
}
|
|
@@ -24584,7 +25560,7 @@ function unwrap(input) {
|
|
|
24584
25560
|
words = words.slice(1);
|
|
24585
25561
|
continue;
|
|
24586
25562
|
}
|
|
24587
|
-
const prog =
|
|
25563
|
+
const prog = basename2(name);
|
|
24588
25564
|
const valueOptions = WRAPPER_VALUE_OPTIONS[prog];
|
|
24589
25565
|
if (valueOptions === void 0)
|
|
24590
25566
|
return words;
|
|
@@ -24628,7 +25604,7 @@ function unwrap(input) {
|
|
|
24628
25604
|
}
|
|
24629
25605
|
var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "ksh"]);
|
|
24630
25606
|
function innerScript(words) {
|
|
24631
|
-
const prog =
|
|
25607
|
+
const prog = basename2(words[0].text);
|
|
24632
25608
|
if (prog === EVAL_PROGRAM)
|
|
24633
25609
|
return words.slice(1).map((w) => w.text).join(" ");
|
|
24634
25610
|
if (!SHELLS.has(prog))
|
|
@@ -24663,13 +25639,13 @@ function innerScript(words) {
|
|
|
24663
25639
|
}
|
|
24664
25640
|
function readsScriptFromInput(raw) {
|
|
24665
25641
|
const words = unwrap(raw);
|
|
24666
|
-
if (words.length === 0 || !SHELLS.has(
|
|
25642
|
+
if (words.length === 0 || !SHELLS.has(basename2(words[0].text)))
|
|
24667
25643
|
return false;
|
|
24668
25644
|
if (innerScript(words) !== void 0)
|
|
24669
25645
|
return false;
|
|
24670
25646
|
return words.slice(1).every((w) => w.text.startsWith("-") || w.text.startsWith("+"));
|
|
24671
25647
|
}
|
|
24672
|
-
function
|
|
25648
|
+
function basename2(word) {
|
|
24673
25649
|
const k = Math.max(word.lastIndexOf("/"), word.lastIndexOf("\\"));
|
|
24674
25650
|
return k < 0 ? word : word.slice(k + 1);
|
|
24675
25651
|
}
|
|
@@ -24684,7 +25660,7 @@ function namesNeverFollowed(line2, commands) {
|
|
|
24684
25660
|
const sourced = commands.some((c) => c.words[0] !== void 0 && (c.words[0].text === "source" || c.words[0].text === "."));
|
|
24685
25661
|
const evaluated = commands.some((c) => c.inPlace === true);
|
|
24686
25662
|
const indirect = commands.some((c) => {
|
|
24687
|
-
const prog = c.words[0] === void 0 ? "" :
|
|
25663
|
+
const prog = c.words[0] === void 0 ? "" : basename2(c.words[0].text);
|
|
24688
25664
|
return prog === "trap" || prog === "builtin" || REFERENCE_BINDERS.has(prog) && c.words.slice(1).some((w) => /^-[A-Za-z]*n/.test(w.text));
|
|
24689
25665
|
});
|
|
24690
25666
|
const fns = functionsIn(line2);
|
|
@@ -24708,7 +25684,7 @@ function namesNeverFollowed(line2, commands) {
|
|
|
24708
25684
|
out.add(hit[1]);
|
|
24709
25685
|
}
|
|
24710
25686
|
}
|
|
24711
|
-
const prog = c.words[0] === void 0 ? "" :
|
|
25687
|
+
const prog = c.words[0] === void 0 ? "" : basename2(c.words[0].text);
|
|
24712
25688
|
const args2 = c.words.slice(1).map((w) => w.text);
|
|
24713
25689
|
if (BINDERS.has(prog) || prog === "unset") {
|
|
24714
25690
|
for (const a of args2) {
|
|
@@ -24933,7 +25909,7 @@ var LineVariables = class {
|
|
|
24933
25909
|
}
|
|
24934
25910
|
}
|
|
24935
25911
|
update(cmd, before) {
|
|
24936
|
-
const prog = cmd.words[0] === void 0 ? "" :
|
|
25912
|
+
const prog = cmd.words[0] === void 0 ? "" : basename2(cmd.words[0].text);
|
|
24937
25913
|
let settings = [];
|
|
24938
25914
|
if (cmd.assigns !== void 0)
|
|
24939
25915
|
settings = cmd.assigns;
|
|
@@ -25127,7 +26103,7 @@ function expandWord(word, vars) {
|
|
|
25127
26103
|
return { text: v.text, literal: !pattern };
|
|
25128
26104
|
}
|
|
25129
26105
|
function expandCommand(cmd, vars) {
|
|
25130
|
-
const first = cmd.words[0] === void 0 ? "" :
|
|
26106
|
+
const first = cmd.words[0] === void 0 ? "" : basename2(cmd.words[0].text);
|
|
25131
26107
|
const keepEmpty = FOLDER_CHANGERS.has(first);
|
|
25132
26108
|
const words = [];
|
|
25133
26109
|
cmd.words.forEach((w, i) => {
|
|
@@ -25171,7 +26147,7 @@ function pathsMade(cmd) {
|
|
|
25171
26147
|
const words = cmd.words;
|
|
25172
26148
|
if (words.length === 0)
|
|
25173
26149
|
return [];
|
|
25174
|
-
const prog =
|
|
26150
|
+
const prog = basename2(words[0].text);
|
|
25175
26151
|
const args2 = words.slice(1);
|
|
25176
26152
|
if (prog === "mkdir") {
|
|
25177
26153
|
const out = [];
|
|
@@ -25211,7 +26187,7 @@ function pathsMade(cmd) {
|
|
|
25211
26187
|
if (ops.length >= 2)
|
|
25212
26188
|
return [{ word: ops[ops.length - 1], by: "clone" }];
|
|
25213
26189
|
if (ops.length === 1 && ops[0].literal) {
|
|
25214
|
-
const name =
|
|
26190
|
+
const name = basename2(ops[0].text.replace(/\/+$/, "")).replace(/\.git$/, "");
|
|
25215
26191
|
if (name !== "" && !name.includes(":"))
|
|
25216
26192
|
return [{ word: { text: name, literal: true }, by: "clone" }];
|
|
25217
26193
|
}
|
|
@@ -25224,24 +26200,6 @@ function pathsMade(cmd) {
|
|
|
25224
26200
|
return [];
|
|
25225
26201
|
}
|
|
25226
26202
|
|
|
25227
|
-
// dist/risky/types.js
|
|
25228
|
-
var RISKY_ACTION_TYPES = [
|
|
25229
|
-
"delete-files",
|
|
25230
|
-
"discard-unsaved",
|
|
25231
|
-
"rewrite-shared-history",
|
|
25232
|
-
"delete-unmerged-work",
|
|
25233
|
-
"wipe-data",
|
|
25234
|
-
"read-credentials",
|
|
25235
|
-
"publish",
|
|
25236
|
-
"deploy",
|
|
25237
|
-
"shrink-record",
|
|
25238
|
-
"outside-own-copy"
|
|
25239
|
-
];
|
|
25240
|
-
function riskyTypeOrder(type) {
|
|
25241
|
-
return RISKY_ACTION_TYPES.indexOf(type);
|
|
25242
|
-
}
|
|
25243
|
-
var CHECKED_TOOLS = ["Bash", "Read", "Grep", "Write", "Edit", "MultiEdit", "NotebookEdit", "Agent"];
|
|
25244
|
-
|
|
25245
26203
|
// dist/risky/recognise.js
|
|
25246
26204
|
function madeKey(dirs, word) {
|
|
25247
26205
|
return [...dirs.map((d) => d.text), word.text].join("\0");
|
|
@@ -25405,7 +26363,7 @@ function surelyRuns(simple, before) {
|
|
|
25405
26363
|
}
|
|
25406
26364
|
function folderAfter(prev, simple, before) {
|
|
25407
26365
|
const first = simple.words[0];
|
|
25408
|
-
if (first === void 0 || !FOLDER_CHANGERS.has(
|
|
26366
|
+
if (first === void 0 || !FOLDER_CHANGERS.has(basename2(first.text)))
|
|
25409
26367
|
return prev;
|
|
25410
26368
|
const dirs = prev?.dirs ?? [];
|
|
25411
26369
|
const unknown2 = { dirs, known: false };
|
|
@@ -25414,7 +26372,7 @@ function folderAfter(prev, simple, before) {
|
|
|
25414
26372
|
return prev;
|
|
25415
26373
|
if (simple.next === "||" || before === "||")
|
|
25416
26374
|
return unknown2;
|
|
25417
|
-
const prog =
|
|
26375
|
+
const prog = basename2(first.text);
|
|
25418
26376
|
if (simple.inPlace === true || simple.conditional === true || prog === "popd")
|
|
25419
26377
|
return unknown2;
|
|
25420
26378
|
let args2 = simple.words.slice(1);
|
|
@@ -25458,7 +26416,7 @@ function recogniseSimple(cmd) {
|
|
|
25458
26416
|
}
|
|
25459
26417
|
function programForms(words) {
|
|
25460
26418
|
const forms = [words];
|
|
25461
|
-
const prog =
|
|
26419
|
+
const prog = basename2(words[0].text);
|
|
25462
26420
|
let rest;
|
|
25463
26421
|
if (prog === "npx" || prog === "bunx" || prog === "pnpx") {
|
|
25464
26422
|
rest = skipRunnerOptions(words.slice(1));
|
|
@@ -25504,7 +26462,7 @@ var PROGRAM_MATCHERS = /* @__PURE__ */ new Map([
|
|
|
25504
26462
|
["git", (cmd, args2) => gitMatch(cmd, args2)]
|
|
25505
26463
|
]);
|
|
25506
26464
|
function recogniseWords(cmd, words) {
|
|
25507
|
-
const prog =
|
|
26465
|
+
const prog = basename2(words[0].text);
|
|
25508
26466
|
const args2 = words.slice(1);
|
|
25509
26467
|
const own = PROGRAM_MATCHERS.get(prog);
|
|
25510
26468
|
if (own !== void 0)
|
|
@@ -25585,7 +26543,7 @@ function findMatch(cmd, args2) {
|
|
|
25585
26543
|
if (t === FIND_DELETE_ACTION)
|
|
25586
26544
|
deletes = true;
|
|
25587
26545
|
if ((t === "-exec" || t === "-execdir" || t === "-ok" || t === "-okdir") && i + 1 < args2.length) {
|
|
25588
|
-
const inner =
|
|
26546
|
+
const inner = basename2(args2[i + 1].text);
|
|
25589
26547
|
const viaGit = inner === "git" && args2[i + 2]?.text === "rm";
|
|
25590
26548
|
if (FIND_DELETERS.has(inner) || viaGit) {
|
|
25591
26549
|
deletes = true;
|
|
@@ -25633,8 +26591,9 @@ function findFilterOf(expr) {
|
|
|
25633
26591
|
return names.length === 0 && excluded.length === 0 ? void 0 : { names, excluded };
|
|
25634
26592
|
}
|
|
25635
26593
|
var GIT_GLOBAL_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env", "--super-prefix", "--exec-path", "--attr-source", "--list-cmds"]);
|
|
25636
|
-
function
|
|
26594
|
+
function gitArgsOf(args2) {
|
|
25637
26595
|
const dirs = [];
|
|
26596
|
+
const config2 = [];
|
|
25638
26597
|
let gitDir;
|
|
25639
26598
|
let workTree;
|
|
25640
26599
|
let i = 0;
|
|
@@ -25660,11 +26619,92 @@ function gitMatch(cmd, args2) {
|
|
|
25660
26619
|
gitDir = value;
|
|
25661
26620
|
if (name === "--work-tree" && value)
|
|
25662
26621
|
workTree = value;
|
|
26622
|
+
if ((name === "-c" || name === "--config-env") && value)
|
|
26623
|
+
config2.push({ word: value, fromEnv: name === "--config-env" });
|
|
25663
26624
|
}
|
|
25664
|
-
|
|
25665
|
-
|
|
26625
|
+
return { git: { dirs, ...gitDir ? { gitDir } : {}, ...workTree ? { workTree } : {} }, at: i, config: config2 };
|
|
26626
|
+
}
|
|
26627
|
+
function gitMatch(cmd, args2) {
|
|
26628
|
+
const { git: git2, at, config: config2 } = gitArgsOf(args2);
|
|
26629
|
+
const sub = args2[at]?.text;
|
|
25666
26630
|
const own = sub === void 0 ? void 0 : GIT_SUBCOMMAND_MATCHERS.get(sub);
|
|
25667
|
-
return own === void 0 ? [] : own(cmd, args2.slice(
|
|
26631
|
+
return own === void 0 ? [] : own(cmd, args2.slice(at + 1), git2, config2);
|
|
26632
|
+
}
|
|
26633
|
+
var PUSH_ALL_REFS_OPTION = "--mirror";
|
|
26634
|
+
function hasLongAsGitReads(args2, name) {
|
|
26635
|
+
for (const a of args2) {
|
|
26636
|
+
if (a.text === "--")
|
|
26637
|
+
return false;
|
|
26638
|
+
const spelled = a.text.split("=")[0];
|
|
26639
|
+
if (spelled.length >= 3 && spelled.startsWith("--") && name.startsWith(spelled))
|
|
26640
|
+
return true;
|
|
26641
|
+
}
|
|
26642
|
+
return false;
|
|
26643
|
+
}
|
|
26644
|
+
function configNameOf(c) {
|
|
26645
|
+
const t = c.word.text;
|
|
26646
|
+
const eq = t.indexOf("=");
|
|
26647
|
+
return eq < 0 ? t : t.slice(0, eq);
|
|
26648
|
+
}
|
|
26649
|
+
function configValueOf(c) {
|
|
26650
|
+
if (c.fromEnv || !c.word.literal)
|
|
26651
|
+
return null;
|
|
26652
|
+
const t = c.word.text;
|
|
26653
|
+
const eq = t.indexOf("=");
|
|
26654
|
+
return eq < 0 ? true : t.slice(eq + 1);
|
|
26655
|
+
}
|
|
26656
|
+
function configNameUnknown(name) {
|
|
26657
|
+
return /[$`*?[]/.test(name);
|
|
26658
|
+
}
|
|
26659
|
+
var GIT_FALSE = /^(false|no|off|[-+]?0+)?$/i;
|
|
26660
|
+
function configMirrors(config2) {
|
|
26661
|
+
return config2.some((c) => {
|
|
26662
|
+
const name = configNameOf(c);
|
|
26663
|
+
if (configNameUnknown(name))
|
|
26664
|
+
return true;
|
|
26665
|
+
if (!/^remote\..+\.mirror$/i.test(name))
|
|
26666
|
+
return false;
|
|
26667
|
+
const value = configValueOf(c);
|
|
26668
|
+
return value === true || value === null || !GIT_FALSE.test(value);
|
|
26669
|
+
});
|
|
26670
|
+
}
|
|
26671
|
+
function pushMirrors(rest, config2) {
|
|
26672
|
+
return hasLongAsGitReads(rest, PUSH_ALL_REFS_OPTION) || configMirrors(config2);
|
|
26673
|
+
}
|
|
26674
|
+
var SAVED_NAMESPACE = SAVED_REF_PREFIX.split("/").slice(0, 2).join("/") + "/";
|
|
26675
|
+
function refspecSendsSaves(text) {
|
|
26676
|
+
if (text.includes(SAVED_NAMESPACE))
|
|
26677
|
+
return true;
|
|
26678
|
+
const src = text.replace(/^\+/, "").split(":")[0];
|
|
26679
|
+
const star = src.indexOf("*");
|
|
26680
|
+
return star >= 0 && SAVED_NAMESPACE.startsWith(src.slice(0, star));
|
|
26681
|
+
}
|
|
26682
|
+
function configPushSendsSaves(config2) {
|
|
26683
|
+
return config2.some((c) => {
|
|
26684
|
+
const name = configNameOf(c);
|
|
26685
|
+
if (configNameUnknown(name))
|
|
26686
|
+
return true;
|
|
26687
|
+
if (!/^remote\..+\.push$/i.test(name))
|
|
26688
|
+
return false;
|
|
26689
|
+
const value = configValueOf(c);
|
|
26690
|
+
return value === null || value !== true && refspecSendsSaves(value);
|
|
26691
|
+
});
|
|
26692
|
+
}
|
|
26693
|
+
function pushesSavedRefs(command) {
|
|
26694
|
+
for (const cmd of splitCommand(command)) {
|
|
26695
|
+
if (cmd.words.length === 0 || basename2(cmd.words[0].text) !== "git")
|
|
26696
|
+
continue;
|
|
26697
|
+
const args2 = cmd.words.slice(1);
|
|
26698
|
+
const { at, config: config2 } = gitArgsOf(args2);
|
|
26699
|
+
if (args2[at]?.text !== "push")
|
|
26700
|
+
continue;
|
|
26701
|
+
const rest = args2.slice(at + 1);
|
|
26702
|
+
if (pushMirrors(rest, config2) || configPushSendsSaves(config2))
|
|
26703
|
+
return true;
|
|
26704
|
+
if (rest.some((w) => refspecSendsSaves(w.text)))
|
|
26705
|
+
return true;
|
|
26706
|
+
}
|
|
26707
|
+
return false;
|
|
25668
26708
|
}
|
|
25669
26709
|
var GIT_SUBCOMMAND_MATCHERS = /* @__PURE__ */ new Map([
|
|
25670
26710
|
["rm", (cmd, rest, git2) => gitRm(cmd, rest, git2)],
|
|
@@ -25675,7 +26715,7 @@ var GIT_SUBCOMMAND_MATCHERS = /* @__PURE__ */ new Map([
|
|
|
25675
26715
|
["clean", (cmd, rest, git2) => gitClean(cmd, rest, git2)],
|
|
25676
26716
|
["stash", (cmd, rest, git2) => gitStash(cmd, rest, git2)],
|
|
25677
26717
|
["worktree", (cmd, rest, git2) => gitWorktree(cmd, rest, git2)],
|
|
25678
|
-
["push", (cmd, rest, git2) => gitPush(cmd, rest, git2)],
|
|
26718
|
+
["push", (cmd, rest, git2, config2) => gitPush(cmd, rest, git2, config2)],
|
|
25679
26719
|
["branch", (cmd, rest, git2) => gitBranch(cmd, rest, git2)]
|
|
25680
26720
|
]);
|
|
25681
26721
|
function gitSwitch(cmd, rest, git2) {
|
|
@@ -25700,7 +26740,8 @@ function gitRm(cmd, rest, git2) {
|
|
|
25700
26740
|
const targets = operands(rest, /* @__PURE__ */ new Set(["--pathspec-from-file"]));
|
|
25701
26741
|
if (targets.length === 0)
|
|
25702
26742
|
return [];
|
|
25703
|
-
|
|
26743
|
+
const form = gitRmRecursive(cmd.words) ? "recursive" : "plain";
|
|
26744
|
+
return [{ type: "delete-files", form, program: "git rm", targets, git: git2, command: cmd }];
|
|
25704
26745
|
}
|
|
25705
26746
|
function gitReset(cmd, rest, git2) {
|
|
25706
26747
|
const hard = hasLong(rest, "--hard");
|
|
@@ -25753,7 +26794,7 @@ function gitWorktree(cmd, rest, git2) {
|
|
|
25753
26794
|
return [{ type: "discard-unsaved", form: "worktree-remove", paths: operands(args2), git: git2, command: cmd }];
|
|
25754
26795
|
}
|
|
25755
26796
|
var PUSH_VALUE_OPTIONS = /* @__PURE__ */ new Set(["-o", "--push-option", "--repo", "--receive-pack", "--exec"]);
|
|
25756
|
-
function gitPush(cmd, rest, git2) {
|
|
26797
|
+
function gitPush(cmd, rest, git2, config2) {
|
|
25757
26798
|
if (hasShort(rest, "n") || hasLong(rest, "--dry-run"))
|
|
25758
26799
|
return [];
|
|
25759
26800
|
const ops = operands(rest, PUSH_VALUE_OPTIONS);
|
|
@@ -25762,7 +26803,7 @@ function gitPush(cmd, rest, git2) {
|
|
|
25762
26803
|
const force = hasShort(rest, "f") || hasLong(rest, "--force");
|
|
25763
26804
|
const forceWithLease = hasLong(rest, "--force-with-lease");
|
|
25764
26805
|
const forceIfIncludes = hasLong(rest, "--force-if-includes");
|
|
25765
|
-
const mirror =
|
|
26806
|
+
const mirror = pushMirrors(rest, config2);
|
|
25766
26807
|
const deletes = hasShort(rest, "d") || hasLong(rest, "--delete") || refspecs.some((r) => r.text.startsWith(":") && r.text.length > 1);
|
|
25767
26808
|
const plusRefspec = refspecs.some((r) => r.text.startsWith("+"));
|
|
25768
26809
|
if (!(force || forceWithLease || forceIfIncludes || mirror || deletes || plusRefspec))
|
|
@@ -25802,7 +26843,7 @@ function wipeDataMatch(cmd, prog, args2, words) {
|
|
|
25802
26843
|
const execAt = args2.findIndex((a) => a.text === "exec" || a.text === "run");
|
|
25803
26844
|
if (execAt >= 0) {
|
|
25804
26845
|
for (let i = execAt + 1; i < args2.length; i++) {
|
|
25805
|
-
const inner =
|
|
26846
|
+
const inner = basename2(args2[i].text);
|
|
25806
26847
|
if (SQL_CLIENTS.has(inner) || inner === "dropdb" || inner === "redis-cli") {
|
|
25807
26848
|
return wipeDataMatch(cmd, inner, args2.slice(i + 1), args2.slice(i));
|
|
25808
26849
|
}
|
|
@@ -26135,7 +27176,7 @@ function shellRecordMatches(cmd, found2) {
|
|
|
26135
27176
|
for (const m of found2) {
|
|
26136
27177
|
if (m.type !== "delete-files" || m.program === "find" || m.program === "git clean" || m.command !== cmd)
|
|
26137
27178
|
continue;
|
|
26138
|
-
const recursive = m.form === "recursive"
|
|
27179
|
+
const recursive = m.form === "recursive";
|
|
26139
27180
|
const targets = recursive ? m.targets : m.targets.filter((w) => mayBeRecordWord(w));
|
|
26140
27181
|
if (targets.length === 0)
|
|
26141
27182
|
continue;
|
|
@@ -26149,7 +27190,7 @@ function shellRecordMatches(cmd, found2) {
|
|
|
26149
27190
|
});
|
|
26150
27191
|
}
|
|
26151
27192
|
const words = cmd.words;
|
|
26152
|
-
const prog = words.length > 0 ?
|
|
27193
|
+
const prog = words.length > 0 ? basename2(words[0].text) : "";
|
|
26153
27194
|
const args2 = words.slice(1);
|
|
26154
27195
|
if (prog === TRUNCATE_PROGRAM) {
|
|
26155
27196
|
const targets = operands(args2, /* @__PURE__ */ new Set(["-s", "--size", "-r", "--reference"])).filter((w) => mayBeRecordWord(w));
|
|
@@ -26182,7 +27223,7 @@ function movesStdout(r) {
|
|
|
26182
27223
|
return [">", ">>", ">|", ">&"].includes(r.op) && (r.fd === void 0 || r.fd === "1");
|
|
26183
27224
|
}
|
|
26184
27225
|
function savedObjectShown(words) {
|
|
26185
|
-
if (words.length !== 3 ||
|
|
27226
|
+
if (words.length !== 3 || basename2(words[0].text) !== "git" || words[1].text !== "show" || !words[2].literal)
|
|
26186
27227
|
return void 0;
|
|
26187
27228
|
return /^[0-9a-f]{7,64}:[^:]+$/.test(words[2].text) ? words[2].text : void 0;
|
|
26188
27229
|
}
|
|
@@ -26285,13 +27326,13 @@ var Env = class {
|
|
|
26285
27326
|
description;
|
|
26286
27327
|
projectCommonDir;
|
|
26287
27328
|
constructor(ctx) {
|
|
26288
|
-
this.projectDir = realOrSelf(
|
|
26289
|
-
this.cwd = realOrSelf(
|
|
26290
|
-
this.home = realOrSelf(
|
|
27329
|
+
this.projectDir = realOrSelf(resolve4(ctx.projectDir));
|
|
27330
|
+
this.cwd = realOrSelf(resolve4(this.projectDir, ctx.cwd));
|
|
27331
|
+
this.home = realOrSelf(resolve4(ctx.home ?? homedir7()));
|
|
26291
27332
|
this.timeoutMs = ctx.gitTimeoutMs ?? FACTS_GIT_TIMEOUT_MS;
|
|
26292
|
-
this.tempDir = realOrSelf(
|
|
27333
|
+
this.tempDir = realOrSelf(resolve4(ctx.tempDir ?? tmpdir2()));
|
|
26293
27334
|
const roots = ctx.tempRoots ?? [this.tempDir, tmpdir2(), ...TEMP_ROOTS];
|
|
26294
|
-
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(
|
|
27335
|
+
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(resolve4(r))))].filter((r) => !isInside(this.projectDir, r));
|
|
26295
27336
|
}
|
|
26296
27337
|
isTemp(abs) {
|
|
26297
27338
|
return this.tempRoots.some((r) => isInside(abs, r));
|
|
@@ -26301,7 +27342,7 @@ var Env = class {
|
|
|
26301
27342
|
}
|
|
26302
27343
|
display(abs) {
|
|
26303
27344
|
if (isInside(abs, this.projectDir)) {
|
|
26304
|
-
const rel =
|
|
27345
|
+
const rel = relative5(this.projectDir, abs);
|
|
26305
27346
|
return rel === "" ? "." : toSlash(rel);
|
|
26306
27347
|
}
|
|
26307
27348
|
return toSlash(abs);
|
|
@@ -26311,7 +27352,7 @@ var Env = class {
|
|
|
26311
27352
|
return this.description;
|
|
26312
27353
|
this.description = null;
|
|
26313
27354
|
try {
|
|
26314
|
-
const raw = JSON.parse(
|
|
27355
|
+
const raw = JSON.parse(readFileSync19(join12(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
|
|
26315
27356
|
const checked = checkSystemDescription(raw);
|
|
26316
27357
|
if (checked.ok)
|
|
26317
27358
|
this.description = checked.description;
|
|
@@ -26342,7 +27383,7 @@ var Env = class {
|
|
|
26342
27383
|
return false;
|
|
26343
27384
|
let dir = abs;
|
|
26344
27385
|
while (!isDirectory(dir)) {
|
|
26345
|
-
const up =
|
|
27386
|
+
const up = dirname6(dir);
|
|
26346
27387
|
if (up === dir)
|
|
26347
27388
|
return false;
|
|
26348
27389
|
dir = up;
|
|
@@ -26354,40 +27395,40 @@ var Env = class {
|
|
|
26354
27395
|
if (!r.ok)
|
|
26355
27396
|
return null;
|
|
26356
27397
|
const d = r.value.trim();
|
|
26357
|
-
return d === "" ? null : realOrSelf(
|
|
27398
|
+
return d === "" ? null : realOrSelf(resolve4(dir, d));
|
|
26358
27399
|
}
|
|
26359
27400
|
};
|
|
26360
27401
|
function realOrSelf(p) {
|
|
26361
27402
|
try {
|
|
26362
|
-
return
|
|
27403
|
+
return realpathSync6(p);
|
|
26363
27404
|
} catch {
|
|
26364
27405
|
return p;
|
|
26365
27406
|
}
|
|
26366
27407
|
}
|
|
26367
27408
|
function toSlash(p) {
|
|
26368
|
-
return
|
|
27409
|
+
return sep3 === "/" ? p : p.split(sep3).join("/");
|
|
26369
27410
|
}
|
|
26370
27411
|
function isInside(child, parent) {
|
|
26371
|
-
const rel =
|
|
26372
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
27412
|
+
const rel = relative5(parent, child);
|
|
27413
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
26373
27414
|
}
|
|
26374
27415
|
function resolveHome(text, home, base) {
|
|
26375
27416
|
if (text === "~")
|
|
26376
27417
|
return home;
|
|
26377
27418
|
if (text.startsWith("~/"))
|
|
26378
|
-
return
|
|
26379
|
-
return
|
|
27419
|
+
return join12(home, text.slice(2));
|
|
27420
|
+
return resolve4(base, text);
|
|
26380
27421
|
}
|
|
26381
27422
|
function realTarget(abs) {
|
|
26382
|
-
const parent =
|
|
27423
|
+
const parent = dirname6(abs);
|
|
26383
27424
|
if (parent === abs)
|
|
26384
27425
|
return abs;
|
|
26385
|
-
return
|
|
27426
|
+
return join12(realOrSelf(parent), pathBasename(abs));
|
|
26386
27427
|
}
|
|
26387
27428
|
function wordPath(word, base, env) {
|
|
26388
27429
|
if (word.temp !== void 0) {
|
|
26389
|
-
const root = word.temp.base === "mktemp" ?
|
|
26390
|
-
return
|
|
27430
|
+
const root = word.temp.base === "mktemp" ? join12(env.tempDir, ".halfcycle-mktemp-new") : env.tempDir;
|
|
27431
|
+
return resolve4(`${root}${word.temp.suffix}`);
|
|
26391
27432
|
}
|
|
26392
27433
|
return word.literal ? resolveHome(word.text, env.home, base) : null;
|
|
26393
27434
|
}
|
|
@@ -26396,7 +27437,7 @@ function follow(start, prefix, dirs, m, env) {
|
|
|
26396
27437
|
let fresh = start.fresh;
|
|
26397
27438
|
let freshRoot = start.freshRoot;
|
|
26398
27439
|
const seen = [...prefix];
|
|
26399
|
-
const insideFresh = (p) => freshRoot !== void 0 && isInside(
|
|
27440
|
+
const insideFresh = (p) => freshRoot !== void 0 && isInside(resolve4(p), resolve4(freshRoot));
|
|
26400
27441
|
const mark = (by) => {
|
|
26401
27442
|
if (fresh !== void 0 && insideFresh(cwd))
|
|
26402
27443
|
return;
|
|
@@ -26525,14 +27566,14 @@ function expandBraces(text) {
|
|
|
26525
27566
|
return body.split(",").map((b) => `${head}${b}${tail}`);
|
|
26526
27567
|
}
|
|
26527
27568
|
function globPath(abs) {
|
|
26528
|
-
const root = abs.startsWith(
|
|
26529
|
-
const segments = abs.split(
|
|
27569
|
+
const root = abs.startsWith(sep3) ? sep3 : "";
|
|
27570
|
+
const segments = abs.split(sep3).filter((s) => s !== "");
|
|
26530
27571
|
let current = [root === "" ? "." : root];
|
|
26531
27572
|
for (const segment of segments) {
|
|
26532
27573
|
const next = [];
|
|
26533
27574
|
if (!/[*?[]/.test(segment)) {
|
|
26534
27575
|
for (const c of current)
|
|
26535
|
-
next.push(
|
|
27576
|
+
next.push(join12(c, segment));
|
|
26536
27577
|
} else {
|
|
26537
27578
|
const re = segmentPattern(segment);
|
|
26538
27579
|
for (const c of current) {
|
|
@@ -26546,7 +27587,7 @@ function globPath(abs) {
|
|
|
26546
27587
|
if (n.startsWith(".") && !segment.startsWith("."))
|
|
26547
27588
|
continue;
|
|
26548
27589
|
if (re.test(n))
|
|
26549
|
-
next.push(
|
|
27590
|
+
next.push(join12(c, n));
|
|
26550
27591
|
}
|
|
26551
27592
|
}
|
|
26552
27593
|
}
|
|
@@ -26582,7 +27623,7 @@ function segmentPattern(segment) {
|
|
|
26582
27623
|
}
|
|
26583
27624
|
function pathExists(p) {
|
|
26584
27625
|
try {
|
|
26585
|
-
|
|
27626
|
+
lstatSync2(p);
|
|
26586
27627
|
return true;
|
|
26587
27628
|
} catch {
|
|
26588
27629
|
return false;
|
|
@@ -26590,28 +27631,28 @@ function pathExists(p) {
|
|
|
26590
27631
|
}
|
|
26591
27632
|
function isEmptyFolder(p) {
|
|
26592
27633
|
try {
|
|
26593
|
-
return
|
|
27634
|
+
return lstatSync2(p).isDirectory() && readdirSync8(p).length === 0;
|
|
26594
27635
|
} catch {
|
|
26595
27636
|
return false;
|
|
26596
27637
|
}
|
|
26597
27638
|
}
|
|
26598
27639
|
function isFolder(p) {
|
|
26599
27640
|
try {
|
|
26600
|
-
return
|
|
27641
|
+
return statSync7(p).isDirectory();
|
|
26601
27642
|
} catch {
|
|
26602
27643
|
return false;
|
|
26603
27644
|
}
|
|
26604
27645
|
}
|
|
26605
27646
|
function isDirectory(p) {
|
|
26606
27647
|
try {
|
|
26607
|
-
return
|
|
27648
|
+
return lstatSync2(p).isDirectory();
|
|
26608
27649
|
} catch {
|
|
26609
27650
|
return false;
|
|
26610
27651
|
}
|
|
26611
27652
|
}
|
|
26612
|
-
function filesUnder(targets, repoRoot, env, keep) {
|
|
27653
|
+
function filesUnder(targets, repoRoot, env, keep, seen) {
|
|
26613
27654
|
const specs = targets.map((t) => {
|
|
26614
|
-
const rel = toSlash(
|
|
27655
|
+
const rel = toSlash(relative5(repoRoot, t));
|
|
26615
27656
|
return rel === "" ? "." : rel;
|
|
26616
27657
|
});
|
|
26617
27658
|
const tracked = env.git(["--literal-pathspecs", "ls-files", "--full-name", "-z", "--", ...specs], repoRoot);
|
|
@@ -26645,6 +27686,8 @@ function filesUnder(targets, repoRoot, env, keep) {
|
|
|
26645
27686
|
goneFromDisk.add(e.path);
|
|
26646
27687
|
}
|
|
26647
27688
|
}
|
|
27689
|
+
if (seen !== void 0)
|
|
27690
|
+
seen.ignored = ignored.length;
|
|
26648
27691
|
const out = /* @__PURE__ */ new Map();
|
|
26649
27692
|
for (const path of tracked.value.split("\0")) {
|
|
26650
27693
|
if (path === "" || goneFromDisk.has(path))
|
|
@@ -26672,12 +27715,12 @@ function findKeeps(filter, roots, repoRoot) {
|
|
|
26672
27715
|
const names = filter.names.map((n) => findPattern(n.pattern, n.fold, false));
|
|
26673
27716
|
const excluded = filter.excluded.map((p) => findPattern(p, false, true));
|
|
26674
27717
|
const steps = (rel) => {
|
|
26675
|
-
const abs =
|
|
27718
|
+
const abs = join12(repoRoot, rel);
|
|
26676
27719
|
const out = [];
|
|
26677
27720
|
for (const r of roots) {
|
|
26678
27721
|
if (!isInside(abs, r.abs))
|
|
26679
27722
|
continue;
|
|
26680
|
-
const below = toSlash(
|
|
27723
|
+
const below = toSlash(relative5(r.abs, abs)).split("/").filter((x) => x !== "");
|
|
26681
27724
|
const rootName = r.text === null ? pathBasename(r.abs) : r.text.replace(/\/+$/, "").split("/").pop() || r.text;
|
|
26682
27725
|
const prefixes = r.text === null ? [] : [.../* @__PURE__ */ new Set([r.text, r.text.replace(/\/+$/, "") || r.text])];
|
|
26683
27726
|
const chain = [{ name: rootName, printed: r.text === null ? [] : [r.text] }];
|
|
@@ -26726,27 +27769,27 @@ function findPattern(pattern, fold, whole) {
|
|
|
26726
27769
|
return new RegExp(`${re}$`, fold ? "i" : "");
|
|
26727
27770
|
}
|
|
26728
27771
|
function walkRoots(ignoredFolders, specs) {
|
|
26729
|
-
const
|
|
27772
|
+
const under2 = (child, parent) => parent === "." || child === parent || child.startsWith(`${parent}/`);
|
|
26730
27773
|
const roots = /* @__PURE__ */ new Set();
|
|
26731
27774
|
for (const raw of ignoredFolders) {
|
|
26732
27775
|
const folder = raw.replace(/\/+$/, "");
|
|
26733
27776
|
for (const spec of specs) {
|
|
26734
|
-
if (
|
|
27777
|
+
if (under2(folder, spec))
|
|
26735
27778
|
roots.add(folder);
|
|
26736
|
-
else if (
|
|
27779
|
+
else if (under2(spec, folder))
|
|
26737
27780
|
roots.add(spec);
|
|
26738
27781
|
}
|
|
26739
27782
|
}
|
|
26740
27783
|
const all = [...roots];
|
|
26741
|
-
return all.filter((r) => !all.some((o) => o !== r &&
|
|
27784
|
+
return all.filter((r) => !all.some((o) => o !== r && under2(r, o)));
|
|
26742
27785
|
}
|
|
26743
27786
|
function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
26744
27787
|
const out = [];
|
|
26745
27788
|
const stack = [];
|
|
26746
27789
|
for (const f of folders) {
|
|
26747
|
-
if (isDirectory(
|
|
27790
|
+
if (isDirectory(join12(repoRoot, f)))
|
|
26748
27791
|
stack.push(f);
|
|
26749
|
-
else if (pathExists(
|
|
27792
|
+
else if (pathExists(join12(repoRoot, f)) && isCredentialPath(f))
|
|
26750
27793
|
out.push(f);
|
|
26751
27794
|
}
|
|
26752
27795
|
let seen = 0;
|
|
@@ -26754,7 +27797,7 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
26754
27797
|
const rel = stack.pop();
|
|
26755
27798
|
let entries;
|
|
26756
27799
|
try {
|
|
26757
|
-
entries = readdirSync8(
|
|
27800
|
+
entries = readdirSync8(join12(repoRoot, rel), { withFileTypes: true });
|
|
26758
27801
|
} catch {
|
|
26759
27802
|
continue;
|
|
26760
27803
|
}
|
|
@@ -26851,7 +27894,7 @@ function deleteFilesFacts(m, env) {
|
|
|
26851
27894
|
for (const raw of expanded) {
|
|
26852
27895
|
const abs = realTarget(raw);
|
|
26853
27896
|
written.set(abs, word.literal ? word.text : null);
|
|
26854
|
-
if (abs ===
|
|
27897
|
+
if (abs === sep3 || abs === env.home || abs !== env.projectDir && isInside(env.projectDir, abs)) {
|
|
26855
27898
|
guarded.push(abs);
|
|
26856
27899
|
} else if (isInside(abs, env.projectDir) || !env.isTemp(abs) && env.sameRepository(abs)) {
|
|
26857
27900
|
if (pathExists(abs))
|
|
@@ -26863,20 +27906,22 @@ function deleteFilesFacts(m, env) {
|
|
|
26863
27906
|
return cannot({ kind: "paths", paths: guarded.map((g) => toSlash(g)) });
|
|
26864
27907
|
if (unseen)
|
|
26865
27908
|
return cannot(null);
|
|
26866
|
-
const plainFolderRefused = m.form === "plain"
|
|
27909
|
+
const plainFolderRefused = m.form === "plain";
|
|
26867
27910
|
const targets = plainFolderRefused ? inside.filter((t) => !isDirectory(t)) : inside;
|
|
26868
27911
|
if (targets.length === 0)
|
|
26869
27912
|
return silentDelete(m);
|
|
26870
|
-
const repoRoot = env.repoRoot(
|
|
27913
|
+
const repoRoot = env.repoRoot(dirname6(targets[0])) ?? env.repoRoot(env.projectDir);
|
|
26871
27914
|
if (repoRoot === null || !targets.every((t) => isInside(t, repoRoot)))
|
|
26872
27915
|
return cannot(thingFor([], targets, null, env));
|
|
26873
27916
|
const keep = m.findFilter === void 0 ? void 0 : findKeeps(m.findFilter, targets.map((t) => ({ abs: t, text: written.get(t) ?? null })), repoRoot);
|
|
26874
|
-
const
|
|
27917
|
+
const seen = { ignored: 0 };
|
|
27918
|
+
const files = filesUnder(targets, repoRoot, env, keep, seen);
|
|
26875
27919
|
if (files === null)
|
|
26876
27920
|
return cannot(thingFor([], targets, repoRoot, env));
|
|
26877
27921
|
const n = files.length;
|
|
26878
27922
|
const k = files.filter((f) => f.neverSaved).length;
|
|
26879
|
-
const
|
|
27923
|
+
const closeFold = m.form !== "find" && seen.ignored === 0 && closeFoldsAll(targets, env);
|
|
27924
|
+
const atStake = m.form === "plain" || closeFold ? k > 0 : n > 0;
|
|
26880
27925
|
if (!atStake)
|
|
26881
27926
|
return silentDelete(m);
|
|
26882
27927
|
const thing = thingFor(files.map((f) => f.path), targets, repoRoot, env);
|
|
@@ -26889,9 +27934,38 @@ function deleteFilesFacts(m, env) {
|
|
|
26889
27934
|
...m.form === "find" ? { cannotTell: "which-files" } : {},
|
|
26890
27935
|
thing,
|
|
26891
27936
|
files: m.form === "find" ? null : n,
|
|
26892
|
-
neverSaved: m.form === "find" ? null : k
|
|
27937
|
+
neverSaved: m.form === "find" ? null : k,
|
|
27938
|
+
...base.pre.length === 0 ? {
|
|
27939
|
+
save: {
|
|
27940
|
+
repoRoot,
|
|
27941
|
+
paths: targets.map((t) => toSlash(relative5(repoRoot, t)) || "."),
|
|
27942
|
+
...keep === void 0 ? {} : { keep: (rel) => keep.deletes(rel) },
|
|
27943
|
+
lines: []
|
|
27944
|
+
}
|
|
27945
|
+
} : {}
|
|
26893
27946
|
};
|
|
26894
27947
|
}
|
|
27948
|
+
function closeFoldsAll(targets, env) {
|
|
27949
|
+
return targets.every((t) => {
|
|
27950
|
+
const root = recordRoot(t, env);
|
|
27951
|
+
if (root === null || !isInside(t, root))
|
|
27952
|
+
return false;
|
|
27953
|
+
const rel = toSlash(relative5(root, t));
|
|
27954
|
+
if (rel === "" || recordPatternFor(rel)?.deleteAsked !== false)
|
|
27955
|
+
return false;
|
|
27956
|
+
let at = root;
|
|
27957
|
+
for (const seg of rel.split("/")) {
|
|
27958
|
+
at = join12(at, seg);
|
|
27959
|
+
try {
|
|
27960
|
+
if (lstatSync2(at).isSymbolicLink())
|
|
27961
|
+
return false;
|
|
27962
|
+
} catch {
|
|
27963
|
+
return false;
|
|
27964
|
+
}
|
|
27965
|
+
}
|
|
27966
|
+
return true;
|
|
27967
|
+
});
|
|
27968
|
+
}
|
|
26895
27969
|
function silentDelete(m) {
|
|
26896
27970
|
return { type: m.type, match: m, atStake: false, lostForGood: [], recoverable: [], thing: null, files: 0, neverSaved: 0 };
|
|
26897
27971
|
}
|
|
@@ -26949,6 +28023,7 @@ function discardUnsavedFacts(m, env) {
|
|
|
26949
28023
|
const repoRoot = env.repoRoot(where.cwd);
|
|
26950
28024
|
const part = repoRoot === null ? null : owningPart(lost, repoRoot, env);
|
|
26951
28025
|
const thing = part !== null ? { kind: "part", name: part } : { kind: "paths", paths: lost };
|
|
28026
|
+
const saveable = repoRoot !== null && where.pre.length === 0 && m.form !== "stash-drop" && m.form !== "stash-clear" && m.form !== "worktree-remove";
|
|
26952
28027
|
return {
|
|
26953
28028
|
type: m.type,
|
|
26954
28029
|
match: m,
|
|
@@ -26956,7 +28031,8 @@ function discardUnsavedFacts(m, env) {
|
|
|
26956
28031
|
lostForGood: [{ kind: "unsaved-changes", count: lost.length }],
|
|
26957
28032
|
recoverable: [],
|
|
26958
28033
|
thing,
|
|
26959
|
-
files: lost.length
|
|
28034
|
+
files: lost.length,
|
|
28035
|
+
...saveable ? { save: { repoRoot, paths: lost, lines: [] } } : {}
|
|
26960
28036
|
};
|
|
26961
28037
|
}
|
|
26962
28038
|
function changedFiles(where, paths, which, env, skipUnmerged = false) {
|
|
@@ -27009,8 +28085,9 @@ function restoreTouchesIndex(m) {
|
|
|
27009
28085
|
}
|
|
27010
28086
|
function cleanFacts(m, env) {
|
|
27011
28087
|
const where = gitFolder(m, env);
|
|
27012
|
-
const
|
|
27013
|
-
|
|
28088
|
+
const cleaned = where === null ? null : cleanFiles(m, where, env);
|
|
28089
|
+
const lost = cleaned === null ? null : cleaned.lost;
|
|
28090
|
+
if (where === null || cleaned === null || lost === null) {
|
|
27014
28091
|
return { type: m.type, match: m, atStake: true, lostForGood: [], recoverable: [], cannotTell: "which-files", thing: null, files: null, neverSaved: null };
|
|
27015
28092
|
}
|
|
27016
28093
|
if (lost.length === 0)
|
|
@@ -27025,7 +28102,8 @@ function cleanFacts(m, env) {
|
|
|
27025
28102
|
recoverable: [],
|
|
27026
28103
|
thing: part !== null ? { kind: "part", name: part } : { kind: "paths", paths: lost },
|
|
27027
28104
|
files: lost.length,
|
|
27028
|
-
neverSaved: lost.length
|
|
28105
|
+
neverSaved: lost.length,
|
|
28106
|
+
...repoRoot !== null && where.pre.length === 0 ? { save: { repoRoot, paths: cleaned.removed, lines: [] } } : {}
|
|
27029
28107
|
};
|
|
27030
28108
|
}
|
|
27031
28109
|
function cleanFiles(m, where, env) {
|
|
@@ -27062,12 +28140,15 @@ function cleanFiles(m, where, env) {
|
|
|
27062
28140
|
return null;
|
|
27063
28141
|
const named = dry.value.split("\n").map((l) => /^Would remove (.*)$/.exec(l)?.[1]).filter((x) => x !== void 0 && x !== "");
|
|
27064
28142
|
if (named.length === 0)
|
|
27065
|
-
return [];
|
|
28143
|
+
return { lost: [], removed: [] };
|
|
27066
28144
|
const repoRoot = env.repoRoot(where.cwd);
|
|
27067
28145
|
if (repoRoot === null)
|
|
27068
28146
|
return null;
|
|
27069
|
-
const
|
|
27070
|
-
|
|
28147
|
+
const abs = named.map((n) => resolve4(where.cwd, n));
|
|
28148
|
+
const files = filesUnder(abs, repoRoot, env);
|
|
28149
|
+
if (files === null)
|
|
28150
|
+
return null;
|
|
28151
|
+
return { lost: files.filter((f) => f.neverSaved).map((f) => f.path), removed: abs.map((a) => toSlash(relative5(repoRoot, a)) || ".") };
|
|
27071
28152
|
}
|
|
27072
28153
|
function stashFiles(m, where, env) {
|
|
27073
28154
|
let refs;
|
|
@@ -27107,7 +28188,7 @@ function worktreeFiles(m, where, env) {
|
|
|
27107
28188
|
if (path === null)
|
|
27108
28189
|
return null;
|
|
27109
28190
|
const abs = realOrSelf(path);
|
|
27110
|
-
if (!
|
|
28191
|
+
if (!existsSync7(abs))
|
|
27111
28192
|
return [];
|
|
27112
28193
|
const top = env.repoRoot(abs);
|
|
27113
28194
|
if (top === null)
|
|
@@ -27119,19 +28200,23 @@ function worktreeFiles(m, where, env) {
|
|
|
27119
28200
|
}
|
|
27120
28201
|
function unmergedFacts(m, env) {
|
|
27121
28202
|
const where = gitFolder(m, env, ["clone"]);
|
|
27122
|
-
const
|
|
28203
|
+
const leaves = [];
|
|
27123
28204
|
if (where === null) {
|
|
27124
|
-
|
|
28205
|
+
leaves.push({ line: { branch: null, commits: null }, tip: null });
|
|
27125
28206
|
} else if (where.fresh === "clone") {
|
|
27126
|
-
|
|
28207
|
+
leaves.push({ line: { branch: null, commits: 0 }, tip: null });
|
|
27127
28208
|
} else if (m.form === "reset-to") {
|
|
27128
|
-
|
|
28209
|
+
leaves.push(resetLeaves(m.rev, where, env));
|
|
27129
28210
|
} else {
|
|
27130
28211
|
for (const b of m.branches)
|
|
27131
|
-
|
|
28212
|
+
leaves.push(branchLeaves(b, m.remoteTracking, where, env));
|
|
27132
28213
|
}
|
|
28214
|
+
const lines2 = leaves.map((l) => l.line);
|
|
27133
28215
|
const unknown2 = lines2.some((l) => l.commits === null);
|
|
27134
28216
|
const total = lines2.reduce((s, l) => s + (l.commits ?? 0), 0);
|
|
28217
|
+
const kept = leaves.filter((l) => (l.line.commits ?? 0) > 0);
|
|
28218
|
+
const repoRoot = where === null || where.fresh !== void 0 || where.pre.length > 0 ? null : env.repoRoot(where.cwd);
|
|
28219
|
+
const saveable = !unknown2 && repoRoot !== null && kept.every((l) => l.tip !== null);
|
|
27135
28220
|
return {
|
|
27136
28221
|
type: m.type,
|
|
27137
28222
|
match: m,
|
|
@@ -27139,44 +28224,46 @@ function unmergedFacts(m, env) {
|
|
|
27139
28224
|
lostForGood: [],
|
|
27140
28225
|
recoverable: total > 0 ? [{ kind: "local-changes", count: total }] : [],
|
|
27141
28226
|
...unknown2 ? { cannotTell: "which-line" } : {},
|
|
27142
|
-
lines: lines2
|
|
28227
|
+
lines: lines2,
|
|
28228
|
+
...saveable ? { save: { repoRoot, paths: [], lines: kept.map((l) => ({ name: m.remoteTracking ? null : l.line.branch, commit: l.tip })) } } : {}
|
|
27143
28229
|
};
|
|
27144
28230
|
}
|
|
27145
28231
|
function resetLeaves(rev, where, env) {
|
|
27146
28232
|
const current = currentBranch(where, env);
|
|
28233
|
+
const at = (commits, tip = null) => ({ line: { branch: current, commits }, tip });
|
|
27147
28234
|
if (rev === void 0)
|
|
27148
|
-
return
|
|
28235
|
+
return at(0);
|
|
27149
28236
|
let target;
|
|
27150
28237
|
if (rev.gitRev !== void 0) {
|
|
27151
28238
|
const r2 = env.git([...where.pre, ...rev.gitRev], where.cwd);
|
|
27152
28239
|
const id = r2.ok ? r2.value.trim() : "";
|
|
27153
28240
|
if (!/^[0-9a-f]{40,64}$/.test(id))
|
|
27154
|
-
return
|
|
28241
|
+
return at(null);
|
|
27155
28242
|
target = id;
|
|
27156
28243
|
} else {
|
|
27157
28244
|
if (!rev.literal || rev.text.startsWith("-"))
|
|
27158
|
-
return
|
|
28245
|
+
return at(null);
|
|
27159
28246
|
target = resolveCommit(rev.text, where, env);
|
|
27160
28247
|
}
|
|
27161
28248
|
if (target === null)
|
|
27162
|
-
return
|
|
28249
|
+
return at(0);
|
|
27163
28250
|
const head = resolveCommit("HEAD", where, env);
|
|
27164
28251
|
if (head === null)
|
|
27165
|
-
return
|
|
28252
|
+
return at(0);
|
|
27166
28253
|
const excludeCurrent = current === null ? [] : [`--exclude=${current}`];
|
|
27167
28254
|
const r = env.git([...where.pre, "rev-list", head, "--not", target, ...excludeCurrent, "--branches", "--remotes", "--tags"], where.cwd);
|
|
27168
|
-
return
|
|
28255
|
+
return at(r.ok ? lines(r.value).length : null, head);
|
|
27169
28256
|
}
|
|
27170
28257
|
function branchLeaves(b, remoteTracking, where, env) {
|
|
27171
28258
|
if (!b.literal || b.text.startsWith("-"))
|
|
27172
|
-
return { branch: b.literal ? b.text : null, commits: null };
|
|
28259
|
+
return { line: { branch: b.literal ? b.text : null, commits: null }, tip: null };
|
|
27173
28260
|
const ref = remoteTracking ? `refs/remotes/${b.text}` : `refs/heads/${b.text}`;
|
|
27174
28261
|
const tip = resolveCommit(ref, where, env);
|
|
27175
28262
|
if (tip === null)
|
|
27176
|
-
return { branch: b.text, commits: 0 };
|
|
28263
|
+
return { line: { branch: b.text, commits: 0 }, tip: null };
|
|
27177
28264
|
const exclude = remoteTracking ? ["--branches", `--exclude=${b.text}`, "--remotes", "--tags"] : [`--exclude=${b.text}`, "--branches", "--remotes", "--tags"];
|
|
27178
28265
|
const r = env.git([...where.pre, "rev-list", tip, "--not", ...exclude], where.cwd);
|
|
27179
|
-
return { branch: b.text, commits: r.ok ? lines(r.value).length : null };
|
|
28266
|
+
return { line: { branch: b.text, commits: r.ok ? lines(r.value).length : null }, tip };
|
|
27180
28267
|
}
|
|
27181
28268
|
function currentBranch(where, env) {
|
|
27182
28269
|
const r = env.git([...where.pre, "symbolic-ref", "--quiet", "--short", "HEAD"], where.cwd);
|
|
@@ -27451,7 +28538,7 @@ function publishFacts(m, env) {
|
|
|
27451
28538
|
const start = startFolder(m, env);
|
|
27452
28539
|
if (start !== null && PACKAGE_JSON_PROGRAMS.has(m.program)) {
|
|
27453
28540
|
try {
|
|
27454
|
-
const pkg = JSON.parse(
|
|
28541
|
+
const pkg = JSON.parse(readFileSync19(join12(start, "package.json"), "utf-8"));
|
|
27455
28542
|
if (typeof pkg["name"] === "string" && typeof pkg["version"] === "string" && pkg["name"] !== "" && pkg["version"] !== "") {
|
|
27456
28543
|
name = pkg["name"];
|
|
27457
28544
|
version2 = pkg["version"];
|
|
@@ -27499,9 +28586,9 @@ function shrinkRecordFacts(m, env) {
|
|
|
27499
28586
|
for (const file2 of [...found2.values()].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)) {
|
|
27500
28587
|
let before;
|
|
27501
28588
|
try {
|
|
27502
|
-
if (!
|
|
28589
|
+
if (!lstatSync2(file2.abs).isFile())
|
|
27503
28590
|
continue;
|
|
27504
|
-
before =
|
|
28591
|
+
before = readFileSync19(file2.abs, "utf-8");
|
|
27505
28592
|
} catch {
|
|
27506
28593
|
continue;
|
|
27507
28594
|
}
|
|
@@ -27534,7 +28621,8 @@ function shrinkRecordFacts(m, env) {
|
|
|
27534
28621
|
lines: lines2,
|
|
27535
28622
|
removed,
|
|
27536
28623
|
history: h === null ? null : h.history,
|
|
27537
|
-
unsaved
|
|
28624
|
+
unsaved,
|
|
28625
|
+
...base.pre.length === 0 ? { save: { repoRoot: file2.root, paths: [file2.rel], lines: [] } } : {}
|
|
27538
28626
|
};
|
|
27539
28627
|
});
|
|
27540
28628
|
}
|
|
@@ -27552,7 +28640,7 @@ function recordRoot(abs, env) {
|
|
|
27552
28640
|
return null;
|
|
27553
28641
|
let dir = abs;
|
|
27554
28642
|
while (!isDirectory(dir)) {
|
|
27555
|
-
const up =
|
|
28643
|
+
const up = dirname6(dir);
|
|
27556
28644
|
if (up === dir)
|
|
27557
28645
|
return null;
|
|
27558
28646
|
dir = up;
|
|
@@ -27563,7 +28651,7 @@ function recordFileAt(abs, env) {
|
|
|
27563
28651
|
const root = recordRoot(abs, env);
|
|
27564
28652
|
if (root === null || !isInside(abs, root))
|
|
27565
28653
|
return null;
|
|
27566
|
-
const rel = toSlash(
|
|
28654
|
+
const rel = toSlash(relative5(root, abs));
|
|
27567
28655
|
if (rel === "")
|
|
27568
28656
|
return null;
|
|
27569
28657
|
const row = recordPatternFor(rel);
|
|
@@ -27573,11 +28661,11 @@ function recordFilesUnder(folder, env) {
|
|
|
27573
28661
|
const root = recordRoot(folder, env);
|
|
27574
28662
|
if (root === null)
|
|
27575
28663
|
return [];
|
|
27576
|
-
const relFolder = toSlash(
|
|
28664
|
+
const relFolder = toSlash(relative5(root, folder));
|
|
27577
28665
|
const starts = /* @__PURE__ */ new Set();
|
|
27578
28666
|
for (const { pattern } of PROJECT_RECORD_PATTERNS) {
|
|
27579
28667
|
if (relFolder === "" || patternOwns(relFolder, pattern))
|
|
27580
|
-
starts.add(
|
|
28668
|
+
starts.add(join12(root, pattern));
|
|
27581
28669
|
else if (patternOwns(pattern, relFolder))
|
|
27582
28670
|
starts.add(folder);
|
|
27583
28671
|
}
|
|
@@ -27591,7 +28679,7 @@ function recordFilesUnder(folder, env) {
|
|
|
27591
28679
|
seen.add(p);
|
|
27592
28680
|
let st;
|
|
27593
28681
|
try {
|
|
27594
|
-
st =
|
|
28682
|
+
st = lstatSync2(p);
|
|
27595
28683
|
} catch {
|
|
27596
28684
|
continue;
|
|
27597
28685
|
}
|
|
@@ -27603,7 +28691,7 @@ function recordFilesUnder(folder, env) {
|
|
|
27603
28691
|
continue;
|
|
27604
28692
|
}
|
|
27605
28693
|
for (const n of names)
|
|
27606
|
-
stack.push(
|
|
28694
|
+
stack.push(join12(p, n));
|
|
27607
28695
|
} else if (st.isFile()) {
|
|
27608
28696
|
const r = recordFileAt(p, env);
|
|
27609
28697
|
if (r !== null)
|
|
@@ -27616,7 +28704,7 @@ function recordHistory(files, env) {
|
|
|
27616
28704
|
const out = /* @__PURE__ */ new Map();
|
|
27617
28705
|
const byTree = /* @__PURE__ */ new Map();
|
|
27618
28706
|
for (const abs of files) {
|
|
27619
|
-
const top = env.repoRoot(
|
|
28707
|
+
const top = env.repoRoot(dirname6(abs));
|
|
27620
28708
|
if (top === null || !isInside(abs, top))
|
|
27621
28709
|
continue;
|
|
27622
28710
|
const list = byTree.get(top) ?? [];
|
|
@@ -27624,7 +28712,7 @@ function recordHistory(files, env) {
|
|
|
27624
28712
|
byTree.set(top, list);
|
|
27625
28713
|
}
|
|
27626
28714
|
for (const [top, list] of byTree) {
|
|
27627
|
-
const specs = list.map((abs) => toSlash(
|
|
28715
|
+
const specs = list.map((abs) => toSlash(relative5(top, abs)));
|
|
27628
28716
|
const tracked = trackedOf(specs, top, env);
|
|
27629
28717
|
if (tracked === null)
|
|
27630
28718
|
continue;
|
|
@@ -27705,14 +28793,7 @@ function oneLine(text) {
|
|
|
27705
28793
|
return text.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").replace(/\s+/g, " ").trim();
|
|
27706
28794
|
}
|
|
27707
28795
|
function questionFor(facts, description) {
|
|
27708
|
-
const
|
|
27709
|
-
const staked = facts.filter((f) => f.atStake).map((f, i) => ({ f, i })).sort((a, b) => riskyTypeOrder(a.f.type) - riskyTypeOrder(b.f.type) || a.i - b.i).map(({ f }) => f).filter((f) => {
|
|
27710
|
-
const part = `${whatOf(f)} ${lostOf(f)}`;
|
|
27711
|
-
if (said.has(part))
|
|
27712
|
-
return false;
|
|
27713
|
-
said.add(part);
|
|
27714
|
-
return true;
|
|
27715
|
-
});
|
|
28796
|
+
const staked = orderedStaked(facts);
|
|
27716
28797
|
const reason = reasonSentence(description);
|
|
27717
28798
|
if (staked.length === 0)
|
|
27718
28799
|
return couldNotTellQuestion(description);
|
|
@@ -27721,15 +28802,45 @@ function questionFor(facts, description) {
|
|
|
27721
28802
|
return `Halfcycle: ${whatOf(only)} ${reason} If you allow it, ${lostOf(only)} ${NOTHING_LOST_ON_NO}`;
|
|
27722
28803
|
}
|
|
27723
28804
|
const shown2 = staked.slice(0, MAX_LISTED).map((f) => `${whatOf(f)} If you allow it, ${lostOf(f)}`);
|
|
27724
|
-
|
|
27725
|
-
|
|
27726
|
-
|
|
28805
|
+
return `Halfcycle: ${shown2.join(" ")}${moreLine(staked.length - shown2.length)} ${reason} ${NOTHING_LOST_ON_NO}`;
|
|
28806
|
+
}
|
|
28807
|
+
function orderedStaked(facts) {
|
|
28808
|
+
const said = /* @__PURE__ */ new Set();
|
|
28809
|
+
return facts.filter((f) => f.atStake).map((f, i) => ({ f, i })).sort((a, b) => riskyTypeOrder(a.f.type) - riskyTypeOrder(b.f.type) || a.i - b.i).map(({ f }) => f).filter((f) => {
|
|
28810
|
+
const part = `${whatOf(f)} ${lostOf(f)}`;
|
|
28811
|
+
if (said.has(part))
|
|
28812
|
+
return false;
|
|
28813
|
+
said.add(part);
|
|
28814
|
+
return true;
|
|
28815
|
+
});
|
|
28816
|
+
}
|
|
28817
|
+
function moreLine(more) {
|
|
28818
|
+
return more > 0 ? ` And ${more} more risky ${more === 1 ? "step" : "steps"}.` : "";
|
|
28819
|
+
}
|
|
28820
|
+
var UNNAMED_PUSH_WHAT = "Replace a line of work on the shared copy.";
|
|
28821
|
+
function whatsText(facts, extraPush = false) {
|
|
28822
|
+
const staked = orderedStaked(facts);
|
|
28823
|
+
const items = staked.map((f) => ({ order: riskyTypeOrder(f.type), what: whatOf(f) }));
|
|
28824
|
+
const pushOrder = riskyTypeOrder("rewrite-shared-history");
|
|
28825
|
+
if (extraPush && !staked.some((f) => f.type === "rewrite-shared-history")) {
|
|
28826
|
+
const at = items.findIndex((x) => x.order > pushOrder);
|
|
28827
|
+
items.splice(at < 0 ? items.length : at, 0, { order: pushOrder, what: UNNAMED_PUSH_WHAT });
|
|
28828
|
+
}
|
|
28829
|
+
const shown2 = items.slice(0, MAX_LISTED).map((x) => x.what);
|
|
28830
|
+
return `${shown2.join(" ")}${moreLine(items.length - shown2.length)}`;
|
|
28831
|
+
}
|
|
28832
|
+
function typeSentenceFor(facts, extraPush = false) {
|
|
28833
|
+
const whats = whatsText(facts, extraPush);
|
|
28834
|
+
return whats === "" ? AUTONOMY_COULD_NOT_TELL_SENTENCE : `Halfcycle: ${whats}`;
|
|
28835
|
+
}
|
|
28836
|
+
function savedWhatFor(facts) {
|
|
28837
|
+
return whatsText(facts);
|
|
27727
28838
|
}
|
|
27728
28839
|
function refusalText(question) {
|
|
27729
28840
|
return `${question} ${TURN_QUESTIONS_ON_LINE}`;
|
|
27730
28841
|
}
|
|
27731
28842
|
function couldNotTellQuestion(description) {
|
|
27732
|
-
return
|
|
28843
|
+
return `${AUTONOMY_COULD_NOT_TELL_SENTENCE} ${reasonSentence(description)} If you allow it, whatever it changes may not be possible to bring back. ${NOTHING_LOST_ON_NO}`;
|
|
27733
28844
|
}
|
|
27734
28845
|
function whatOf(f) {
|
|
27735
28846
|
switch (f.type) {
|
|
@@ -27811,7 +28922,7 @@ function pushWhat(f) {
|
|
|
27811
28922
|
const verb = lines2.length > 0 && lines2.every((l) => l.deletes) ? "Delete" : "Replace";
|
|
27812
28923
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
27813
28924
|
if (named.length === 0)
|
|
27814
|
-
return `${verb} a line of work on the shared copy.`;
|
|
28925
|
+
return verb === "Replace" ? UNNAMED_PUSH_WHAT : `${verb} a line of work on the shared copy.`;
|
|
27815
28926
|
return `${verb} the shared copy's ${named.length === 1 ? "line" : "lines"} of work ${listNames(named)}.`;
|
|
27816
28927
|
}
|
|
27817
28928
|
function pushLost(f) {
|
|
@@ -28013,16 +29124,71 @@ function answerModeFor(permissionMode) {
|
|
|
28013
29124
|
return typeof permissionMode === "string" && QUESTIONS_ON_MODES.has(permissionMode) ? "questions-on" : "questions-off";
|
|
28014
29125
|
}
|
|
28015
29126
|
var REFUSED_IN_EVERY_MODE = /* @__PURE__ */ new Set(["outside-own-copy"]);
|
|
28016
|
-
function
|
|
29127
|
+
function autonomousRowOf(type) {
|
|
29128
|
+
switch (type) {
|
|
29129
|
+
case "delete-files":
|
|
29130
|
+
case "discard-unsaved":
|
|
29131
|
+
case "delete-unmerged-work":
|
|
29132
|
+
case "shrink-record":
|
|
29133
|
+
return "save";
|
|
29134
|
+
case "rewrite-shared-history":
|
|
29135
|
+
case "wipe-data":
|
|
29136
|
+
return "wait-item-1";
|
|
29137
|
+
case "publish":
|
|
29138
|
+
case "deploy":
|
|
29139
|
+
return "publish";
|
|
29140
|
+
case "read-credentials":
|
|
29141
|
+
return "never";
|
|
29142
|
+
case "outside-own-copy":
|
|
29143
|
+
return "every-mode";
|
|
29144
|
+
default: {
|
|
29145
|
+
const unhandled = type;
|
|
29146
|
+
return unhandled;
|
|
29147
|
+
}
|
|
29148
|
+
}
|
|
29149
|
+
}
|
|
29150
|
+
function savedByAutonomy(type) {
|
|
29151
|
+
return autonomousRowOf(type) === "save";
|
|
29152
|
+
}
|
|
29153
|
+
function ownerWaitItemNumber(id) {
|
|
29154
|
+
return OWNER_WAIT_ITEM_IDS.indexOf(id) + 1;
|
|
29155
|
+
}
|
|
29156
|
+
function decide(action, facts, mode, mayPublishNow = () => false) {
|
|
28017
29157
|
void action;
|
|
28018
29158
|
if (facts.some((f) => REFUSED_IN_EVERY_MODE.has(f.type)))
|
|
28019
29159
|
return "refuse";
|
|
28020
|
-
|
|
29160
|
+
switch (mode) {
|
|
29161
|
+
case "questions-on":
|
|
29162
|
+
return "ask";
|
|
29163
|
+
case "questions-off":
|
|
29164
|
+
return "refuse";
|
|
29165
|
+
case "autonomous":
|
|
29166
|
+
return decideAutonomous(facts, mayPublishNow);
|
|
29167
|
+
default: {
|
|
29168
|
+
const unhandled = mode;
|
|
29169
|
+
void unhandled;
|
|
29170
|
+
return "refuse";
|
|
29171
|
+
}
|
|
29172
|
+
}
|
|
29173
|
+
}
|
|
29174
|
+
function decideAutonomous(facts, mayPublishNow) {
|
|
29175
|
+
if (facts.length === 0)
|
|
29176
|
+
return { autonomous: "could-not-tell" };
|
|
29177
|
+
const rows = [...facts].sort((a, b) => riskyTypeOrder(a.type) - riskyTypeOrder(b.type)).map((f) => autonomousRowOf(f.type));
|
|
29178
|
+
if (rows.includes("never"))
|
|
29179
|
+
return { autonomous: "never-under-autonomy" };
|
|
29180
|
+
const firstWait = rows.find((r) => r === "wait-item-1" || r === "publish");
|
|
29181
|
+
if (firstWait === "wait-item-1")
|
|
29182
|
+
return { autonomous: "waits-for-owner", item: ownerWaitItemNumber("delete-users-data") };
|
|
29183
|
+
if (firstWait === "publish" && !mayPublishNow()) {
|
|
29184
|
+
return { autonomous: "waits-for-owner", item: ownerWaitItemNumber("publish-or-release") };
|
|
29185
|
+
}
|
|
29186
|
+
return rows.includes("save") ? { autonomous: "save-then-let-through" } : { autonomous: "let-through" };
|
|
28021
29187
|
}
|
|
28022
29188
|
|
|
28023
29189
|
// dist/risky/own-copy.js
|
|
28024
|
-
import { readFileSync as
|
|
28025
|
-
import { join as
|
|
29190
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
29191
|
+
import { join as join13, posix as posix2, win32 as win322 } from "node:path";
|
|
28026
29192
|
var OWN_COPY_START_TOOL = "Agent";
|
|
28027
29193
|
var OWN_COPY_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
28028
29194
|
var OWN_COPY_ISOLATION = "worktree";
|
|
@@ -28043,7 +29209,7 @@ function stringField2(o, key) {
|
|
|
28043
29209
|
}
|
|
28044
29210
|
function readOr(deps, path) {
|
|
28045
29211
|
try {
|
|
28046
|
-
return (deps.readFile ?? ((p) =>
|
|
29212
|
+
return (deps.readFile ?? ((p) => readFileSync20(p, "utf-8")))(path);
|
|
28047
29213
|
} catch {
|
|
28048
29214
|
return null;
|
|
28049
29215
|
}
|
|
@@ -28083,7 +29249,7 @@ function typeCanChangeFiles(subagentType, deps) {
|
|
|
28083
29249
|
return true;
|
|
28084
29250
|
if (AGENT_FILE_NAME.test(type)) {
|
|
28085
29251
|
for (const base of [deps.projectDir, deps.home]) {
|
|
28086
|
-
const text = readOr(deps,
|
|
29252
|
+
const text = readOr(deps, join13(base, ".claude", "agents", `${type}.md`));
|
|
28087
29253
|
if (text === null)
|
|
28088
29254
|
continue;
|
|
28089
29255
|
const tools = frontmatterTools(text);
|
|
@@ -28096,105 +29262,608 @@ function targetOf(call) {
|
|
|
28096
29262
|
const t = call.toolInput;
|
|
28097
29263
|
return call.toolName === "NotebookEdit" ? stringField2(t, "notebook_path") ?? stringField2(t, "file_path") : stringField2(t, "file_path");
|
|
28098
29264
|
}
|
|
28099
|
-
function partOf(top, rel, p, deps) {
|
|
28100
|
-
const text = readOr(deps, p.join(top, ...SYSTEM_DESCRIPTION_REL.split("/")));
|
|
28101
|
-
if (text === null)
|
|
28102
|
-
return null;
|
|
29265
|
+
function partOf(top, rel, p, deps) {
|
|
29266
|
+
const text = readOr(deps, p.join(top, ...SYSTEM_DESCRIPTION_REL.split("/")));
|
|
29267
|
+
if (text === null)
|
|
29268
|
+
return null;
|
|
29269
|
+
try {
|
|
29270
|
+
const checked = checkSystemDescription(JSON.parse(text));
|
|
29271
|
+
if (!checked.ok)
|
|
29272
|
+
return null;
|
|
29273
|
+
const owner = ownerOf(rel, checked.description);
|
|
29274
|
+
return checked.description.parts.find((p2) => p2.id === owner)?.name ?? null;
|
|
29275
|
+
} catch {
|
|
29276
|
+
return null;
|
|
29277
|
+
}
|
|
29278
|
+
}
|
|
29279
|
+
function isMainCopy(c) {
|
|
29280
|
+
return c.top === c.main;
|
|
29281
|
+
}
|
|
29282
|
+
function judgeOwnCopy(input, call, deps) {
|
|
29283
|
+
try {
|
|
29284
|
+
if (call.toolName === OWN_COPY_START_TOOL)
|
|
29285
|
+
return judgeStart(input, call, deps);
|
|
29286
|
+
if (OWN_COPY_WRITE_TOOLS.has(call.toolName))
|
|
29287
|
+
return judgeChange(input, call, deps);
|
|
29288
|
+
return PASS;
|
|
29289
|
+
} catch {
|
|
29290
|
+
return PASS;
|
|
29291
|
+
}
|
|
29292
|
+
}
|
|
29293
|
+
function judgeStart(input, call, deps) {
|
|
29294
|
+
if (call.toolInput["isolation"] === OWN_COPY_ISOLATION)
|
|
29295
|
+
return PASS;
|
|
29296
|
+
if (!typeCanChangeFiles(call.toolInput["subagent_type"], deps))
|
|
29297
|
+
return PASS;
|
|
29298
|
+
const cwd = stringField2(input, "cwd");
|
|
29299
|
+
if (cwd === null || whichCopy(cwd, deps.walk) === null)
|
|
29300
|
+
return UNKNOWN;
|
|
29301
|
+
const record2 = readLiveness(stringField2(input, "session_id"), cwd, deps.liveness);
|
|
29302
|
+
if (!runsBesideOtherWork(call.toolInput, withoutAgent(record2, stringField2(input, "agent_id"))))
|
|
29303
|
+
return PASS;
|
|
29304
|
+
return { kind: "refused", facts: { type: "outside-own-copy", atStake: true, arm: "start" } };
|
|
29305
|
+
}
|
|
29306
|
+
function judgeChange(input, call, deps) {
|
|
29307
|
+
const cwd = stringField2(input, "cwd");
|
|
29308
|
+
const own = cwd === null ? null : whichCopy(cwd, deps.walk);
|
|
29309
|
+
if (cwd === null || own === null)
|
|
29310
|
+
return UNKNOWN;
|
|
29311
|
+
const given = targetOf(call);
|
|
29312
|
+
if (given === null)
|
|
29313
|
+
return PASS;
|
|
29314
|
+
const rules = deps.walk?.rules ?? machineRules();
|
|
29315
|
+
const p = rules === "windows" ? win322 : posix2;
|
|
29316
|
+
const place = copyHolding(given, deps.walk);
|
|
29317
|
+
if (place === null || place.copy.common !== own.common)
|
|
29318
|
+
return PASS;
|
|
29319
|
+
const target = place.copy;
|
|
29320
|
+
const agentId = stringField2(input, "agent_id");
|
|
29321
|
+
let arm = null;
|
|
29322
|
+
let record2;
|
|
29323
|
+
const live = () => record2 === void 0 ? record2 = readLiveness(stringField2(input, "session_id"), cwd, deps.liveness) : record2;
|
|
29324
|
+
if (agentId !== null) {
|
|
29325
|
+
if (target.top !== own.top)
|
|
29326
|
+
arm = "other-copy";
|
|
29327
|
+
else {
|
|
29328
|
+
const rec = live();
|
|
29329
|
+
const mainTop = rec?.main?.top ?? (isMainCopy(own) ? own.top : null);
|
|
29330
|
+
const others = withoutAgent(rec, agentId);
|
|
29331
|
+
if (mainTop === own.top && others !== null && others.agents.length > 0)
|
|
29332
|
+
arm = "main-copy-while-others";
|
|
29333
|
+
}
|
|
29334
|
+
} else if (target.top !== own.top && (live()?.agents ?? []).some((a) => a.top === target.top)) {
|
|
29335
|
+
arm = "agents-copy";
|
|
29336
|
+
}
|
|
29337
|
+
if (arm === null)
|
|
29338
|
+
return PASS;
|
|
29339
|
+
const inFolder = place.folder.length > target.top.length ? place.folder.slice(target.top.length + 1) : "";
|
|
29340
|
+
const rel = [inFolder, place.rest].filter((s) => s !== "").join(p.sep).split(p.sep).join("/");
|
|
29341
|
+
return {
|
|
29342
|
+
kind: "refused",
|
|
29343
|
+
facts: {
|
|
29344
|
+
type: "outside-own-copy",
|
|
29345
|
+
atStake: true,
|
|
29346
|
+
arm,
|
|
29347
|
+
part: partOf(target.top, rel, p, deps),
|
|
29348
|
+
file: rel,
|
|
29349
|
+
ownPath: p.join(own.top, ...rel.split("/")),
|
|
29350
|
+
ownIsMain: isMainCopy(own),
|
|
29351
|
+
targetIsMain: isMainCopy(target)
|
|
29352
|
+
}
|
|
29353
|
+
};
|
|
29354
|
+
}
|
|
29355
|
+
|
|
29356
|
+
// dist/risky/save.js
|
|
29357
|
+
import { randomBytes } from "node:crypto";
|
|
29358
|
+
import { chmodSync as chmodSync2, constants as fsConstants, copyFileSync, lstatSync as lstatSync3, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync21, readlinkSync, realpathSync as realpathSync7, renameSync as renameSync10, rmSync as rmSync2, statSync as statSync8, symlinkSync, writeFileSync as writeFileSync12 } from "node:fs";
|
|
29359
|
+
import { homedir as homedir8 } from "node:os";
|
|
29360
|
+
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join14, resolve as resolve5 } from "node:path";
|
|
29361
|
+
var SAVE_LIMIT_BYTES = 2e8;
|
|
29362
|
+
var SAVE_BUDGET_MS = 5e3;
|
|
29363
|
+
function notSaved(reason) {
|
|
29364
|
+
return { saved: false, reason };
|
|
29365
|
+
}
|
|
29366
|
+
function savedFolderOf(engagementId, home = homedir8()) {
|
|
29367
|
+
if (!isEngagementId(engagementId))
|
|
29368
|
+
return null;
|
|
29369
|
+
return join14(home, HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
29370
|
+
}
|
|
29371
|
+
function saveFirst(staked, ctx) {
|
|
29372
|
+
const now = ctx.now ?? Date.now;
|
|
29373
|
+
const deadline = now() + (ctx.budgetMs ?? SAVE_BUDGET_MS);
|
|
29374
|
+
const folder = savedFolderOf(ctx.engagementId, ctx.home);
|
|
29375
|
+
if (folder === null)
|
|
29376
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29377
|
+
const plan = planOf(staked);
|
|
29378
|
+
if (!plan.ok)
|
|
29379
|
+
return notSaved(plan.reason);
|
|
29380
|
+
const g = new SaveGit(plan.root, deadline, now);
|
|
29381
|
+
const top = g.run(["rev-parse", "--show-toplevel"]);
|
|
29382
|
+
if (!top.ok || realOrSelf2(top.value.trim()) !== plan.root)
|
|
29383
|
+
return notSaved(AUTONOMY_SAVE_NOT_COVERED_REASON);
|
|
29384
|
+
const head = g.run(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
29385
|
+
const headCommit2 = head.ok ? head.value.trim() : "";
|
|
29386
|
+
if (!/^[0-9a-f]{40,64}$/.test(headCommit2))
|
|
29387
|
+
return notSaved(AUTONOMY_SAVE_NO_HISTORY_REASON);
|
|
29388
|
+
try {
|
|
29389
|
+
pruneSaves(plan.root, ctx.engagementId, { ...ctx.home === void 0 ? {} : { home: ctx.home }, now, deadline });
|
|
29390
|
+
} catch {
|
|
29391
|
+
}
|
|
29392
|
+
const found2 = filesOf(plan, g, deadline);
|
|
29393
|
+
if (!found2.ok)
|
|
29394
|
+
return notSaved(found2.reason);
|
|
29395
|
+
let saveDir = null;
|
|
29396
|
+
try {
|
|
29397
|
+
const id = freshId(folder, ctx.newId);
|
|
29398
|
+
if (id === null)
|
|
29399
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29400
|
+
saveDir = join14(folder, id);
|
|
29401
|
+
const repository = repositoryOf(g);
|
|
29402
|
+
if (repository === null)
|
|
29403
|
+
return failed(saveDir);
|
|
29404
|
+
writeFileSync12(join14(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
|
|
29405
|
+
`, { mode: 384 });
|
|
29406
|
+
const privateIndex = join14(saveDir, PRIVATE_INDEX_NAME);
|
|
29407
|
+
const commit = saveCommit(id, headCommit2, found2.tracked, plan.lines, privateIndex, g, now());
|
|
29408
|
+
rmSync2(privateIndex, { force: true });
|
|
29409
|
+
if (commit === null)
|
|
29410
|
+
return failed(saveDir);
|
|
29411
|
+
const copiesDir = join14(saveDir, SAVED_COPIES_DIR_NAME);
|
|
29412
|
+
for (const rel of found2.copies) {
|
|
29413
|
+
if (now() > deadline || !copyOne(join14(plan.root, rel), join14(copiesDir, rel)))
|
|
29414
|
+
return failed(saveDir);
|
|
29415
|
+
}
|
|
29416
|
+
const description = savedSchema.safeParse({
|
|
29417
|
+
id,
|
|
29418
|
+
savedAt: new Date(now()).toISOString(),
|
|
29419
|
+
ref: savedRefOf(id),
|
|
29420
|
+
what: ctx.what.slice(0, 2e3),
|
|
29421
|
+
paths: [
|
|
29422
|
+
...found2.tracked.map((path) => ({ path, keptIn: "commit" })),
|
|
29423
|
+
...found2.copies.map((path) => ({ path, keptIn: "copy" }))
|
|
29424
|
+
],
|
|
29425
|
+
branches: plan.lines.filter((l) => l.name !== null).map((l) => ({ name: l.name, commit: l.commit }))
|
|
29426
|
+
});
|
|
29427
|
+
if (!description.success)
|
|
29428
|
+
return failed(saveDir);
|
|
29429
|
+
const file2 = join14(saveDir, SAVED_FILE_NAME);
|
|
29430
|
+
writeFileSync12(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
|
|
29431
|
+
`, { mode: 384 });
|
|
29432
|
+
renameSync10(`${file2}.part`, file2);
|
|
29433
|
+
ctx.beforeRef?.();
|
|
29434
|
+
const ref = g.run(["update-ref", "--no-deref", savedRefOf(id), commit, ""]);
|
|
29435
|
+
if (!ref.ok)
|
|
29436
|
+
return failed(saveDir);
|
|
29437
|
+
return { saved: true, id, description: description.data };
|
|
29438
|
+
} catch {
|
|
29439
|
+
return saveDir === null ? notSaved(AUTONOMY_SAVE_FAILED_REASON) : failed(saveDir);
|
|
29440
|
+
}
|
|
29441
|
+
}
|
|
29442
|
+
var PRIVATE_INDEX_NAME = "index.part";
|
|
29443
|
+
function failed(saveDir) {
|
|
29444
|
+
try {
|
|
29445
|
+
rmSync2(saveDir, { recursive: true, force: true });
|
|
29446
|
+
} catch {
|
|
29447
|
+
}
|
|
29448
|
+
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29449
|
+
}
|
|
29450
|
+
function planOf(staked) {
|
|
29451
|
+
if (staked.length === 0)
|
|
29452
|
+
return { ok: false, reason: AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29453
|
+
let root = null;
|
|
29454
|
+
const specs = [];
|
|
29455
|
+
const lines2 = /* @__PURE__ */ new Map();
|
|
29456
|
+
for (const f of staked) {
|
|
29457
|
+
const t = f.save;
|
|
29458
|
+
if (t === void 0)
|
|
29459
|
+
return { ok: false, reason: f.cannotTell === void 0 ? AUTONOMY_SAVE_NOT_COVERED_REASON : AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29460
|
+
const r = realOrSelf2(resolve5(t.repoRoot));
|
|
29461
|
+
if (root === null)
|
|
29462
|
+
root = r;
|
|
29463
|
+
else if (root !== r)
|
|
29464
|
+
return { ok: false, reason: AUTONOMY_SAVE_NOT_COVERED_REASON };
|
|
29465
|
+
for (const p of t.paths) {
|
|
29466
|
+
if (!isInsideRel(p))
|
|
29467
|
+
return { ok: false, reason: AUTONOMY_SAVE_NOT_COVERED_REASON };
|
|
29468
|
+
specs.push(t.keep === void 0 ? { path: p } : { path: p, keep: t.keep });
|
|
29469
|
+
}
|
|
29470
|
+
for (const l of t.lines)
|
|
29471
|
+
if (!lines2.has(l.commit) || l.name !== null)
|
|
29472
|
+
lines2.set(l.commit, l);
|
|
29473
|
+
}
|
|
29474
|
+
return { ok: true, root, specs, lines: [...lines2.values()] };
|
|
29475
|
+
}
|
|
29476
|
+
function isInsideRel(p) {
|
|
29477
|
+
if (p === ".")
|
|
29478
|
+
return true;
|
|
29479
|
+
if (p === "" || isAbsolute3(p) || p.includes("\\"))
|
|
29480
|
+
return false;
|
|
29481
|
+
return p.split("/").every((s) => s !== "" && s !== "." && s !== "..");
|
|
29482
|
+
}
|
|
29483
|
+
function under(rel, spec) {
|
|
29484
|
+
return spec === "." || rel === spec || rel.startsWith(`${spec}/`);
|
|
29485
|
+
}
|
|
29486
|
+
function reached(rel, specs) {
|
|
29487
|
+
return specs.some((s) => under(rel, s.path) && (s.keep === void 0 || s.keep(rel)));
|
|
29488
|
+
}
|
|
29489
|
+
function filesOf(plan, g, deadline) {
|
|
29490
|
+
if (plan.specs.length === 0)
|
|
29491
|
+
return { ok: true, tracked: [], copies: [] };
|
|
29492
|
+
const specs = [...new Set(plan.specs.map((s) => s.path))];
|
|
29493
|
+
const listed = g.run(["--literal-pathspecs", "ls-files", "-z", "--full-name", "--stage", "--", ...specs]);
|
|
29494
|
+
if (!listed.ok)
|
|
29495
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29496
|
+
const trackedAll = /* @__PURE__ */ new Set();
|
|
29497
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
29498
|
+
for (const rec of listed.value.split("\0")) {
|
|
29499
|
+
const tab = rec.indexOf(" ");
|
|
29500
|
+
if (tab < 0)
|
|
29501
|
+
continue;
|
|
29502
|
+
const mode = rec.slice(0, rec.indexOf(" "));
|
|
29503
|
+
const path = rec.slice(tab + 1);
|
|
29504
|
+
if (mode === "160000")
|
|
29505
|
+
continue;
|
|
29506
|
+
trackedAll.add(path);
|
|
29507
|
+
if (!reached(path, plan.specs))
|
|
29508
|
+
continue;
|
|
29509
|
+
const st = lstatOrNull(join14(plan.root, path));
|
|
29510
|
+
if (st !== null && (st.isFile() || st.isSymbolicLink()))
|
|
29511
|
+
tracked.add(path);
|
|
29512
|
+
}
|
|
29513
|
+
const copies = /* @__PURE__ */ new Set();
|
|
29514
|
+
let bytes = 0;
|
|
29515
|
+
let seen = 0;
|
|
29516
|
+
const stack = [...specs];
|
|
29517
|
+
while (stack.length > 0) {
|
|
29518
|
+
const rel = stack.pop();
|
|
29519
|
+
if (rel === ".git")
|
|
29520
|
+
continue;
|
|
29521
|
+
const abs = rel === "." ? plan.root : join14(plan.root, rel);
|
|
29522
|
+
const st = lstatOrNull(abs);
|
|
29523
|
+
if (st === null)
|
|
29524
|
+
continue;
|
|
29525
|
+
if (st.isDirectory()) {
|
|
29526
|
+
let names;
|
|
29527
|
+
try {
|
|
29528
|
+
names = readdirSync9(abs);
|
|
29529
|
+
} catch {
|
|
29530
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29531
|
+
}
|
|
29532
|
+
for (const n of names) {
|
|
29533
|
+
const child = rel === "." ? n : `${rel}/${n}`;
|
|
29534
|
+
if (child !== ".git")
|
|
29535
|
+
stack.push(child);
|
|
29536
|
+
}
|
|
29537
|
+
} else if ((st.isFile() || st.isSymbolicLink()) && !trackedAll.has(rel) && !copies.has(rel) && reached(rel, plan.specs)) {
|
|
29538
|
+
copies.add(rel);
|
|
29539
|
+
bytes += st.size;
|
|
29540
|
+
if (bytes > SAVE_LIMIT_BYTES)
|
|
29541
|
+
return { ok: false, reason: AUTONOMY_SAVE_TOO_LARGE_REASON };
|
|
29542
|
+
}
|
|
29543
|
+
if (++seen % 1024 === 0 && Date.now() > deadline)
|
|
29544
|
+
return { ok: false, reason: AUTONOMY_SAVE_FAILED_REASON };
|
|
29545
|
+
}
|
|
29546
|
+
return { ok: true, tracked: [...tracked].sort(), copies: [...copies].sort() };
|
|
29547
|
+
}
|
|
29548
|
+
function copyOne(src, dst) {
|
|
29549
|
+
const st = lstatOrNull(src);
|
|
29550
|
+
if (st === null)
|
|
29551
|
+
return false;
|
|
29552
|
+
mkdirSync8(dirname7(dst), { recursive: true, mode: 448 });
|
|
29553
|
+
if (st.isSymbolicLink()) {
|
|
29554
|
+
symlinkSync(readlinkSync(src), dst);
|
|
29555
|
+
return true;
|
|
29556
|
+
}
|
|
29557
|
+
copyFileSync(src, dst, fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE);
|
|
29558
|
+
chmodSync2(dst, st.mode & 4095);
|
|
29559
|
+
return statSync8(dst).size === st.size;
|
|
29560
|
+
}
|
|
29561
|
+
function saveCommit(id, head, tracked, lines2, privateIndex, g, at) {
|
|
29562
|
+
const indexPath = g.run(["rev-parse", "--git-path", "index"]);
|
|
29563
|
+
if (!indexPath.ok)
|
|
29564
|
+
return null;
|
|
29565
|
+
const projectIndex = resolve5(g.root, indexPath.value.trim());
|
|
29566
|
+
const env = commitEnv(privateIndex, at);
|
|
29567
|
+
if (lstatOrNull(projectIndex) !== null)
|
|
29568
|
+
copyFileSync(projectIndex, privateIndex);
|
|
29569
|
+
else if (!g.run(["read-tree", head], env).ok)
|
|
29570
|
+
return null;
|
|
29571
|
+
const stagedTree = g.run(["write-tree"], env);
|
|
29572
|
+
if (!stagedTree.ok)
|
|
29573
|
+
return null;
|
|
29574
|
+
const staged = g.run(["commit-tree", "--no-gpg-sign", stagedTree.value.trim(), "-p", head, "-m", `Halfcycle save ${id}: staged changes`], env);
|
|
29575
|
+
if (!staged.ok)
|
|
29576
|
+
return null;
|
|
29577
|
+
if (tracked.length > 0 && !g.run(["update-index", "-z", "--stdin"], env, `${tracked.join("\0")}\0`).ok)
|
|
29578
|
+
return null;
|
|
29579
|
+
const tree = g.run(["write-tree"], env);
|
|
29580
|
+
if (!tree.ok)
|
|
29581
|
+
return null;
|
|
29582
|
+
const parents = [.../* @__PURE__ */ new Set([head, staged.value.trim(), ...lines2.map((l) => l.commit)])];
|
|
29583
|
+
const commit = g.run(["commit-tree", "--no-gpg-sign", tree.value.trim(), ...parents.flatMap((p) => ["-p", p]), "-m", `Halfcycle save ${id}`], env);
|
|
29584
|
+
if (!commit.ok)
|
|
29585
|
+
return null;
|
|
29586
|
+
const sha = commit.value.trim();
|
|
29587
|
+
return /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
|
|
29588
|
+
}
|
|
29589
|
+
function commitEnv(privateIndex, at) {
|
|
29590
|
+
const date5 = `${String(Math.floor(at / 1e3))} +0000`;
|
|
29591
|
+
return {
|
|
29592
|
+
GIT_INDEX_FILE: privateIndex,
|
|
29593
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
29594
|
+
GIT_AUTHOR_NAME: "Halfcycle",
|
|
29595
|
+
GIT_AUTHOR_EMAIL: "save@halfcycle.invalid",
|
|
29596
|
+
GIT_AUTHOR_DATE: date5,
|
|
29597
|
+
GIT_COMMITTER_NAME: "Halfcycle",
|
|
29598
|
+
GIT_COMMITTER_EMAIL: "save@halfcycle.invalid",
|
|
29599
|
+
GIT_COMMITTER_DATE: date5
|
|
29600
|
+
};
|
|
29601
|
+
}
|
|
29602
|
+
var LONGEST_CAP_MS = 2147483647;
|
|
29603
|
+
function wholeMsLeft(ms) {
|
|
29604
|
+
if (!(ms >= 1))
|
|
29605
|
+
return null;
|
|
29606
|
+
return Math.min(Math.floor(ms), LONGEST_CAP_MS);
|
|
29607
|
+
}
|
|
29608
|
+
var SaveGit = class {
|
|
29609
|
+
root;
|
|
29610
|
+
deadline;
|
|
29611
|
+
now;
|
|
29612
|
+
constructor(root, deadline, now) {
|
|
29613
|
+
this.root = root;
|
|
29614
|
+
this.deadline = deadline;
|
|
29615
|
+
this.now = now;
|
|
29616
|
+
}
|
|
29617
|
+
run(args2, env, input) {
|
|
29618
|
+
const left = wholeMsLeft(this.deadline - this.now());
|
|
29619
|
+
if (left === null)
|
|
29620
|
+
return { ok: false, message: "out of time" };
|
|
29621
|
+
return gitForSave(args2, this.root, left, {
|
|
29622
|
+
env: env ?? { GIT_OPTIONAL_LOCKS: "0" },
|
|
29623
|
+
...input === void 0 ? {} : { input }
|
|
29624
|
+
});
|
|
29625
|
+
}
|
|
29626
|
+
};
|
|
29627
|
+
function freshId(folder, newId) {
|
|
29628
|
+
mkdirSync8(folder, { recursive: true, mode: 448 });
|
|
29629
|
+
for (let i = 0; i < 5; i++) {
|
|
29630
|
+
const id = newId?.() ?? defaultId();
|
|
29631
|
+
if (!savedIdSchema.safeParse(id).success)
|
|
29632
|
+
return null;
|
|
29633
|
+
try {
|
|
29634
|
+
mkdirSync8(join14(folder, id), { mode: 448 });
|
|
29635
|
+
return id;
|
|
29636
|
+
} catch (e) {
|
|
29637
|
+
if (e.code !== "EEXIST")
|
|
29638
|
+
return null;
|
|
29639
|
+
}
|
|
29640
|
+
}
|
|
29641
|
+
return null;
|
|
29642
|
+
}
|
|
29643
|
+
function defaultId() {
|
|
29644
|
+
const d = /* @__PURE__ */ new Date();
|
|
29645
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
29646
|
+
const stamp = `${String(d.getUTCFullYear())}${two(d.getUTCMonth() + 1)}${two(d.getUTCDate())}-${two(d.getUTCHours())}${two(d.getUTCMinutes())}${two(d.getUTCSeconds())}`;
|
|
29647
|
+
const rand = [...randomBytes(4)].map((b) => "abcdefghijklmnopqrstuvwxyz0123456789"[b % 36]).join("");
|
|
29648
|
+
return `${stamp}-${rand}`;
|
|
29649
|
+
}
|
|
29650
|
+
function repositoryOf(g) {
|
|
29651
|
+
const r = g.run(["rev-parse", "--git-common-dir"]);
|
|
29652
|
+
if (!r.ok)
|
|
29653
|
+
return null;
|
|
29654
|
+
const dir = r.value.trim();
|
|
29655
|
+
return dir === "" ? null : realOrSelf2(resolve5(g.root, dir));
|
|
29656
|
+
}
|
|
29657
|
+
function markedRepository(dir) {
|
|
29658
|
+
try {
|
|
29659
|
+
const text = readFileSync21(join14(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
29660
|
+
return text === "" ? null : text;
|
|
29661
|
+
} catch {
|
|
29662
|
+
return null;
|
|
29663
|
+
}
|
|
29664
|
+
}
|
|
29665
|
+
function pruneSaves(root, engagementId, ctx = {}) {
|
|
29666
|
+
const folder = savedFolderOf(engagementId, ctx.home);
|
|
29667
|
+
if (folder === null)
|
|
29668
|
+
return;
|
|
29669
|
+
const now = ctx.now ?? Date.now;
|
|
29670
|
+
const g = new SaveGit(root, ctx.deadline ?? now() + SAVE_BUDGET_MS, now);
|
|
29671
|
+
const listed = g.run(["for-each-ref", "--format=%(refname)%00%(committerdate:unix)", SAVED_REF_PREFIX]);
|
|
29672
|
+
if (!listed.ok)
|
|
29673
|
+
return;
|
|
29674
|
+
const refs = /* @__PURE__ */ new Map();
|
|
29675
|
+
for (const line2 of listed.value.split("\n")) {
|
|
29676
|
+
const [name, when] = line2.split("\0");
|
|
29677
|
+
if (name === void 0 || when === void 0 || !name.startsWith(SAVED_REF_PREFIX))
|
|
29678
|
+
continue;
|
|
29679
|
+
refs.set(name.slice(SAVED_REF_PREFIX.length), Number(when) * 1e3);
|
|
29680
|
+
}
|
|
29681
|
+
const repository = repositoryOf(g);
|
|
29682
|
+
let names = [];
|
|
29683
|
+
try {
|
|
29684
|
+
names = readdirSync9(folder);
|
|
29685
|
+
} catch {
|
|
29686
|
+
names = [];
|
|
29687
|
+
}
|
|
29688
|
+
const here = /* @__PURE__ */ new Set();
|
|
29689
|
+
for (const id of names) {
|
|
29690
|
+
if (!savedIdSchema.safeParse(id).success)
|
|
29691
|
+
continue;
|
|
29692
|
+
here.add(id);
|
|
29693
|
+
const dir = join14(folder, id);
|
|
29694
|
+
const st = lstatOrNull(dir);
|
|
29695
|
+
const answer = savedPruneAnswer({
|
|
29696
|
+
refMadeAt: refs.get(id) ?? null,
|
|
29697
|
+
folderChangedAt: st !== null && st.isDirectory() ? st.mtimeMs : null,
|
|
29698
|
+
folderRepository: markedRepository(dir)
|
|
29699
|
+
}, { repository, now: now() });
|
|
29700
|
+
if (answer === "remove-folder")
|
|
29701
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
29702
|
+
else if (answer === "remove-ref-then-folder" && g.run(["update-ref", "-d", savedRefOf(id)]).ok) {
|
|
29703
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
29704
|
+
} else if (answer === "remove-ref")
|
|
29705
|
+
g.run(["update-ref", "-d", savedRefOf(id)]);
|
|
29706
|
+
}
|
|
29707
|
+
for (const [id, madeAt] of refs) {
|
|
29708
|
+
if (here.has(id) || !savedIdSchema.safeParse(id).success)
|
|
29709
|
+
continue;
|
|
29710
|
+
const answer = savedPruneAnswer({ refMadeAt: madeAt, folderChangedAt: null, folderRepository: null }, { repository, now: now() });
|
|
29711
|
+
if (answer === "remove-ref")
|
|
29712
|
+
g.run(["update-ref", "-d", savedRefOf(id)]);
|
|
29713
|
+
}
|
|
29714
|
+
}
|
|
29715
|
+
function lstatOrNull(p) {
|
|
29716
|
+
try {
|
|
29717
|
+
return lstatSync3(p);
|
|
29718
|
+
} catch {
|
|
29719
|
+
return null;
|
|
29720
|
+
}
|
|
29721
|
+
}
|
|
29722
|
+
function realOrSelf2(p) {
|
|
29723
|
+
try {
|
|
29724
|
+
return realpathSync7(p);
|
|
29725
|
+
} catch {
|
|
29726
|
+
return p;
|
|
29727
|
+
}
|
|
29728
|
+
}
|
|
29729
|
+
|
|
29730
|
+
// dist/autonomy-state.js
|
|
29731
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29732
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync22, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
29733
|
+
import { homedir as homedir9, platform as platform2 } from "node:os";
|
|
29734
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
29735
|
+
var AUTONOMY_STATE_FILENAME = "autonomy.json";
|
|
29736
|
+
var AUTONOMY_STATE_DEADLINE_MS = 1500;
|
|
29737
|
+
function autonomyStateFilePath(engagementId, home) {
|
|
29738
|
+
return join15(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, AUTONOMY_STATE_FILENAME);
|
|
29739
|
+
}
|
|
29740
|
+
function readCachedAutonomy(engagementId, nowMs, home) {
|
|
29741
|
+
if (!isEngagementId(engagementId))
|
|
29742
|
+
return null;
|
|
29743
|
+
try {
|
|
29744
|
+
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync22(autonomyStateFilePath(engagementId, home), "utf-8")));
|
|
29745
|
+
if (!parsed.success || !parsed.data.on)
|
|
29746
|
+
return null;
|
|
29747
|
+
const age = nowMs - Date.parse(parsed.data.readAt);
|
|
29748
|
+
return Number.isFinite(age) && age >= 0 && age < AUTONOMY_STATE_FILE_MAX_AGE_MS ? parsed.data : null;
|
|
29749
|
+
} catch {
|
|
29750
|
+
return null;
|
|
29751
|
+
}
|
|
29752
|
+
}
|
|
29753
|
+
function confirmMayPublishNow(opts) {
|
|
29754
|
+
if (!isEngagementId(opts.engagementId) || CREDENTIAL_PLACEHOLDERS.includes(opts.bearer))
|
|
29755
|
+
return false;
|
|
29756
|
+
if (opts.timeoutMs <= 0)
|
|
29757
|
+
return false;
|
|
29758
|
+
try {
|
|
29759
|
+
const url2 = `${opts.controlOrigin}/engagements/${encodeURIComponent(opts.engagementId)}/autonomy/state`;
|
|
29760
|
+
const r = spawnSync2(process.execPath, ["--input-type=module", "-e", LIVE_READ_PROGRAM], {
|
|
29761
|
+
input: JSON.stringify({ url: url2, bearer: opts.bearer, ms: Math.max(1, opts.timeoutMs - 250) }),
|
|
29762
|
+
encoding: "utf-8",
|
|
29763
|
+
timeout: opts.timeoutMs,
|
|
29764
|
+
windowsHide: true
|
|
29765
|
+
});
|
|
29766
|
+
if (r.status !== 0 || typeof r.stdout !== "string")
|
|
29767
|
+
return false;
|
|
29768
|
+
const parsed = autonomyStateSchema.safeParse(JSON.parse(r.stdout));
|
|
29769
|
+
return parsed.success && parsed.data.on && parsed.data.mayPublish;
|
|
29770
|
+
} catch {
|
|
29771
|
+
return false;
|
|
29772
|
+
}
|
|
29773
|
+
}
|
|
29774
|
+
var LIVE_READ_PROGRAM = [
|
|
29775
|
+
"let t='';process.stdin.setEncoding('utf8');",
|
|
29776
|
+
"process.stdin.on('data',(c)=>{t+=c;});",
|
|
29777
|
+
"process.stdin.on('end',async()=>{try{const o=JSON.parse(t);",
|
|
29778
|
+
"const r=await fetch(o.url,{method:'GET',headers:{Authorization:'Bearer '+o.bearer},redirect:'manual',signal:AbortSignal.timeout(o.ms)});",
|
|
29779
|
+
"if(r.status<200||r.status>299)process.exit(1);process.stdout.write(JSON.stringify(await r.json()));process.exit(0);",
|
|
29780
|
+
"}catch{process.exit(1);}});"
|
|
29781
|
+
].join("");
|
|
29782
|
+
function deleteAutonomyStateFile(engagementId, home) {
|
|
28103
29783
|
try {
|
|
28104
|
-
|
|
28105
|
-
if (!checked.ok)
|
|
28106
|
-
return null;
|
|
28107
|
-
const owner = ownerOf(rel, checked.description);
|
|
28108
|
-
return checked.description.parts.find((p2) => p2.id === owner)?.name ?? null;
|
|
29784
|
+
rmSync3(autonomyStateFilePath(engagementId, home), { force: true });
|
|
28109
29785
|
} catch {
|
|
28110
|
-
return null;
|
|
28111
29786
|
}
|
|
28112
29787
|
}
|
|
28113
|
-
function
|
|
28114
|
-
|
|
28115
|
-
}
|
|
28116
|
-
function judgeOwnCopy(input, call, deps) {
|
|
29788
|
+
function writeAutonomyStateFile(engagementId, file2, home) {
|
|
29789
|
+
const target = autonomyStateFilePath(engagementId, home);
|
|
29790
|
+
const temp = join15(dirname8(target), `.${AUTONOMY_STATE_FILENAME}.${process.pid}.tmp`);
|
|
28117
29791
|
try {
|
|
28118
|
-
|
|
28119
|
-
|
|
28120
|
-
|
|
28121
|
-
|
|
28122
|
-
|
|
29792
|
+
mkdirSync9(dirname8(target), { recursive: true, mode: 448 });
|
|
29793
|
+
writeFileSync13(temp, `${JSON.stringify(file2)}
|
|
29794
|
+
`, { mode: 384 });
|
|
29795
|
+
if (platform2() !== "win32")
|
|
29796
|
+
chmodSync3(temp, 384);
|
|
29797
|
+
renameSync11(temp, target);
|
|
29798
|
+
return true;
|
|
28123
29799
|
} catch {
|
|
28124
|
-
|
|
29800
|
+
try {
|
|
29801
|
+
rmSync3(temp, { force: true });
|
|
29802
|
+
} catch {
|
|
29803
|
+
}
|
|
29804
|
+
return false;
|
|
28125
29805
|
}
|
|
28126
29806
|
}
|
|
28127
|
-
function
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
29807
|
+
async function readAutonomyStateFromControl(opts) {
|
|
29808
|
+
const controller = new AbortController();
|
|
29809
|
+
const timer = setTimeout(() => controller.abort(), opts.deadlineMs);
|
|
29810
|
+
try {
|
|
29811
|
+
const url2 = `${opts.controlOrigin}/engagements/${encodeURIComponent(opts.engagementId)}/autonomy/state`;
|
|
29812
|
+
const res = await opts.fetchFn(url2, {
|
|
29813
|
+
method: "GET",
|
|
29814
|
+
headers: { Authorization: `Bearer ${opts.bearer}` },
|
|
29815
|
+
redirect: "manual",
|
|
29816
|
+
signal: controller.signal
|
|
29817
|
+
});
|
|
29818
|
+
if (res.status < 200 || res.status > 299)
|
|
29819
|
+
return void 0;
|
|
29820
|
+
const parsed = autonomyStateSchema.safeParse(await res.json());
|
|
29821
|
+
return parsed.success ? parsed.data : void 0;
|
|
29822
|
+
} catch {
|
|
29823
|
+
return void 0;
|
|
29824
|
+
} finally {
|
|
29825
|
+
clearTimeout(timer);
|
|
29826
|
+
}
|
|
28139
29827
|
}
|
|
28140
|
-
function
|
|
28141
|
-
const
|
|
28142
|
-
|
|
28143
|
-
|
|
28144
|
-
|
|
28145
|
-
const
|
|
28146
|
-
|
|
28147
|
-
|
|
28148
|
-
|
|
28149
|
-
|
|
28150
|
-
|
|
28151
|
-
|
|
28152
|
-
|
|
28153
|
-
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28157
|
-
const
|
|
28158
|
-
if (
|
|
28159
|
-
|
|
28160
|
-
arm = "other-copy";
|
|
28161
|
-
else {
|
|
28162
|
-
const rec = live();
|
|
28163
|
-
const mainTop = rec?.main?.top ?? (isMainCopy(own) ? own.top : null);
|
|
28164
|
-
const others = withoutAgent(rec, agentId);
|
|
28165
|
-
if (mainTop === own.top && others !== null && others.agents.length > 0)
|
|
28166
|
-
arm = "main-copy-while-others";
|
|
28167
|
-
}
|
|
28168
|
-
} else if (target.top !== own.top && (live()?.agents ?? []).some((a) => a.top === target.top)) {
|
|
28169
|
-
arm = "agents-copy";
|
|
29828
|
+
async function cacheAutonomySetting(opts) {
|
|
29829
|
+
const { engagementId, home } = opts;
|
|
29830
|
+
if (!isEngagementId(engagementId))
|
|
29831
|
+
return;
|
|
29832
|
+
const bearer = opts.bearer;
|
|
29833
|
+
const usable = bearer !== void 0 && !CREDENTIAL_PLACEHOLDERS.includes(bearer) && opts.deadlineMs > 0;
|
|
29834
|
+
const state = usable ? await readAutonomyStateFromControl({
|
|
29835
|
+
controlOrigin: opts.controlOrigin,
|
|
29836
|
+
engagementId,
|
|
29837
|
+
bearer,
|
|
29838
|
+
fetchFn: opts.fetchFn,
|
|
29839
|
+
deadlineMs: opts.deadlineMs
|
|
29840
|
+
}) : void 0;
|
|
29841
|
+
if (state === void 0) {
|
|
29842
|
+
deleteAutonomyStateFile(engagementId, home);
|
|
29843
|
+
return;
|
|
29844
|
+
}
|
|
29845
|
+
const file2 = autonomyStateFileSchema.safeParse({ ...state, readAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
29846
|
+
if (!file2.success || !writeAutonomyStateFile(engagementId, file2.data, home)) {
|
|
29847
|
+
deleteAutonomyStateFile(engagementId, home);
|
|
28170
29848
|
}
|
|
28171
|
-
if (arm === null)
|
|
28172
|
-
return PASS;
|
|
28173
|
-
const inFolder = place.folder.length > target.top.length ? place.folder.slice(target.top.length + 1) : "";
|
|
28174
|
-
const rel = [inFolder, place.rest].filter((s) => s !== "").join(p.sep).split(p.sep).join("/");
|
|
28175
|
-
return {
|
|
28176
|
-
kind: "refused",
|
|
28177
|
-
facts: {
|
|
28178
|
-
type: "outside-own-copy",
|
|
28179
|
-
atStake: true,
|
|
28180
|
-
arm,
|
|
28181
|
-
part: partOf(target.top, rel, p, deps),
|
|
28182
|
-
file: rel,
|
|
28183
|
-
ownPath: p.join(own.top, ...rel.split("/")),
|
|
28184
|
-
ownIsMain: isMainCopy(own),
|
|
28185
|
-
targetIsMain: isMainCopy(target)
|
|
28186
|
-
}
|
|
28187
|
-
};
|
|
28188
29849
|
}
|
|
28189
29850
|
|
|
28190
29851
|
// dist/hooks/pre-tool-use.js
|
|
28191
|
-
import {
|
|
29852
|
+
import { performance } from "node:perf_hooks";
|
|
29853
|
+
import { homedir as homedir10 } from "node:os";
|
|
28192
29854
|
var UNREADABLE_INPUT_NOTICE = "Halfcycle could not read what the coding agent was about to do, so that step went ahead without being checked for anything that cannot be undone. If this keeps happening, update Halfcycle.";
|
|
29855
|
+
var PRE_TOOL_USE_LIMIT_MS = (HALFCYCLE_HOOK_WIRING.find((e) => e.event === "PreToolUse")?.timeout ?? 10) * 1e3;
|
|
29856
|
+
var AUTONOMY_REPLY_MARGIN_MS = 1e3;
|
|
29857
|
+
var MAY_PUBLISH_READ_MS = 3e3;
|
|
28193
29858
|
var OWN_COPY_UNKNOWN_NOTICE = "Halfcycle could not tell which copy of the project this agent works in, so it let the step go ahead without checking that it stays in its own copy.";
|
|
28194
29859
|
var CHECKED = new Set(CHECKED_TOOLS);
|
|
29860
|
+
var RECORDED_ONLY = new Set(RECORDED_ONLY_TOOLS);
|
|
28195
29861
|
function runPreToolUse(deps) {
|
|
28196
29862
|
const input = parseObject(deps.input);
|
|
28197
|
-
|
|
29863
|
+
const at = clockOf(deps)();
|
|
29864
|
+
const stamped = { ...deps, now: () => at };
|
|
29865
|
+
recordTimeEvent("action", input ?? {}, stamped);
|
|
29866
|
+
recordOwnShell(input, at, { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir } });
|
|
28198
29867
|
const seen = recordLiveness(input ?? {}, deps.liveness);
|
|
28199
29868
|
const paceDeps = {
|
|
28200
29869
|
...deps.pace ?? {},
|
|
@@ -28204,6 +29873,10 @@ function runPreToolUse(deps) {
|
|
|
28204
29873
|
};
|
|
28205
29874
|
if (seen?.firstSeen === true && input !== null && typeof input["agent_id"] === "string")
|
|
28206
29875
|
retirePendingStart(input, paceDeps);
|
|
29876
|
+
const { mode, autonomy } = answerModeOf(input, deps, at);
|
|
29877
|
+
const toolName = input === null ? void 0 : stringOf(input, "tool_name");
|
|
29878
|
+
if (toolName !== void 0 && RECORDED_ONLY.has(toolName))
|
|
29879
|
+
return recordedOnlyReply(mode);
|
|
28207
29880
|
const call = input === null ? null : toolCallOf(input);
|
|
28208
29881
|
if (call === null || !CHECKED.has(call.toolName))
|
|
28209
29882
|
return unreadable(deps, input);
|
|
@@ -28226,7 +29899,6 @@ function runPreToolUse(deps) {
|
|
|
28226
29899
|
}
|
|
28227
29900
|
const own = ownCopyOf(input, call, deps);
|
|
28228
29901
|
try {
|
|
28229
|
-
const mode = answerModeFor(input["permission_mode"]);
|
|
28230
29902
|
if (own.kind === "refused") {
|
|
28231
29903
|
const staked = [...answer.silent ? [] : answer.staked, own.facts];
|
|
28232
29904
|
if (decide(call, staked, mode) === "refuse") {
|
|
@@ -28235,6 +29907,11 @@ function runPreToolUse(deps) {
|
|
|
28235
29907
|
`;
|
|
28236
29908
|
}
|
|
28237
29909
|
}
|
|
29910
|
+
if (mode === "autonomous") {
|
|
29911
|
+
const sendsSaves = call.toolName === "Bash" && typeof call.toolInput["command"] === "string" && pushesSavedRefs(call.toolInput["command"]);
|
|
29912
|
+
if (!answer.silent || sendsSaves)
|
|
29913
|
+
return autonomousReply(call, answer.silent ? [] : answer.staked, sendsSaves, mode, autonomy, deps);
|
|
29914
|
+
}
|
|
28238
29915
|
if (answer.silent) {
|
|
28239
29916
|
if (call.toolName === PACE_HELD_TOOL) {
|
|
28240
29917
|
const held = paceHoldText(input, call.toolInput, paceDeps);
|
|
@@ -28255,12 +29932,100 @@ function runPreToolUse(deps) {
|
|
|
28255
29932
|
return "";
|
|
28256
29933
|
}
|
|
28257
29934
|
}
|
|
29935
|
+
function clockOf(deps) {
|
|
29936
|
+
return deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
29937
|
+
}
|
|
29938
|
+
function answerModeOf(input, deps, at) {
|
|
29939
|
+
const engagementId = readConfig(deps.env ?? process.env).config?.guardEngagementId;
|
|
29940
|
+
const cached2 = engagementId === void 0 ? null : readCachedAutonomy(engagementId, at.getTime(), deps.home);
|
|
29941
|
+
if (engagementId !== void 0 && cached2 !== null)
|
|
29942
|
+
return { mode: "autonomous", autonomy: { engagementId, mayPublish: cached2.mayPublish } };
|
|
29943
|
+
return { mode: answerModeFor(input === null ? void 0 : input["permission_mode"]), autonomy: null };
|
|
29944
|
+
}
|
|
29945
|
+
function autonomousReply(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29946
|
+
try {
|
|
29947
|
+
return autonomousAnswer(call, staked, sendsSaves, mode, autonomy, deps);
|
|
29948
|
+
} catch {
|
|
29949
|
+
return `${preToolUseReplyText("refuse", autonomyA12(typeSentenceFor(staked, sendsSaves), AUTONOMY_SAVE_FAILED_REASON))}
|
|
29950
|
+
`;
|
|
29951
|
+
}
|
|
29952
|
+
}
|
|
29953
|
+
function autonomousAnswer(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29954
|
+
const startedAt = deps.autonomy?.startedAtMs ?? performance.timeOrigin;
|
|
29955
|
+
const left = () => Math.floor(startedAt + PRE_TOOL_USE_LIMIT_MS - AUTONOMY_REPLY_MARGIN_MS - Date.now());
|
|
29956
|
+
const mayPublishNow = () => {
|
|
29957
|
+
if (!autonomy.mayPublish)
|
|
29958
|
+
return false;
|
|
29959
|
+
if (deps.autonomy?.mayPublishNow !== void 0)
|
|
29960
|
+
return deps.autonomy.mayPublishNow();
|
|
29961
|
+
const env = deps.env ?? process.env;
|
|
29962
|
+
const config2 = readConfig(env).config;
|
|
29963
|
+
if (config2 === null)
|
|
29964
|
+
return false;
|
|
29965
|
+
return confirmMayPublishNow({
|
|
29966
|
+
controlOrigin: readControlOrigin(env).origin,
|
|
29967
|
+
engagementId: autonomy.engagementId,
|
|
29968
|
+
bearer: config2.guardServiceToken,
|
|
29969
|
+
timeoutMs: Math.min(MAY_PUBLISH_READ_MS, left())
|
|
29970
|
+
});
|
|
29971
|
+
};
|
|
29972
|
+
const decided = decide(call, sendsSaves ? [...staked, { type: "rewrite-shared-history" }] : staked, mode, mayPublishNow);
|
|
29973
|
+
const refuse = (text) => `${preToolUseReplyText("refuse", text)}
|
|
29974
|
+
`;
|
|
29975
|
+
if (decided === "ask" || decided === "refuse")
|
|
29976
|
+
return refuse(autonomyA12(typeSentenceFor([]), AUTONOMY_COULD_NOT_TELL_REASON));
|
|
29977
|
+
switch (decided.autonomous) {
|
|
29978
|
+
case "waits-for-owner":
|
|
29979
|
+
return refuse(autonomyA10(typeSentenceFor(staked, sendsSaves), decided.item));
|
|
29980
|
+
case "never-under-autonomy":
|
|
29981
|
+
return refuse(autonomyA11(typeSentenceFor(staked, sendsSaves)));
|
|
29982
|
+
case "could-not-tell":
|
|
29983
|
+
return refuse(autonomyA12(typeSentenceFor([]), AUTONOMY_COULD_NOT_TELL_REASON));
|
|
29984
|
+
case "let-through":
|
|
29985
|
+
return "";
|
|
29986
|
+
case "save-then-let-through": {
|
|
29987
|
+
const kept = staked.filter((f) => savedByAutonomy(f.type));
|
|
29988
|
+
const what = savedWhatFor(kept);
|
|
29989
|
+
const budgetMs = Math.min(SAVE_BUDGET_MS, left());
|
|
29990
|
+
const saved = budgetMs <= 0 ? { saved: false, reason: AUTONOMY_SAVE_FAILED_REASON } : saveFirst(kept, {
|
|
29991
|
+
engagementId: autonomy.engagementId,
|
|
29992
|
+
what,
|
|
29993
|
+
budgetMs,
|
|
29994
|
+
...deps.home === void 0 ? {} : { home: deps.home },
|
|
29995
|
+
...deps.autonomy?.newId === void 0 ? {} : { newId: deps.autonomy.newId },
|
|
29996
|
+
...deps.autonomy?.beforeRef === void 0 ? {} : { beforeRef: deps.autonomy.beforeRef }
|
|
29997
|
+
});
|
|
29998
|
+
if (!saved.saved)
|
|
29999
|
+
return refuse(autonomyA12(typeSentenceFor(saved.reason === AUTONOMY_COULD_NOT_TELL_REASON ? [] : staked), saved.reason));
|
|
30000
|
+
return `${preToolUseSavedText(autonomyA9(what, saved.id))}
|
|
30001
|
+
`;
|
|
30002
|
+
}
|
|
30003
|
+
default: {
|
|
30004
|
+
const unhandled = decided;
|
|
30005
|
+
return unhandled;
|
|
30006
|
+
}
|
|
30007
|
+
}
|
|
30008
|
+
}
|
|
30009
|
+
function recordedOnlyReply(mode) {
|
|
30010
|
+
switch (mode) {
|
|
30011
|
+
case "questions-on":
|
|
30012
|
+
case "questions-off":
|
|
30013
|
+
return "";
|
|
30014
|
+
case "autonomous":
|
|
30015
|
+
return `${preToolUseReplyText("refuse", AUTONOMY_A18)}
|
|
30016
|
+
`;
|
|
30017
|
+
default: {
|
|
30018
|
+
const unhandled = mode;
|
|
30019
|
+
return unhandled;
|
|
30020
|
+
}
|
|
30021
|
+
}
|
|
30022
|
+
}
|
|
28258
30023
|
function ownCopyOf(input, call, deps) {
|
|
28259
30024
|
try {
|
|
28260
30025
|
const walk2 = deps.walk ?? deps.liveness?.walk;
|
|
28261
30026
|
return judgeOwnCopy(input, call, {
|
|
28262
30027
|
projectDir: deps.projectDir ?? projectFolder(deps.env ?? process.env),
|
|
28263
|
-
home: deps.home ??
|
|
30028
|
+
home: deps.home ?? homedir10(),
|
|
28264
30029
|
...walk2 === void 0 ? {} : { walk: walk2 },
|
|
28265
30030
|
...deps.liveness === void 0 ? {} : { liveness: deps.liveness }
|
|
28266
30031
|
});
|
|
@@ -28300,6 +30065,8 @@ function unreadable(deps, input) {
|
|
|
28300
30065
|
}
|
|
28301
30066
|
|
|
28302
30067
|
// dist/hooks/session-diff.js
|
|
30068
|
+
import { unlinkSync as unlinkSync9 } from "node:fs";
|
|
30069
|
+
import { join as join16 } from "node:path";
|
|
28303
30070
|
var STOP_HOOK_EVENT = {
|
|
28304
30071
|
stop: "Stop",
|
|
28305
30072
|
"subagent-stop": "SubagentStop"
|
|
@@ -28351,6 +30118,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28351
30118
|
}
|
|
28352
30119
|
const repoRoot = repoRootResult.value;
|
|
28353
30120
|
const sessionId = input.session_id;
|
|
30121
|
+
const folder = realPathOf(repoRoot);
|
|
28354
30122
|
orphanSweep(repoRoot, (ref) => refExists(ref, repoRoot), sessionId, Date.now(), whichCopy(process.cwd())?.common ?? null);
|
|
28355
30123
|
const base = resolveDiffBase2(repoRoot, sessionId);
|
|
28356
30124
|
const diffBase = base.kind;
|
|
@@ -28359,9 +30127,16 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28359
30127
|
reply.notice(localFaultText(`Could not get session diff: ${collected.message}`));
|
|
28360
30128
|
return;
|
|
28361
30129
|
}
|
|
28362
|
-
const
|
|
28363
|
-
const
|
|
30130
|
+
const tracked = new Set(collected.files);
|
|
30131
|
+
const candidates = ownEditedFiles(repoRoot, sessionId).filter((relPath) => !tracked.has(relPath));
|
|
30132
|
+
const untracked = getUntrackedFiles(repoRoot, candidates);
|
|
30133
|
+
const created = untracked.ok ? untracked.value.filter((relPath) => !tracked.has(relPath)) : [];
|
|
30134
|
+
const getDiff = (relPath) => tracked.has(relPath) ? collected.getDiff(relPath) : getSingleFileDiff(join16(repoRoot, relPath), repoRoot);
|
|
30135
|
+
const { evaluated } = partitionInstallerOutput(collected.files);
|
|
30136
|
+
const { evaluated: createdEvaluated } = partitionInstallerOutput(created);
|
|
30137
|
+
const changedFiles2 = [...ownChangedFiles(repoRoot, evaluated, sessionId), ...createdEvaluated];
|
|
28364
30138
|
if (changedFiles2.length === 0) {
|
|
30139
|
+
queueTurnEnd(keepBooks(sessionId, folder, { kind: "no-own-changes" }, kind === "stop"), reply);
|
|
28365
30140
|
return;
|
|
28366
30141
|
}
|
|
28367
30142
|
const fileDiffEntries = changedFiles2.map((relPath) => ({
|
|
@@ -28372,6 +30147,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28372
30147
|
const digest = digestChangeState(baseKey, fileDiffEntries);
|
|
28373
30148
|
const prior = readDedupe(repoRoot, sessionId);
|
|
28374
30149
|
if (prior && prior.base === baseKey && prior.digest === digest) {
|
|
30150
|
+
queueTurnEnd(keepBooks(sessionId, folder, { kind: "unchanged" }, kind === "stop"), reply);
|
|
28375
30151
|
return;
|
|
28376
30152
|
}
|
|
28377
30153
|
const context = loadChangeSetContext(repoRoot);
|
|
@@ -28450,30 +30226,56 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
28450
30226
|
warn: (msg) => process.stderr.write(msg + "\n")
|
|
28451
30227
|
});
|
|
28452
30228
|
writeDedupe(repoRoot, sessionId, { base: baseKey, digest });
|
|
28453
|
-
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply);
|
|
30229
|
+
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply, sessionId, folder);
|
|
30230
|
+
}
|
|
30231
|
+
function keepBooks(sessionId, folder, outcome, topLevel) {
|
|
30232
|
+
const books = () => {
|
|
30233
|
+
const before = readFindings(sessionId);
|
|
30234
|
+
const { file: file2, news } = turnEndBookkeeping(before ?? emptyFindings(""), folder, outcome, topLevel, /* @__PURE__ */ new Date());
|
|
30235
|
+
if (before !== null && JSON.stringify(before) !== JSON.stringify(file2)) {
|
|
30236
|
+
if (file2.findings.length > 0)
|
|
30237
|
+
writeFindings(file2);
|
|
30238
|
+
else
|
|
30239
|
+
removeFindingsFile(sessionId);
|
|
30240
|
+
}
|
|
30241
|
+
return turnEndMessage(news);
|
|
30242
|
+
};
|
|
30243
|
+
const locked = withFindingsLock(sessionId, books);
|
|
30244
|
+
return locked === void 0 ? books() : locked;
|
|
28454
30245
|
}
|
|
28455
|
-
function
|
|
28456
|
-
const
|
|
28457
|
-
|
|
28458
|
-
${notRun}` : "";
|
|
28459
|
-
if (envelope.guardsFired.length === 0) {
|
|
28460
|
-
if (notRun)
|
|
28461
|
-
reply.notice(notRun);
|
|
30246
|
+
function removeFindingsFile(sessionId) {
|
|
30247
|
+
const path = findingsPath(sessionId);
|
|
30248
|
+
if (path === null)
|
|
28462
30249
|
return;
|
|
30250
|
+
try {
|
|
30251
|
+
unlinkSync9(path);
|
|
30252
|
+
} catch {
|
|
28463
30253
|
}
|
|
28464
|
-
|
|
28465
|
-
|
|
30254
|
+
}
|
|
30255
|
+
function queueTurnEnd(words, reply) {
|
|
30256
|
+
if (words !== null)
|
|
30257
|
+
reply.notice(words);
|
|
30258
|
+
}
|
|
30259
|
+
function actOnEnvelope3(envelope, kind, neverBlock, reply, sessionId, folder) {
|
|
30260
|
+
const notRun = notRunMessage((envelope.notRun ?? []).map((n) => n.reason));
|
|
30261
|
+
const outcome = {
|
|
30262
|
+
kind: "evaluated",
|
|
30263
|
+
fired: envelope.guardsFired,
|
|
30264
|
+
notRun: (envelope.notRun ?? []).map((n) => n.patternRef)
|
|
30265
|
+
};
|
|
30266
|
+
const topLevel = kind === "stop";
|
|
28466
30267
|
if (envelope.blocking && !neverBlock) {
|
|
28467
|
-
|
|
28468
|
-
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
|
|
28474
|
-
reply.notice(`[Halfcycle] Guard warning at ${where}: ${envelope.explanation}
|
|
28475
|
-
${guardLines}${notRunBlock}`);
|
|
30268
|
+
keepBooks(sessionId, folder, outcome, false);
|
|
30269
|
+
const reason = blockReason(envelope.guardsFired.map((g) => g.explanation));
|
|
30270
|
+
reply.block(notRun ? `${reason}
|
|
30271
|
+
${notRun}` : reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
30272
|
+
}
|
|
30273
|
+
if (envelope.blocking && neverBlock) {
|
|
30274
|
+
reply.notice(couldNotFixMessage(blockingExplanations(envelope.guardsFired)));
|
|
28476
30275
|
}
|
|
30276
|
+
queueTurnEnd(keepBooks(sessionId, folder, outcome, topLevel), reply);
|
|
30277
|
+
if (notRun)
|
|
30278
|
+
reply.notice(notRun);
|
|
28477
30279
|
}
|
|
28478
30280
|
|
|
28479
30281
|
// dist/hooks/stdin.js
|
|
@@ -28492,9 +30294,9 @@ async function readHookStdin() {
|
|
|
28492
30294
|
}
|
|
28493
30295
|
}
|
|
28494
30296
|
function readStdin2() {
|
|
28495
|
-
return new Promise((
|
|
30297
|
+
return new Promise((resolve8) => {
|
|
28496
30298
|
if (process.stdin.isTTY) {
|
|
28497
|
-
|
|
30299
|
+
resolve8("");
|
|
28498
30300
|
return;
|
|
28499
30301
|
}
|
|
28500
30302
|
const chunks = [];
|
|
@@ -28504,7 +30306,7 @@ function readStdin2() {
|
|
|
28504
30306
|
return;
|
|
28505
30307
|
settled = true;
|
|
28506
30308
|
clearTimeout(timer);
|
|
28507
|
-
|
|
30309
|
+
resolve8(Buffer.concat(chunks).toString("utf-8"));
|
|
28508
30310
|
};
|
|
28509
30311
|
const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
|
|
28510
30312
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -28584,6 +30386,22 @@ function matchScopePhraseShape(shape, shown2) {
|
|
|
28584
30386
|
return { kind: "show-first", act: "send-back" };
|
|
28585
30387
|
}
|
|
28586
30388
|
|
|
30389
|
+
// dist/hooks/autonomy-phrase.js
|
|
30390
|
+
var AUTONOMY_ON = /^autonomy on$/i;
|
|
30391
|
+
var AUTONOMY_OFF = /^autonomy off$/i;
|
|
30392
|
+
function autonomyPhraseShape(prompt) {
|
|
30393
|
+
if (typeof prompt !== "string")
|
|
30394
|
+
return null;
|
|
30395
|
+
const line2 = prompt.trim();
|
|
30396
|
+
if (LINE_BREAK.test(line2))
|
|
30397
|
+
return null;
|
|
30398
|
+
if (AUTONOMY_ON.test(line2))
|
|
30399
|
+
return "on";
|
|
30400
|
+
if (AUTONOMY_OFF.test(line2))
|
|
30401
|
+
return "off";
|
|
30402
|
+
return null;
|
|
30403
|
+
}
|
|
30404
|
+
|
|
28587
30405
|
// dist/hooks/review-phrase.js
|
|
28588
30406
|
var VERBS = [
|
|
28589
30407
|
[/^accept /i, "accept"],
|
|
@@ -28650,20 +30468,20 @@ function matchReviewPhraseShape(shape, reviews) {
|
|
|
28650
30468
|
}
|
|
28651
30469
|
|
|
28652
30470
|
// dist/hooks/shown-record.js
|
|
28653
|
-
import { chmodSync as
|
|
28654
|
-
import { homedir as
|
|
28655
|
-
import { join as
|
|
30471
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync23, renameSync as renameSync12, unlinkSync as unlinkSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
30472
|
+
import { homedir as homedir11, platform as platform3 } from "node:os";
|
|
30473
|
+
import { join as join17 } from "node:path";
|
|
28656
30474
|
function recordDir(engagementId, home) {
|
|
28657
30475
|
if (!isEngagementId(engagementId))
|
|
28658
30476
|
return null;
|
|
28659
|
-
return
|
|
30477
|
+
return join17(home ?? homedir11(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
28660
30478
|
}
|
|
28661
30479
|
function readShownRecord(engagementId, home) {
|
|
28662
30480
|
const dir = recordDir(engagementId, home);
|
|
28663
30481
|
if (dir === null)
|
|
28664
30482
|
return { entries: [], reviews: [] };
|
|
28665
30483
|
try {
|
|
28666
|
-
return parseShownRecord(
|
|
30484
|
+
return parseShownRecord(readFileSync23(join17(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
28667
30485
|
} catch {
|
|
28668
30486
|
return { entries: [], reviews: [] };
|
|
28669
30487
|
}
|
|
@@ -28684,24 +30502,24 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
28684
30502
|
const dir = recordDir(engagementId, home);
|
|
28685
30503
|
if (dir === null)
|
|
28686
30504
|
return false;
|
|
28687
|
-
const path =
|
|
28688
|
-
const current = parseShownRecord(
|
|
30505
|
+
const path = join17(dir, SHOWN_PLANS_FILENAME);
|
|
30506
|
+
const current = parseShownRecord(readFileSync23(path, "utf-8"));
|
|
28689
30507
|
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
28690
30508
|
if (at === -1)
|
|
28691
30509
|
return false;
|
|
28692
30510
|
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
28693
30511
|
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
28694
30512
|
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
28695
|
-
temp =
|
|
28696
|
-
|
|
28697
|
-
if (
|
|
28698
|
-
|
|
28699
|
-
|
|
30513
|
+
temp = join17(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
30514
|
+
writeFileSync14(temp, body, { mode: 384 });
|
|
30515
|
+
if (platform3() !== "win32")
|
|
30516
|
+
chmodSync4(temp, 384);
|
|
30517
|
+
renameSync12(temp, path);
|
|
28700
30518
|
return true;
|
|
28701
30519
|
} catch {
|
|
28702
30520
|
if (temp !== null) {
|
|
28703
30521
|
try {
|
|
28704
|
-
|
|
30522
|
+
unlinkSync10(temp);
|
|
28705
30523
|
} catch {
|
|
28706
30524
|
return false;
|
|
28707
30525
|
}
|
|
@@ -28996,15 +30814,59 @@ function reviewLineForAnswer(context, answer) {
|
|
|
28996
30814
|
}
|
|
28997
30815
|
}
|
|
28998
30816
|
}
|
|
30817
|
+
var AUTONOMY_NOTHING_CHANGED = "Nothing changed";
|
|
30818
|
+
function autonomyNotAttendedLine() {
|
|
30819
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: turning this on or off by typing counts only when a person types it into an open session.`);
|
|
30820
|
+
}
|
|
30821
|
+
function autonomyNotSignedInLine() {
|
|
30822
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
|
|
30823
|
+
}
|
|
30824
|
+
function autonomyNotOwnerLine() {
|
|
30825
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: only the project's owner can turn this on or off.`);
|
|
30826
|
+
}
|
|
30827
|
+
function autonomyNoOwnerLine() {
|
|
30828
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: this project has no owner, so nobody can turn this on or off.`);
|
|
30829
|
+
}
|
|
30830
|
+
function autonomyNotOfferedLine() {
|
|
30831
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}. ${TYPED_APPROVAL_OFF_SENTENCE}`);
|
|
30832
|
+
}
|
|
30833
|
+
function autonomyCannotReachLine() {
|
|
30834
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: Halfcycle could not be reached just now. Type the line again in a minute.`);
|
|
30835
|
+
}
|
|
30836
|
+
function autonomyLineForAnswer(answer) {
|
|
30837
|
+
switch (answer.outcome) {
|
|
30838
|
+
case "recorded":
|
|
30839
|
+
return line(answer.on ? AUTONOMY_A4 : AUTONOMY_A5);
|
|
30840
|
+
case "not-owner":
|
|
30841
|
+
return autonomyNotOwnerLine();
|
|
30842
|
+
case "no-owner":
|
|
30843
|
+
return autonomyNoOwnerLine();
|
|
30844
|
+
case "not-offered":
|
|
30845
|
+
return autonomyNotOfferedLine();
|
|
30846
|
+
case "unavailable":
|
|
30847
|
+
return autonomyCannotReachLine();
|
|
30848
|
+
default: {
|
|
30849
|
+
const unreachable = answer;
|
|
30850
|
+
return unreachable;
|
|
30851
|
+
}
|
|
30852
|
+
}
|
|
30853
|
+
}
|
|
28999
30854
|
|
|
29000
30855
|
// dist/hooks/user-prompt-submit.js
|
|
29001
30856
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
29002
30857
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
29003
30858
|
async function runUserPromptSubmit(opts) {
|
|
29004
30859
|
let act = null;
|
|
30860
|
+
let autonomyAct = null;
|
|
29005
30861
|
const review = { recognised: false };
|
|
29006
30862
|
try {
|
|
29007
30863
|
const prompt = promptOf(opts.input);
|
|
30864
|
+
const autonomyShape = autonomyPhraseShape(prompt);
|
|
30865
|
+
if (autonomyShape !== null) {
|
|
30866
|
+
autonomyAct = autonomyShape;
|
|
30867
|
+
return `${await decideAutonomy(autonomyShape, opts)}
|
|
30868
|
+
`;
|
|
30869
|
+
}
|
|
29008
30870
|
const reviewShape = reviewPhraseShape(prompt);
|
|
29009
30871
|
if (reviewShape !== null) {
|
|
29010
30872
|
const out = await decideReview(reviewShape, opts, review);
|
|
@@ -29018,6 +30880,9 @@ async function runUserPromptSubmit(opts) {
|
|
|
29018
30880
|
return `${await decide2(shape, opts)}
|
|
29019
30881
|
`;
|
|
29020
30882
|
} catch {
|
|
30883
|
+
if (autonomyAct !== null)
|
|
30884
|
+
return `${autonomyCannotReachLine()}
|
|
30885
|
+
`;
|
|
29021
30886
|
if (review.recognised)
|
|
29022
30887
|
return `${reviewCannotReachLine()}
|
|
29023
30888
|
`;
|
|
@@ -29025,6 +30890,44 @@ async function runUserPromptSubmit(opts) {
|
|
|
29025
30890
|
`;
|
|
29026
30891
|
}
|
|
29027
30892
|
}
|
|
30893
|
+
async function decideAutonomy(act, opts) {
|
|
30894
|
+
const env = opts.env ?? process.env;
|
|
30895
|
+
if (!readSessionAttended(env))
|
|
30896
|
+
return autonomyNotAttendedLine();
|
|
30897
|
+
const read = readConfig(env);
|
|
30898
|
+
if (read.config === null) {
|
|
30899
|
+
return readNeverConfiguredReason(env) === "not-this-account" ? autonomyNotOwnerLine() : autonomyNotSignedInLine();
|
|
30900
|
+
}
|
|
30901
|
+
const { guardServiceToken, guardEngagementId } = read.config;
|
|
30902
|
+
return postAutonomy(act, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
30903
|
+
}
|
|
30904
|
+
async function postAutonomy(act, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
30905
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/autonomy/chat`;
|
|
30906
|
+
const controller = new AbortController();
|
|
30907
|
+
const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
|
|
30908
|
+
try {
|
|
30909
|
+
const response = await fetchFn(url2, {
|
|
30910
|
+
method: "POST",
|
|
30911
|
+
headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
|
|
30912
|
+
body: JSON.stringify(autonomyChatRequestSchema.parse({ act })),
|
|
30913
|
+
signal: controller.signal
|
|
30914
|
+
});
|
|
30915
|
+
if (response.status === 401)
|
|
30916
|
+
return autonomyNotSignedInLine();
|
|
30917
|
+
if (response.status === 429 || response.status >= 500)
|
|
30918
|
+
return autonomyCannotReachLine();
|
|
30919
|
+
const parsed = autonomyChatResultSchema.safeParse(await response.json());
|
|
30920
|
+
if (!parsed.success)
|
|
30921
|
+
return autonomyCannotReachLine();
|
|
30922
|
+
if (parsed.data.outcome === "recorded" && response.status !== 200)
|
|
30923
|
+
return autonomyCannotReachLine();
|
|
30924
|
+
return autonomyLineForAnswer(parsed.data);
|
|
30925
|
+
} catch {
|
|
30926
|
+
return autonomyCannotReachLine();
|
|
30927
|
+
} finally {
|
|
30928
|
+
clearTimeout(timer);
|
|
30929
|
+
}
|
|
30930
|
+
}
|
|
29028
30931
|
async function decideReview(shape, opts, progress) {
|
|
29029
30932
|
const env = opts.env ?? process.env;
|
|
29030
30933
|
if (shape.kind === "phase") {
|
|
@@ -29206,18 +31109,36 @@ async function post(act, entry, request, engagementId, credential, env, fetchFn
|
|
|
29206
31109
|
}
|
|
29207
31110
|
}
|
|
29208
31111
|
|
|
31112
|
+
// dist/hooks/user-prompt-findings.js
|
|
31113
|
+
function runUserPromptFindings(opts) {
|
|
31114
|
+
try {
|
|
31115
|
+
const parsed = JSON.parse(opts.input);
|
|
31116
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
31117
|
+
return "";
|
|
31118
|
+
const sessionId = parsed["session_id"];
|
|
31119
|
+
const given = deliverFindings(sessionId, null);
|
|
31120
|
+
if (given.length === 0)
|
|
31121
|
+
return "";
|
|
31122
|
+
const project = realPathOf(projectFolder(opts.env ?? process.env));
|
|
31123
|
+
return `${userPromptContextReply(detailsBlock(given, project))}
|
|
31124
|
+
`;
|
|
31125
|
+
} catch {
|
|
31126
|
+
return "";
|
|
31127
|
+
}
|
|
31128
|
+
}
|
|
31129
|
+
|
|
29209
31130
|
// dist/resolve-engagement.js
|
|
29210
|
-
import { existsSync as
|
|
29211
|
-
import { join as
|
|
29212
|
-
import { homedir as
|
|
31131
|
+
import { existsSync as existsSync8, readFileSync as readFileSync24, realpathSync as realpathSync8 } from "node:fs";
|
|
31132
|
+
import { join as join18, sep as sep4 } from "node:path";
|
|
31133
|
+
import { homedir as homedir12 } from "node:os";
|
|
29213
31134
|
function defaultRegistryPath() {
|
|
29214
|
-
return
|
|
31135
|
+
return join18(homedir12(), ".halfcycle", "registry.json");
|
|
29215
31136
|
}
|
|
29216
31137
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
29217
|
-
if (!
|
|
31138
|
+
if (!existsSync8(registryPath))
|
|
29218
31139
|
return {};
|
|
29219
31140
|
try {
|
|
29220
|
-
const parsed = JSON.parse(
|
|
31141
|
+
const parsed = JSON.parse(readFileSync24(registryPath, "utf-8"));
|
|
29221
31142
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
29222
31143
|
return {};
|
|
29223
31144
|
const out = {};
|
|
@@ -29232,7 +31153,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
29232
31153
|
}
|
|
29233
31154
|
function canonicalise(p) {
|
|
29234
31155
|
try {
|
|
29235
|
-
return
|
|
31156
|
+
return realpathSync8(p);
|
|
29236
31157
|
} catch {
|
|
29237
31158
|
return p;
|
|
29238
31159
|
}
|
|
@@ -29253,7 +31174,7 @@ function resolveEngagement(cwd, registry2) {
|
|
|
29253
31174
|
function isSelfOrAncestor(ancestor, target) {
|
|
29254
31175
|
if (ancestor === target)
|
|
29255
31176
|
return true;
|
|
29256
|
-
const withSep = ancestor.endsWith(
|
|
31177
|
+
const withSep = ancestor.endsWith(sep4) ? ancestor : ancestor + sep4;
|
|
29257
31178
|
return target.startsWith(withSep);
|
|
29258
31179
|
}
|
|
29259
31180
|
function runResolveEngagement(cwd, registryPath) {
|
|
@@ -29265,13 +31186,13 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
29265
31186
|
}
|
|
29266
31187
|
|
|
29267
31188
|
// dist/refresh-credential.js
|
|
29268
|
-
import { readFileSync as
|
|
29269
|
-
import { resolve as
|
|
31189
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
31190
|
+
import { resolve as resolve7 } from "node:path";
|
|
29270
31191
|
|
|
29271
31192
|
// dist/mcp-headers.js
|
|
29272
|
-
import { readFileSync as
|
|
29273
|
-
import { homedir as
|
|
29274
|
-
import { dirname as
|
|
31193
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
31194
|
+
import { homedir as homedir13 } from "node:os";
|
|
31195
|
+
import { dirname as dirname9, resolve as resolve6 } from "node:path";
|
|
29275
31196
|
function defaultWriteErr(text) {
|
|
29276
31197
|
process.stderr.write(text);
|
|
29277
31198
|
}
|
|
@@ -29284,12 +31205,12 @@ function headerLine(bearer, clientVersion) {
|
|
|
29284
31205
|
}
|
|
29285
31206
|
function projectRootFromBinary(binaryPath) {
|
|
29286
31207
|
const depth = VENDORED_BIN_REL.split("/").length - 1;
|
|
29287
|
-
return
|
|
31208
|
+
return resolve6(dirname9(binaryPath), ...Array(depth).fill(".."));
|
|
29288
31209
|
}
|
|
29289
31210
|
function readPinnedVersion(root) {
|
|
29290
31211
|
let raw;
|
|
29291
31212
|
try {
|
|
29292
|
-
raw =
|
|
31213
|
+
raw = readFileSync25(resolve6(root, BUNDLE_PIN_REL), "utf-8");
|
|
29293
31214
|
} catch {
|
|
29294
31215
|
return void 0;
|
|
29295
31216
|
}
|
|
@@ -29331,7 +31252,7 @@ function sentences(lines2) {
|
|
|
29331
31252
|
}
|
|
29332
31253
|
function badIdSentence(root) {
|
|
29333
31254
|
return sentences([
|
|
29334
|
-
`${
|
|
31255
|
+
`${resolve6(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
|
|
29335
31256
|
'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
|
|
29336
31257
|
"is then set up as a new Halfcycle project."
|
|
29337
31258
|
]);
|
|
@@ -29345,7 +31266,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
29345
31266
|
]);
|
|
29346
31267
|
case "no-id":
|
|
29347
31268
|
return sentences([
|
|
29348
|
-
`${
|
|
31269
|
+
`${resolve6(root, BUNDLE_PIN_REL)} names no engagement id.`,
|
|
29349
31270
|
'run "npx halfcycle" here to rewrite it.'
|
|
29350
31271
|
]);
|
|
29351
31272
|
case "bad-id":
|
|
@@ -29362,7 +31283,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
29362
31283
|
case "no-credential":
|
|
29363
31284
|
return sentences([
|
|
29364
31285
|
`this machine holds no credential for engagement ${engagementId ?? ""}.`,
|
|
29365
|
-
`run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${
|
|
31286
|
+
`run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${resolve6(home, HALFCYCLE_DIR_NAME)}, outside this repository.`
|
|
29366
31287
|
]);
|
|
29367
31288
|
}
|
|
29368
31289
|
}
|
|
@@ -29385,7 +31306,7 @@ async function refreshWithinDeadline(source, opts, writeErr) {
|
|
|
29385
31306
|
}
|
|
29386
31307
|
async function chooseBearer(opts) {
|
|
29387
31308
|
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
29388
|
-
const home = opts.home ??
|
|
31309
|
+
const home = opts.home ?? homedir13();
|
|
29389
31310
|
const root = projectRootFromBinary(opts.binaryPath);
|
|
29390
31311
|
const environmentToken = opts.environmentTokenFirst ? readEnvironmentToken() : void 0;
|
|
29391
31312
|
const store = resolveStore(root, home);
|
|
@@ -29512,10 +31433,11 @@ async function refreshCredentialToken(opts) {
|
|
|
29512
31433
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
29513
31434
|
var CONNECTED_SENTENCE = `Halfcycle's sign-in works and its service answered. If its tools are missing, turn on ${MCP_SERVER_KEY} in /mcp; if it is not listed, run "claude mcp reset-project-choices" here and restart Claude Code.`;
|
|
29514
31435
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
31436
|
+
var SESSION_START_PROBE_ATTEMPTS = 2;
|
|
29515
31437
|
function registeredMcpUrl(projectRoot) {
|
|
29516
31438
|
let parsed;
|
|
29517
31439
|
try {
|
|
29518
|
-
parsed = JSON.parse(
|
|
31440
|
+
parsed = JSON.parse(readFileSync26(resolve7(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
29519
31441
|
} catch {
|
|
29520
31442
|
return void 0;
|
|
29521
31443
|
}
|
|
@@ -29581,11 +31503,11 @@ async function postInitialize(url2, bearer, fetchFn, signal) {
|
|
|
29581
31503
|
}
|
|
29582
31504
|
return namesItsServer(body) ? void 0 : SERVICE_PROBLEM_SENTENCE;
|
|
29583
31505
|
}
|
|
29584
|
-
async function probeDelivery(url2, bearer, fetchFn, deadlineMs) {
|
|
31506
|
+
async function probeDelivery(url2, bearer, fetchFn, deadlineMs, maxAttempts) {
|
|
29585
31507
|
const controller = new AbortController();
|
|
29586
31508
|
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
29587
31509
|
try {
|
|
29588
|
-
for (let attempt2 = 0; attempt2 <
|
|
31510
|
+
for (let attempt2 = 0; attempt2 < maxAttempts; attempt2++) {
|
|
29589
31511
|
const outcome = await postInitialize(url2, bearer, fetchFn, controller.signal);
|
|
29590
31512
|
if (outcome !== CUT_OFF)
|
|
29591
31513
|
return outcome;
|
|
@@ -29611,7 +31533,7 @@ async function checkConnection(renewedToken, opts) {
|
|
|
29611
31533
|
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
29612
31534
|
return { sentence: PLACEHOLDER_SENTENCES[bearer], url: url2, bearer };
|
|
29613
31535
|
}
|
|
29614
|
-
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
|
|
31536
|
+
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS, opts.maxAttempts ?? SESSION_START_PROBE_ATTEMPTS);
|
|
29615
31537
|
return { sentence: outcome ?? CONNECTED_SENTENCE, url: url2, bearer };
|
|
29616
31538
|
}
|
|
29617
31539
|
function positionRequestBody(engagementId) {
|
|
@@ -29722,6 +31644,20 @@ async function runRefreshCredential(opts) {
|
|
|
29722
31644
|
}
|
|
29723
31645
|
}
|
|
29724
31646
|
}
|
|
31647
|
+
if (CHECKED_SOURCES.has(input["source"])) {
|
|
31648
|
+
const config2 = readConfig().config;
|
|
31649
|
+
if (config2 !== null) {
|
|
31650
|
+
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
31651
|
+
await cacheAutonomySetting({
|
|
31652
|
+
controlOrigin: readControlOrigin().origin,
|
|
31653
|
+
engagementId: config2.guardEngagementId,
|
|
31654
|
+
bearer: renewedToken ?? config2.guardServiceToken,
|
|
31655
|
+
fetchFn: opts.fetchFn ?? globalThis.fetch,
|
|
31656
|
+
home: opts.home,
|
|
31657
|
+
deadlineMs: Math.min(opts.autonomyStateDeadlineMs ?? AUTONOMY_STATE_DEADLINE_MS, remainingForMargin)
|
|
31658
|
+
});
|
|
31659
|
+
}
|
|
31660
|
+
}
|
|
29725
31661
|
if (readConfig().config === null) {
|
|
29726
31662
|
const remedySaid = parts.some(namesTheSignInCommand);
|
|
29727
31663
|
parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid, pinnedEngagementId(projectRootFromBinary(opts.binaryPath))));
|
|
@@ -29748,8 +31684,8 @@ async function runRefreshCredential(opts) {
|
|
|
29748
31684
|
import { fileURLToPath } from "node:url";
|
|
29749
31685
|
|
|
29750
31686
|
// dist/capture.js
|
|
29751
|
-
import { readFileSync as
|
|
29752
|
-
import { dirname as
|
|
31687
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, existsSync as existsSync9 } from "node:fs";
|
|
31688
|
+
import { dirname as dirname10 } from "node:path";
|
|
29753
31689
|
function runCapture(provider, endpoint, opts = {}) {
|
|
29754
31690
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
29755
31691
|
const raw = opts.input ?? readInputFile(opts.fromFile);
|
|
@@ -29784,8 +31720,8 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
29784
31720
|
const fixture = fixturePath(repoRoot, provider, slug);
|
|
29785
31721
|
const manifestFile = manifestPath(repoRoot);
|
|
29786
31722
|
try {
|
|
29787
|
-
|
|
29788
|
-
|
|
31723
|
+
mkdirSync10(dirname10(fixture), { recursive: true });
|
|
31724
|
+
writeFileSync15(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
29789
31725
|
} catch (err) {
|
|
29790
31726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29791
31727
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -29816,7 +31752,7 @@ function readInputFile(fromFile) {
|
|
|
29816
31752
|
if (!fromFile)
|
|
29817
31753
|
return void 0;
|
|
29818
31754
|
try {
|
|
29819
|
-
return
|
|
31755
|
+
return readFileSync27(fromFile, "utf-8");
|
|
29820
31756
|
} catch {
|
|
29821
31757
|
return void 0;
|
|
29822
31758
|
}
|
|
@@ -29827,11 +31763,11 @@ function relativeFixturePath(repoRoot, fixture) {
|
|
|
29827
31763
|
}
|
|
29828
31764
|
|
|
29829
31765
|
// dist/prove.js
|
|
29830
|
-
import { spawn as spawn2, spawnSync as
|
|
29831
|
-
import { createHash as
|
|
29832
|
-
import { createWriteStream, mkdirSync as
|
|
29833
|
-
import { homedir as
|
|
29834
|
-
import { dirname as
|
|
31766
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
31767
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
|
|
31768
|
+
import { createWriteStream, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync28, renameSync as renameSync13, unlinkSync as unlinkSync11, writeFileSync as writeFileSync16 } from "node:fs";
|
|
31769
|
+
import { homedir as homedir14 } from "node:os";
|
|
31770
|
+
import { dirname as dirname11, join as join19 } from "node:path";
|
|
29835
31771
|
|
|
29836
31772
|
// dist/test-counts.js
|
|
29837
31773
|
function sum(values) {
|
|
@@ -29866,36 +31802,36 @@ function jestCounts(output) {
|
|
|
29866
31802
|
if (lines2.length === 0)
|
|
29867
31803
|
return null;
|
|
29868
31804
|
let passed = 0;
|
|
29869
|
-
let
|
|
31805
|
+
let failed2 = 0;
|
|
29870
31806
|
for (const line2 of lines2) {
|
|
29871
31807
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29872
|
-
|
|
31808
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29873
31809
|
}
|
|
29874
|
-
return { passed, failed };
|
|
31810
|
+
return { passed, failed: failed2 };
|
|
29875
31811
|
}
|
|
29876
31812
|
function vitestCounts(output) {
|
|
29877
31813
|
const lines2 = [...output.matchAll(/^\s*Tests\s+(.+)$/gm)];
|
|
29878
31814
|
if (lines2.length === 0)
|
|
29879
31815
|
return null;
|
|
29880
31816
|
let passed = 0;
|
|
29881
|
-
let
|
|
31817
|
+
let failed2 = 0;
|
|
29882
31818
|
for (const line2 of lines2) {
|
|
29883
31819
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29884
|
-
|
|
31820
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29885
31821
|
}
|
|
29886
|
-
return { passed, failed };
|
|
31822
|
+
return { passed, failed: failed2 };
|
|
29887
31823
|
}
|
|
29888
31824
|
function pytestCounts(output) {
|
|
29889
31825
|
const lines2 = [...output.matchAll(/^\W*((?:\d+ \w+(?:, )?)+)\s+in [\d.]+s\W*$/gm)];
|
|
29890
31826
|
if (lines2.length === 0)
|
|
29891
31827
|
return null;
|
|
29892
31828
|
let passed = 0;
|
|
29893
|
-
let
|
|
31829
|
+
let failed2 = 0;
|
|
29894
31830
|
for (const line2 of lines2) {
|
|
29895
31831
|
passed += firstNumber(line2[1], /(\d+) passed/) ?? 0;
|
|
29896
|
-
|
|
31832
|
+
failed2 += firstNumber(line2[1], /(\d+) failed/) ?? 0;
|
|
29897
31833
|
}
|
|
29898
|
-
return { passed, failed };
|
|
31834
|
+
return { passed, failed: failed2 };
|
|
29899
31835
|
}
|
|
29900
31836
|
function cargoTestCounts(output) {
|
|
29901
31837
|
const lines2 = [...output.matchAll(/^test result: (?:ok|FAILED)\.\s*(\d+) passed;\s*(\d+) failed;/gm)];
|
|
@@ -29940,19 +31876,19 @@ function isProofTestPath(path) {
|
|
|
29940
31876
|
function testRunsDir(engagementId, home) {
|
|
29941
31877
|
if (!isEngagementId(engagementId))
|
|
29942
31878
|
return null;
|
|
29943
|
-
return
|
|
31879
|
+
return join19(home ?? homedir14(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, TEST_RUNS_DIR_NAME);
|
|
29944
31880
|
}
|
|
29945
31881
|
function commandIdOf(command) {
|
|
29946
|
-
return
|
|
31882
|
+
return createHash5("sha256").update(command, "utf8").digest("hex").slice(0, 16);
|
|
29947
31883
|
}
|
|
29948
31884
|
var PLAIN_WORD = /^[A-Za-z0-9_./:=@%+,-]+$/;
|
|
29949
|
-
function commandFromWords(words,
|
|
31885
|
+
function commandFromWords(words, platform4 = process.platform) {
|
|
29950
31886
|
if (words.length === 1)
|
|
29951
31887
|
return words[0];
|
|
29952
31888
|
return words.map((word) => {
|
|
29953
31889
|
if (PLAIN_WORD.test(word))
|
|
29954
31890
|
return word;
|
|
29955
|
-
return
|
|
31891
|
+
return platform4 === "win32" ? `"${word.replace(/"/g, '""')}"` : quoteForShell(word);
|
|
29956
31892
|
}).join(" ");
|
|
29957
31893
|
}
|
|
29958
31894
|
var PROVE_USAGE = 'Usage: prove <phase> <task> --label "<label>" --before -- <test command>\n prove <phase> <task> --label "<label>" --after [-- <test command>]\n (or --change <id> in place of <phase> <task>; add --reason "<why>" to say why)';
|
|
@@ -30026,21 +31962,21 @@ function parseProveArgs(args2) {
|
|
|
30026
31962
|
};
|
|
30027
31963
|
}
|
|
30028
31964
|
function writeJsonFile2(path, value) {
|
|
30029
|
-
|
|
30030
|
-
const tmp = `${path}.${process.pid}.${
|
|
30031
|
-
|
|
30032
|
-
|
|
31965
|
+
mkdirSync11(dirname11(path), { recursive: true, mode: 448 });
|
|
31966
|
+
const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
31967
|
+
writeFileSync16(tmp, JSON.stringify(value), { mode: 384 });
|
|
31968
|
+
renameSync13(tmp, path);
|
|
30033
31969
|
}
|
|
30034
31970
|
function subjectKey(subject) {
|
|
30035
31971
|
return subject.kind === "task" ? ["task", subject.phase, subject.task] : ["change", subject.change];
|
|
30036
31972
|
}
|
|
30037
31973
|
function keptCheckPath(dir, subject, label) {
|
|
30038
|
-
const key =
|
|
30039
|
-
return
|
|
31974
|
+
const key = createHash5("sha256").update(JSON.stringify([...subjectKey(subject), label]), "utf8").digest("hex");
|
|
31975
|
+
return join19(dir, COMMANDS_DIR_NAME, `${key.slice(0, 32)}.json`);
|
|
30040
31976
|
}
|
|
30041
31977
|
function readKeptCheck(dir, subject, label) {
|
|
30042
31978
|
try {
|
|
30043
|
-
const raw = JSON.parse(
|
|
31979
|
+
const raw = JSON.parse(readFileSync28(keptCheckPath(dir, subject, label), "utf-8"));
|
|
30044
31980
|
if (typeof raw.command !== "string" || raw.command === "" || typeof raw.commandId !== "string")
|
|
30045
31981
|
return null;
|
|
30046
31982
|
const b = raw.before;
|
|
@@ -30051,21 +31987,21 @@ function readKeptCheck(dir, subject, label) {
|
|
|
30051
31987
|
}
|
|
30052
31988
|
}
|
|
30053
31989
|
function queuePath(dir, runId) {
|
|
30054
|
-
return
|
|
31990
|
+
return join19(dir, QUEUE_DIR_NAME, `${runId}.json`);
|
|
30055
31991
|
}
|
|
30056
31992
|
function readQueue(dir) {
|
|
30057
31993
|
let names;
|
|
30058
31994
|
try {
|
|
30059
|
-
names =
|
|
31995
|
+
names = readdirSync10(join19(dir, QUEUE_DIR_NAME)).filter((n) => n.endsWith(".json"));
|
|
30060
31996
|
} catch {
|
|
30061
31997
|
return [];
|
|
30062
31998
|
}
|
|
30063
31999
|
const reports = [];
|
|
30064
32000
|
for (const name of names) {
|
|
30065
|
-
const path =
|
|
32001
|
+
const path = join19(dir, QUEUE_DIR_NAME, name);
|
|
30066
32002
|
let parsed = null;
|
|
30067
32003
|
try {
|
|
30068
|
-
parsed = testRunReportSchema.safeParse(JSON.parse(
|
|
32004
|
+
parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync28(path, "utf-8")));
|
|
30069
32005
|
} catch {
|
|
30070
32006
|
parsed = null;
|
|
30071
32007
|
}
|
|
@@ -30078,7 +32014,7 @@ function readQueue(dir) {
|
|
|
30078
32014
|
}
|
|
30079
32015
|
function removeFile(path) {
|
|
30080
32016
|
try {
|
|
30081
|
-
|
|
32017
|
+
unlinkSync11(path);
|
|
30082
32018
|
} catch {
|
|
30083
32019
|
}
|
|
30084
32020
|
}
|
|
@@ -30118,13 +32054,13 @@ async function sendQueuedRuns(dir, config2, controlOrigin, fetchFn) {
|
|
|
30118
32054
|
}
|
|
30119
32055
|
return outcomes;
|
|
30120
32056
|
}
|
|
30121
|
-
function killTree(child,
|
|
32057
|
+
function killTree(child, platform4, signal) {
|
|
30122
32058
|
const pid = child.pid;
|
|
30123
32059
|
if (pid === void 0)
|
|
30124
32060
|
return;
|
|
30125
32061
|
try {
|
|
30126
|
-
if (
|
|
30127
|
-
|
|
32062
|
+
if (platform4 === "win32") {
|
|
32063
|
+
spawnSync3("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
30128
32064
|
} else {
|
|
30129
32065
|
process.kill(-pid, signal);
|
|
30130
32066
|
}
|
|
@@ -30136,16 +32072,16 @@ function killTree(child, platform3, signal) {
|
|
|
30136
32072
|
}
|
|
30137
32073
|
}
|
|
30138
32074
|
function runCommand(command, repoRoot, logPath, env, deps) {
|
|
30139
|
-
const
|
|
32075
|
+
const platform4 = deps.platform ?? process.platform;
|
|
30140
32076
|
const out = deps.stdout ?? process.stdout;
|
|
30141
32077
|
const err = deps.stderr ?? process.stderr;
|
|
30142
32078
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
30143
32079
|
const childEnv = { ...env };
|
|
30144
32080
|
delete childEnv[SERVICE_TOKEN_VAR];
|
|
30145
|
-
|
|
32081
|
+
mkdirSync11(dirname11(logPath), { recursive: true, mode: 448 });
|
|
30146
32082
|
const log = createWriteStream(logPath, { mode: 384 });
|
|
30147
32083
|
const began = process.hrtime.bigint();
|
|
30148
|
-
return new Promise((
|
|
32084
|
+
return new Promise((resolve8) => {
|
|
30149
32085
|
let timedOut = false;
|
|
30150
32086
|
let started = true;
|
|
30151
32087
|
let settled = false;
|
|
@@ -30154,15 +32090,15 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
30154
32090
|
env: childEnv,
|
|
30155
32091
|
shell: true,
|
|
30156
32092
|
stdio: ["ignore", "pipe", "pipe"],
|
|
30157
|
-
detached:
|
|
32093
|
+
detached: platform4 !== "win32",
|
|
30158
32094
|
windowsHide: true
|
|
30159
32095
|
});
|
|
30160
|
-
const onSignal = (signal) => killTree(child,
|
|
32096
|
+
const onSignal = (signal) => killTree(child, platform4, signal);
|
|
30161
32097
|
process.on("SIGINT", onSignal);
|
|
30162
32098
|
process.on("SIGTERM", onSignal);
|
|
30163
32099
|
const timer = setTimeout(() => {
|
|
30164
32100
|
timedOut = true;
|
|
30165
|
-
killTree(child,
|
|
32101
|
+
killTree(child, platform4, "SIGKILL");
|
|
30166
32102
|
}, deadline);
|
|
30167
32103
|
child.stdout?.on("data", (chunk) => {
|
|
30168
32104
|
out.write(chunk);
|
|
@@ -30180,7 +32116,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
30180
32116
|
process.off("SIGINT", onSignal);
|
|
30181
32117
|
process.off("SIGTERM", onSignal);
|
|
30182
32118
|
const durationMs = Number((process.hrtime.bigint() - began) / 1000000n);
|
|
30183
|
-
log.end(() =>
|
|
32119
|
+
log.end(() => resolve8({ exitCode: timedOut || !started ? null : code, timedOut, started, durationMs }));
|
|
30184
32120
|
};
|
|
30185
32121
|
child.on("error", (e) => {
|
|
30186
32122
|
started = false;
|
|
@@ -30301,14 +32237,14 @@ ${PROVE_USAGE}`);
|
|
|
30301
32237
|
}
|
|
30302
32238
|
if (request.when === "before" && changedOutsideTests > 0)
|
|
30303
32239
|
say(lateBeforeLine(changedOutsideTests));
|
|
30304
|
-
const runId =
|
|
30305
|
-
const logPath =
|
|
32240
|
+
const runId = randomUUID3();
|
|
32241
|
+
const logPath = join19(dir, `${runId}.log`);
|
|
30306
32242
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30307
32243
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
30308
32244
|
const end = await runCommand(command, repoRoot, logPath, env, deps);
|
|
30309
32245
|
let output = "";
|
|
30310
32246
|
try {
|
|
30311
|
-
output =
|
|
32247
|
+
output = readFileSync28(logPath, "utf-8");
|
|
30312
32248
|
} catch {
|
|
30313
32249
|
output = "";
|
|
30314
32250
|
}
|
|
@@ -30358,7 +32294,7 @@ ${PROVE_USAGE}`);
|
|
|
30358
32294
|
|
|
30359
32295
|
// dist/statusline.js
|
|
30360
32296
|
import { spawn as spawn3 } from "node:child_process";
|
|
30361
|
-
import { readFileSync as
|
|
32297
|
+
import { readFileSync as readFileSync29 } from "node:fs";
|
|
30362
32298
|
import nodePath5 from "node:path";
|
|
30363
32299
|
var OWN_STATUS_LINE_DEADLINE_MS = 5e3;
|
|
30364
32300
|
var PERSON_SETTINGS_FILENAME = "settings.json";
|
|
@@ -30396,7 +32332,7 @@ function halfcycleStatusLineOf(figures) {
|
|
|
30396
32332
|
}
|
|
30397
32333
|
function personStatusLineCommand(configFolder) {
|
|
30398
32334
|
try {
|
|
30399
|
-
const v = JSON.parse(
|
|
32335
|
+
const v = JSON.parse(readFileSync29(nodePath5.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
|
|
30400
32336
|
const line2 = v["statusLine"];
|
|
30401
32337
|
if (typeof line2 !== "object" || line2 === null)
|
|
30402
32338
|
return null;
|
|
@@ -30420,14 +32356,14 @@ function personCommandEnv(env) {
|
|
|
30420
32356
|
}
|
|
30421
32357
|
return out;
|
|
30422
32358
|
}
|
|
30423
|
-
var runOwnCommandReal = (command, input, env) => new Promise((
|
|
32359
|
+
var runOwnCommandReal = (command, input, env) => new Promise((resolve8) => {
|
|
30424
32360
|
let out = "";
|
|
30425
32361
|
let settled = false;
|
|
30426
32362
|
const done = (v) => {
|
|
30427
32363
|
if (settled)
|
|
30428
32364
|
return;
|
|
30429
32365
|
settled = true;
|
|
30430
|
-
|
|
32366
|
+
resolve8(v);
|
|
30431
32367
|
};
|
|
30432
32368
|
try {
|
|
30433
32369
|
const child = spawn3(command, {
|
|
@@ -30490,6 +32426,43 @@ async function runStatusline(raw, deps = {}) {
|
|
|
30490
32426
|
`;
|
|
30491
32427
|
}
|
|
30492
32428
|
|
|
32429
|
+
// dist/check-connection.js
|
|
32430
|
+
function connectionKind(bearer, sentence) {
|
|
32431
|
+
if (bearer === NOT_THIS_ACCOUNT_PLACEHOLDER)
|
|
32432
|
+
return "other-account";
|
|
32433
|
+
if (bearer === UPDATE_CLAUDE_CODE_PLACEHOLDER)
|
|
32434
|
+
return "update-claude-code";
|
|
32435
|
+
if (bearer === NO_CREDENTIAL_PLACEHOLDER)
|
|
32436
|
+
return "not-signed-in";
|
|
32437
|
+
if (sentence === CONNECTED_SENTENCE)
|
|
32438
|
+
return "connected";
|
|
32439
|
+
if (sentence === CANNOT_REACH_SENTENCE)
|
|
32440
|
+
return "cannot-reach";
|
|
32441
|
+
if (sentence === SERVICE_PROBLEM_SENTENCE)
|
|
32442
|
+
return "service-problem";
|
|
32443
|
+
if (sentence === NO_CREDENTIAL_SENTENCE)
|
|
32444
|
+
return "signed-out";
|
|
32445
|
+
throw new Error("the connection check gave an outcome with no kind");
|
|
32446
|
+
}
|
|
32447
|
+
async function checkConnectionLine(opts) {
|
|
32448
|
+
const result = await checkConnection(void 0, {
|
|
32449
|
+
...opts,
|
|
32450
|
+
maxAttempts: 1,
|
|
32451
|
+
writeErr: opts.writeErr ?? discardReason
|
|
32452
|
+
});
|
|
32453
|
+
if (result === void 0)
|
|
32454
|
+
return { kind: "not-registered", sentence: null };
|
|
32455
|
+
return { kind: connectionKind(result.bearer, result.sentence), sentence: result.sentence };
|
|
32456
|
+
}
|
|
32457
|
+
async function runCheckConnection(opts) {
|
|
32458
|
+
const line2 = await checkConnectionLine(opts);
|
|
32459
|
+
return `${JSON.stringify(line2)}
|
|
32460
|
+
`;
|
|
32461
|
+
}
|
|
32462
|
+
function discardReason() {
|
|
32463
|
+
return void 0;
|
|
32464
|
+
}
|
|
32465
|
+
|
|
30493
32466
|
// dist/bin.js
|
|
30494
32467
|
var args = process.argv.slice(2);
|
|
30495
32468
|
async function main() {
|
|
@@ -30572,6 +32545,18 @@ async function main() {
|
|
|
30572
32545
|
process.stdout.write(line2, () => process.exit(0));
|
|
30573
32546
|
return;
|
|
30574
32547
|
}
|
|
32548
|
+
if (cmd === "check-connection") {
|
|
32549
|
+
let line2;
|
|
32550
|
+
try {
|
|
32551
|
+
line2 = await runCheckConnection({ binaryPath: fileURLToPath(import.meta.url) });
|
|
32552
|
+
} catch {
|
|
32553
|
+
process.stderr.write("halfcycle-runner: the connection could not be checked.\n");
|
|
32554
|
+
process.exit(1);
|
|
32555
|
+
return;
|
|
32556
|
+
}
|
|
32557
|
+
process.stdout.write(line2, () => process.exit(0));
|
|
32558
|
+
return;
|
|
32559
|
+
}
|
|
30575
32560
|
if (cmd === "mcp-headers") {
|
|
30576
32561
|
const line2 = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
|
|
30577
32562
|
process.stdout.write(line2, () => process.exit(0));
|
|
@@ -30650,8 +32635,18 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30650
32635
|
process.stdout.write(reply, () => process.exit(0));
|
|
30651
32636
|
return;
|
|
30652
32637
|
}
|
|
32638
|
+
if (sub === "user-prompt-findings") {
|
|
32639
|
+
let reply = "";
|
|
32640
|
+
try {
|
|
32641
|
+
reply = runUserPromptFindings({ input: await readStdin2() });
|
|
32642
|
+
} catch {
|
|
32643
|
+
reply = "";
|
|
32644
|
+
}
|
|
32645
|
+
process.stdout.write(reply, () => process.exit(0));
|
|
32646
|
+
return;
|
|
32647
|
+
}
|
|
30653
32648
|
process.stderr.write(`halfcycle-runner: unknown hook "${sub ?? ""}"
|
|
30654
|
-
Valid hooks: post-tool-use, pre-tool-use, stop, subagent-stop, user-prompt-submit
|
|
32649
|
+
Valid hooks: post-tool-use, pre-tool-use, stop, subagent-stop, user-prompt-submit, user-prompt-findings
|
|
30655
32650
|
`);
|
|
30656
32651
|
process.exit(1);
|
|
30657
32652
|
return;
|
|
@@ -30663,7 +32658,9 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30663
32658
|
halfcycle-runner hook stop
|
|
30664
32659
|
halfcycle-runner hook subagent-stop
|
|
30665
32660
|
halfcycle-runner hook user-prompt-submit
|
|
32661
|
+
halfcycle-runner hook user-prompt-findings
|
|
30666
32662
|
halfcycle-runner refresh-credential
|
|
32663
|
+
halfcycle-runner check-connection
|
|
30667
32664
|
halfcycle-runner mcp-headers
|
|
30668
32665
|
halfcycle-runner observe
|
|
30669
32666
|
halfcycle-runner observe --change <sortingId>
|
|
@@ -30678,10 +32675,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
30678
32675
|
process.exit(1);
|
|
30679
32676
|
}
|
|
30680
32677
|
function readStdin3() {
|
|
30681
|
-
return new Promise((
|
|
32678
|
+
return new Promise((resolve8, reject) => {
|
|
30682
32679
|
const chunks = [];
|
|
30683
32680
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
30684
|
-
process.stdin.on("end", () =>
|
|
32681
|
+
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
30685
32682
|
process.stdin.on("error", reject);
|
|
30686
32683
|
});
|
|
30687
32684
|
}
|